24. What does the _objc_msgForward function do, and what happens when you call it directly?

_objc_msgForward is an IMP type for message forwarding: when a message is sent to an object and it is not implemented, _objc_msgForward will attempt to forward the message.

We can create an _objc_msgForward object like this:

IMP msgForwardIMP = _objc_msgForward;

In objC, what is the relationship between sending a message to an object [obj foo] and the objc_msgSend() function? I mentioned objc_msgSend’s role in “messaging.” In the “message passing” process, the action of objc_msgSend is clear: first look for the IMP in the cache of the Class (initialize the cache if there is no cache), and if not, look for the parent Class. If the root class is still not implemented, use the _objc_msgForward function pointer instead of IMP. Finally, execute the IMP.

The Objective-C runtime is open source, so we can see its implementation. Open the Obj package in the Mac code of Apple Open Source and download the latest version. Go to objc-Runtime-new. mm and search _objc_msgForward.

_objc_msgForward

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
* initialize==NO tries to avoid +initialize (but sometimes fails)
* cache==NO skips optimistic unlocked lookup (but uses cache elsewhere)
* Most callers should use initialize==YES and cache==YES.
* inst is an instance of cls or a subclass thereof, or nil if none is known. 
*   If cls is an un-initialized metaclass then a non-nil inst is faster.
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
*   If you don't want forwarding at all, use lookUpImpOrNil() instead.
**********************************************************************/
Copy the code

Objc-runtime-new. mm file for _objc_msgForward:

. / / objc runtime - new mm file associated with _objc_msgForward three functions using pseudo code display / / Created by HTTP: / / https://github.com/ChenYilong / / Copyright (c) Weibo @ iOS application dogs yuan (http://weibo.com/luohanchenyilong/). All rights reserved. / / at the same time, Objc_msgSend (id self, SEL op...) {if(! self)returnnil; IMP imp = class_getMethodImplementation(self->isa, SEL op); imp(self, op, ...) ; // Call this function, pseudo code... } // search IMP IMP class_getMethodImplementation(Class CLS, SEL SEL) {if(! cls || ! sel)return nil;
    IMP imp = lookUpImpOrNil(cls, sel);
    if(! imp)return_objc_msgForward; //_objc_msgForward is used to forward messagesreturn imp;
}
 
IMP lookUpImpOrNil(Class cls, SEL sel) {
    if(! cls->initialize()) { _class_initialize(cls); } Class curClass = cls; IMP imp = nil;do{// check the cache first, if there is no cache, rebuild, if there is still no parent class queryif(! curClass)break;
        if(! curClass->cache) fill_cache(cls, curClass); imp = cache_getImp(curClass, sel);if (imp) break;
    } while (curClass = curClass->superclass);
 
    return imp;
}
Copy the code

Although Apple does not have the source code for the public _objc_msgForward, we can still conclude that:

_objc_msgForward is a function pointer (of the same type as IMP) that is used for message forwarding: when a message is sent to an object and it is not implemented, _objc_msgForward attempts to forward the message.

In objC, what is the relationship between sending a message to an object [obj foo] and the objc_msgSend() function? I mentioned objc_msgSend’s role in “messaging.” In the “message passing” process, the action of objc_msgSend is clear: first look for the IMP in the cache of the Class (initialize the cache if there is no cache), and if not, look for the parent Class. If the root class is still not implemented, use the _objc_msgForward function pointer instead of IMP. Finally, execute the IMP.

To demonstrate the action of message forwarding, try sending an error message to an object and see how _objc_msgForward forwards.

Start by turning on debug mode and printing out all messages sent at runtime: you can execute the following methods in your code:

(void)instrumentObjcMessageSends(YES);

Because this function is in objc-internal.h, and the file is not open, it is called with a declaration first. The purpose is to tell the compiler that the object file contains the method and let the compilation pass

OBJC_EXPORT void instrumentObjcMessageSends (BOOL flag) OBJC_AVAILABLE (10.0, 2.0, 9.0, 1.0, 2.0);Copy the code

Or a breakpoint to pause the program and type the following command in GDB:

call (void)instrumentObjcMessageSends(YES)

In the second case, the operations are as follows:

After that, all messages sent by the runtime are printed to the/TMP/msgsend-xxxx file. Enter the command in the terminal to go to:

open /private/tmp

You may see more than one, find the latest generated, double-click open

Execute the following statement on the emulator (this debugging scheme only applies to the emulator, the real machine is not available, about the debugging scheme extension link: Can the messages be sent to an object in objective-c be monitored or printed out?) Send an error message to an object:

/ / / / the main m / / CYLObjcMsgForwardTest / / / / Created by HTTP: / / http://weibo.com/luohanchenyilong/. / / Copyright (c) 2015 Weibo @ios program dog Yuan. All rights reserved. //#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "CYLTest.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        CYLTest *test = [[CYLTest alloc] init];
        [testPerformSelector :(@selector(iOS program dog yuan))];returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

/tmp/msgSend-xxxx

+ CYLTest NSObject initialize
+ CYLTest NSObject alloc
- CYLTest NSObject init
- CYLTest NSObject performSelector:
+ CYLTest NSObject resolveInstanceMethod:
+ CYLTest NSObject resolveInstanceMethod:
- CYLTest NSObject forwardingTargetForSelector:
- CYLTest NSObject forwardingTargetForSelector:
- CYLTest NSObject methodSignatureForSelector:
- CYLTest NSObject methodSignatureForSelector:
- CYLTest NSObject class
- CYLTest NSObject doesNotRecognizeSelector:
- CYLTest NSObject doesNotRecognizeSelector:
- CYLTest NSObject class
Copy the code

In conjunction with the Official NSObject documentation, which excludes what NSObject does, there are just a few things that _objc_msgForward message forwarding does:

  1. callresolveInstanceMethod:Method (orresolveClassMethod:). Allows users to dynamically add implementations to the Class at this point. If there is an implementation, it is called and returns YES, and you start againobjc_msgSendProcess. This time the object responds to the selector, usually because it has already been calledclass_addMethod. If not, proceed with the following action.
  2. callforwardingTargetForSelector:Method to try to find an object that can respond to the message. If it does, the message is forwarded directly to it, returning a non-nil object. Otherwise return nil and continue. Be careful not to return self here, otherwise it will form an infinite loop.
  3. callmethodSignatureForSelector:Method to try to get a method signature. If not, call it directlydoesNotRecognizeSelectorThrow an exception. If available, return non-nil: create an NSlnvocation and pass itforwardInvocation:.
  4. callforwardInvocation:Method, which wraps the method signature from Step 3 as the Invocation passed in, how to handle it in there, and returns non-nil.
  5. calldoesNotRecognizeSelector:The default implementation is to throw an exception. If step 3 fails to obtain a method signature, perform this step.

The above four methods are template methods that developers can override and call from the Runtime. The most common implementation of message forwarding is overriding methods 3 and 4, where it is ok to swallow a message or delegate it to another object

In other words, _objc_msgForward involves the following methods in the process of message forwarding:

  1. resolveInstanceMethod:Method (orresolveClassMethod:).
  2. forwardingTargetForSelector:methods
  3. methodSignatureForSelector:methods
  4. orwardInvocation:methods
  5. doesNotRecognizeSelector:methods

To get a better understanding of how these methods work, git repository also provides a Demo called “_objc_msgForward_demo” that you can run.

Let’s answer the second question, “What happens when you call it directly _objc_msgForward?”

Calling _objc_msgForward directly is a very dangerous thing to do and will Crash the program if used badly, but it can do a lot of cool things if used well.

Like parkour, do good, called “play cool”, do not call “die”.

As mentioned above:

_objc_msgForward is an IMP type for message forwarding: when a message is sent to an object and it is not implemented, _objc_msgForward will attempt to forward the message.

How do I call _objc_msgForward? _objc_msgForward belongs to THE C language and has three parameters:

_objc_msgForwardparameter type
1. Subordinate to the object Id type
2. The method name SEL type
3. Variable parameter Variable parameter type

First understand how to call IMP type method, IMP type is the following format:

For intuition, we can define an IMP type as follows:

typedef void (*voidIMP)(id, SEL, ...)

Once _objc_msgForward is called, the IMP lookup process is skipped and “message forwarding” is triggered,

If you call _objc_msgForward, you’ll tell objc_msgSend, even if the object actually implements this method:

“I don’t find an implementation of this method in this object.”

What are the scenarios where you need to call _objc_msgForward directly? The most common scenario is when you want to get the NSInvocation object for a method. For example:

JSPatch (Github link) calls _objc_msgForward directly to implement its core functionality:

JSPatch’s compact size allows JS to call/replace any OC method, enabling iOS apps to be hot updated.

The author’s blog post JSPatch Implementation Details the implementation principle, if you are interested in it.

This method is also used in RAC(ReactiveCocoa) source code.

25. How does runtime automatically set weak variables to nil?

The Runtime lays out the registered classes and puts weak objects into a hash table. If the reference count of this object is 0, dealloc will be dealloc. If the memory address of the weak object is A, then a will be searched in the weak table, find all the weak objects with a as the key, and set to nil. How the Runtime implements the weak attribute was discussed in the previous article. Do objects associated with the Runtime Associate method need to be released when the main object is dealloc? The object’s memory destruction schedule also mentions the removal time of __weak references.

We can design a function (pseudocode) to represent the above mechanism:

The objc_storeWeak(&a, b) function:

The objc_storeWeak function registers the memory address of the assignment object (b) as key and the memory address of the weak variable (a) as value. If the second argument (b) is 0 (nil), then remove the memory address (&a) of variable (a) from the weak table.

You can think of objc_storeWeak(&a, b) as objc_storeWeak(value, key), and set value to nil when key goes nil.

A and B point to the same memory address when B is non-nil, and a becomes nil when B becomes nil. Sending a message to A does not crash at this point: it is safe to send a message to nil in Objective-C.

If a is assigned, then a and B refer to the same memory address when B is not nil. If B becomes nil, A refers to the same memory address again. In this case, sending messages to A may crash.

Here we will use pseudocode to simulate how the Runtime implements the weak property based on the objc_storeWeak(&a, b) function:

/ / using pseudo code simulation: how to implement the runtime weak properties / / http://weibo.com/luohanchenyilong/ / / https://github.com/ChenYilong id obj1; objc_initWeak(&obj1, obj); /* objc_destroyWeak(&obj1); /* objc_destroyWeak(&obj1);Copy the code

The objc_initWeak and objc_destroyWeak methods are explained below:

In general, it initializes the “variable with the weak modifier (obj1)” with the objc_initWeak function, and releases it at the end of the scope with the objc_destoryWeak function (obj1).

The internal implementation of the methods is described below:

The implementation of the objc_initWeak function looks like this: after initializing the variable with the weak modifier (obj1) to 0 (nil), the objc_storeWeak function is called with the assignment object (obj) as an argument.

Obj1 = 0; obj_storeWeak(&obj1, obj);Copy the code

In other words:

The pointer to the weak modifier defaults to nil (it is safe to send messages to nil in Objective-C)

The obj_destroyWeak function then calls the objc_storeWeak function, taking 0 (nil) as an argument.

objc_storeWeak(&obj1, 0);

The previous source code is the same as the following.

/ / using pseudo code simulation: how to implement the runtime weak properties / / http://weibo.com/luohanchenyilong/ / / https://github.com/ChenYilong id obj1; obj1 = 0; objc_storeWeak(&obj1, obj); / *... Obj's reference count becomes 0, set to nil... */ objc_storeWeak(&obj1, 0);Copy the code

The objc_storeWeak function takes the memory address of the assignment object (obj) as the key value and registers the memory address of the property variable (obj1) modified with weak as the first argument into the weak table. If the second argument (obj) is 0 (nil), remove the address of the variable (obj1) from the weak table.

26. Can I add instance variables to the compiled class? Can I add instance variables to classes created at run time? Why is that?

  • You cannot add instance variables to compiled classes.
  • Ability to add instance variables to classes created at run time

Explain:

  • Because the compiled classes are already registered in the Runtime, the class structureobjc_ivar_listThe linked list sum of instance variablesinstance_sizeThe memory size of the instance variable is determined and the Runtime callsclass_setIvarLayoutclass_setWeakIvarLayoutTo handle strong weak references. So you can’t add instance variables to existing classes;
  • Classes created at run time can be called by adding instance variablesclass_addIvarFunction. But it has to be calledobjc_allocateClassPairAfter that,objc_registerClassPairBefore, for the same reason.

27. What does runloop have to do with threads?

In general, Run loop, as the name suggests, is a loop of some kind, and together with Run means a loop that is always running. In fact, run loop and thread are closely related. It can be said that run loop is for thread, without thread, it has no need to exist. Run Loops are part of the thread infrastructure. Both Cocoa and CoreFundation provide Run Loop objects for configuring and managing Run loops for threads (Both Cocoa and CoreFundation use the following examples). Each thread, including the main thread of the program, has a corresponding Run loop object.

Runloop and thread:

  1. The run loop for the main thread is started by default.

In iOS apps, there is a function called main() after the app starts

int main(int argc, char * argv[]) {
   @autoreleasepool {
       returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

The UIApplicationMain() function sets an NSRunLoop object for the main thread, which explains why our application can rest when no one is working and respond when it needs to.

  1. For other threads, run loop is not started by default, and can be configured and started manually if you need more thread interaction, but not if the thread is just going to perform a long, determined task.
  2. In any Cocoa thread, you can get the run loop of the current thread by using the following code.

NSRunLoop *runloop = [NSRunLoop currentRunLoop]; Reference: Objective-C Run Loop in detail.

28. What is the mode function of runloop?

Model is mainly used to specify the priority of events in the run cycle, divided into:

  • NSDefaultRunLoopMode (kCFRunLoopDefaultMode) : the default, idle state
  • UITrackingRunLoopMode: when ScrollView slides
  • UIInitializationRunLoopMode: at the start
  • NSRunLoopCommonModes (kCFRunLoopCommonModes) : Set of modes

There are two modes publicly provided by Apple:

  1. NSDefaultRunLoopMode (kCFRunLoopDefaultMode)
  2. NSRunLoopCommonModes (kCFRunLoopCommonModes)

29. + scheduledTimerWithTimeInterval… When sliding the list on the page, the timer will temporarily callback, why? How to solve it?

A RunLoop can only run in one mode. To change mode, the current loop must also be stopped and restarted. Using this mechanism, the mode of NSDefaultRunLoopMode (kCFRunLoopDefaultMode) will switch to UITrackingRunLoopMode during ScrollView to ensure smooth sliding of ScrollView: Events that can only be handled in NSDefaultRunLoopMode affect ScrollView sliding.

If we add an NSTimer object to the main running loop as NSDefaultRunLoopMode (kCFRunLoopDefaultMode), during the ScrollView, the NSTimer will no longer be scheduled because of the mode switch.

And since mode is customizable:

The problem of Timer timing being affected by scrollView sliding can be solved by adding Timer modes to NSRunLoopCommonModes (kCFRunLoopCommonModes). The code is as follows:

/ / / / http://weibo.com/luohanchenyilong/ (weibo @ iOS application dogs yuen) / / https://github.com/ChenYilong / / add the timer to the NSDefaultRunLoopMode [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @ the selector (timerTick:) the userInfo: nil repeats: YES]; / / and then added to the NSRunLoopCommonModes NSTimer * timer = [NSTimer timerWithTimeInterval: 1.0 target: the self selector:@selector(timerTick:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSRunLoopCommonModes];
Copy the code

30. Guess how runloop is implemented internally?

Generally speaking, a thread can only execute one task at a time, and when it is finished, the thread exits. If we need a mechanism for threads to handle events at any time without exiting, the usual code logic looks like this:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while(message ! = quit); }Copy the code

Or use pseudocode to show it:

/ / / / http://weibo.com/luohanchenyilong/ (weibo @ iOS application dogs yuen) / / https://github.com/ChenYilong int main (int arg c, Char * argv[]) {// The program is always runningwhileWhoWakesMe = SleepForWakingUp(); Event = GetEvent(whoWakesMe); // Start handling the event HandleEvent(event); }return 0;
}
Copy the code

Reference links:

  1. Understanding RunLoop in Depth
  2. From CFRunLoop, originally written by @Sunny What’s wrong

31. What mechanism does ObjC use to manage object memory?

RetainCount is used to determine if an object needs to be released. Every time the runloop is run, the object’s retainCount is checked. If the retainCount is 0, the object has no further use and can be released.

32. In what ways does ARC help developers manage memory?

ARC compared with MRC, not add at compile time retain/release/autorelease so simple. Compile time and run time work together to help developers manage memory.

At compile time, the ARC is more at the bottom of the C interface implementation retain/release/autorelease, do better, and why not in manual retain/release/autorelease ARC environment, Simultaneously optimizing pairs of retain/release operations on the same object in the same context (that is, ignoring unnecessary operations); ARC also includes run-time components, where optimization is more complex, but not negligible.

33. When can an Autorealese object be released without manually specifying autoReleasepool? (such as in a VC viewDidLoad)

It can be divided into two cases: manual intervention of release timing and automatic release by the system.

  1. Manual intervention with release timing – Specifying that autoReleasepool is released when the current scope braces end.
  2. Autoreleasepool Autorelease automatically — Once the AutoReleasepool Autorelease object is out of scope, it is added to the Autorelease pool created last time and released at the end of the current runloop iteration.

The timing of release can be summarized as follows:

There is a complete run loop from the start of the program to the completion of the load, and then it stops and waits for the user to interact. Each interaction of the user starts a run loop to process all the user’s click events, touch events.

We all know that all autoRelease objects, once out of scope, are automatically added to the recently created autorelease pool.

But if you put autoReleasepool in your application’s main.m every time, it will fill up sooner or later. There must be an act of release. When?

It is destroyed before the completion of a complete run cycle.

So when is the automatic release pool created? Once the run cycle detects the event and starts, the automatic release pool is created.

[NSRunLoop CurrentRunLoop] [NSRunLoop CurrentRunLoop] [NSRunLoop CurrentRunLoop] [NSRunLoop CurrentRunLoop] Create a RunLoop for a child thread in the form of lazy loading. And stored in a global mutable dictionary. The RunLoop is created automatically when the programmer calls [NSRunLoop CurrentRunLoop].

You need to manually create automatic release pools for user-defined NSOperation and NSThread. For example, automatic release pools must be added to the main method of the custom NSOperation class. Otherwise, when the autofree object is out of scope, it will leak memory because there is no autofree pool to process it.

However, for the default operations, blockOperation and invocationOperation, the system has already wrapped them for us. There is no need to manually create automatic release pools.

@autoreleasepool when the autoreleasepool is destroyed or exhausted, a release message is sent to all objects in the autoreleasepool torelease all objects in the autoreleasepool.

If you create an Autorelease object in a VC viewDidLoad, it will be destroyed before the viewDidAppear method executes.

Autorelease behind the Curtain

34. Under what circumstances does BAD_ACCESS appear?

Access dangling Pointers, such as releasing an object that has been freed, accessing a member variable of a freed object, or sending a message. Infinite loop

35. How does apple implement autoreleasepool?

Autoreleasepool is implemented as a queue array through the following three functions.

  1. objc_autoreleasepoolPush
  2. objc_autoreleasepoolPop
  3. objc_autorelease

As you can see from the name of the function, push and POP are performed on the autorelease. The release operation is performed when the object is destroyed.

For example, we know that objects created using class methods are Autorelease objects, so once Person is out of scope, when a breakpoint is placed on Person’s Dealloc method, we can see the call stack like this:

37. When do reference loops occur when using blocks, and how do I resolve them?

A circular reference is emitted when a block is strongly referenced in an object and the object is strongly referenced in a block.

The solution is to modify the object with an __weak or __block modifier before using it in a block.

  1. id weak weakSelf = self; Weak __typeof(&*self)weakSelf = self This method can set macro
  2. id __block weakSelf = self; Or force one of the parties to null XXX = nil.

To detect circular reference problems in your code, you can use FBRetainCycleDetector, an open source detection tool from Facebook.

37. How do I modify external block variables inside a block?

By default, external variables accessed in a block are copied over; that is, write operations do not take effect on the original variable. But you can add __block to make it work, as shown in the following example:

__block int a = 0; void (^foo)(void) = ^{ a = 1; }; foo(); // Here, the value of a is changed to 1Copy the code

This is described in chapter 11.2.3 of iOS Development Progress by Weibo @Tang Qiao_boy. You could have said the same thing in an interview, but you didn’t get the “right” answer. The real reason isn’t as “magical” as the book suggests, and it’s a bit of a stretch. The interviewer is sure to ask, “Why does writing work?” The real reason is this:

As we all know, blocks do not allow you to change the value of an external variable, which is the memory address of the pointer on the stack. All __block does is put the memory address of the “external variable” in the stack into the heap whenever it is observed that the variable is held by the block. In turn, you can modify the values of external variables inside the block.

Block does not allow changing the value of an external variable. Apple’s design in this way should have taken into account the particularity of blocks, which also belong to the category of “functions”. When a variable enters a block, it actually changes its scope. When switching between scopes, variables become much less maintainable without such a restriction. Or if I want to declare a variable with the same name as the outside inside the block, should I allow it or not? Such a scenario can only be achieved if such restrictions are imposed. So the stack area becomes the red light area, the stack area becomes the green light area.

We can print the memory address to verify:

  __block int a = 0;
   NSLog(@"Before definition: %p", &a); // stack void (^foo)(void) = ^{a = 1; NSLog(@"Inside block: %p", &a); / / heap area}; NSLog(@"After definition: %p", &a); / / heap area foo ();Copy the code
LeanCloudChatKit-iOS[1505:713679] 0x16FDA86F8 2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] Defined: 0x155b22fc8 2016-05-17 02:03:33.559 LeanCloudChatKit-iOS[1505:713679] Block internal: 0x155B22fc8
Copy the code

We know that variables in a block are copied to the heap. “Inside the block” prints the heap address, so we know that “inside the block” prints the heap address.

So how do you prove that “inside the block” prints the heap address?

Convert three hexadecimal memory addresses to base 10:

  1. Before definition: 6171559672
  2. Inside the block: 5732708296
  3. After definition: 5732708296

Because the heap address is smaller than the stack address, and because the stack memory of a process in iOS is only 1M, and the Mac is only 8M, it is obvious that A is already in the heap.

This also confirms that a was the stack area before the definition, but becomes the heap area as soon as it enters the block area. This is what the __block keyword is really for.

The __block keyword modifier also changes the int type from 4 bytes to 32 bytes, which comes from the Foundation framework Malloc. This also confirms the above conclusion. PS: Twice as many bytes as the NSObject alloc.

It is important to understand that this is due to a stack address change, not a “write effect”, otherwise how do you explain the following phenomenon:

The following code compiles and successfully changes a from Tom to Jerry in the block.

   NSMutableString *a = [NSMutableString stringWithString:@"Tom"];
   NSLog(@"\ n set before, -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- \ n \ a point to the heap address: % p; Pointer to a on stack: %p", a, &a); Void (^foo)(void) = ^{a.tring = @"Jerry";
       NSLog(@"\ n block internal: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - \ n \ a point to the heap address: % p; Pointer to a on stack: %p", a, &a); //a = [NSMutableString stringWithString:@"William"];
   };
   foo();
   NSLog(@After "\ n: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - \ n \ a point to the heap address: % p; Pointer to a on stack: %p", a, &a); //a is in the stack! [](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/15/16e6cbdf53b0673a~tplv-t2oaga2asx-image.imag e)Copy the code

So here a has gone from a primitive data type to an object type. A block copies a pointer to an object type into the heap, but it does not change the address in the heap to which the pointer points, so in the example above, the block actually modifies the contents of the heap to which A points.

But if we try to do something like line 65 in the image above, it will fail because you are not modifying what is in the heap, but what is in the stack.

As mentioned above, blocks do not allow changes to the values of external variables, which are the memory addresses of Pointers on the stack. Stack area is the red light area, heap area is the green light area.

38. Are reference loops also considered when using some of the system’s block apis (such as the block version of UIView when writing animations)?

There are some block apis in the system where the block version of UIView doesn’t need to be considered when writing animations, but there are also some apis that need to be considered:

A “reference loop” is a two-way strong reference, so there is no problem with a “one-way strong reference” (block strong reference self), such as this:

[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }]; 
Copy the code
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }]; 
Copy the code
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
                                                 object:nil 
                          queue:[NSOperationQueue mainQueue]
                                             usingBlock:^(NSNotification * notification) {
                                                   self.someProperty = xyz; }]; 
Copy the code

There is no need for “reference loops” in these cases.

However, if you use system apis such as GCD and NSNotificationCenter that may contain IVAR in their arguments, be careful: for example, if self is referenced inside GCD, and the other arguments of GCD are ivar, consider circular references:

__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse]; });Copy the code

Similar:

 __weak __typeof__(self) weakSelf = self;
 _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                               object:nil
                                                                queue:nil
                                                           usingBlock:^(NSNotification *note) {
     __typeof__(self) strongSelf = weakSelf;
     [strongSelf dismissModalViewControllerAnimated:YES];
 }];
Copy the code

Self –> _observer –> block –> self obviously this is also a circular reference.

To detect circular reference problems in your code, you can use FBRetainCycleDetector, an open source detection tool from Facebook.

39. What are the two types of DISPATch_queue_t for GCD?

  1. Serial Dispatch Queue Serial Dispatch Queue
  2. Concurrent Dispatch Queue

40. How to synchronize several asynchronous calls with GCD? (For example, load multiple images asynchronously based on several urls, and then compose a whole image after downloading them all)

Use the Dispatch Group to append blocks to the Global Group Queue. If all of these blocks are executed, the end-of-processing block in the Main Dispatch Queue is executed.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); Dispatch_group_async (group, queue, ^{/* load picture 1 */}); Dispatch_group_async (group, queue, ^{/* load picture 2 */}); Dispatch_group_async (group, queue, ^{/* load picture 3 */}); Dispatch_group_notify (dispatch_get_main_queue(), ^{// merge picture});Copy the code

41. What is the function of dispatch_barrier_async?

In parallel queues, in order to maintain the order of some tasks, it is necessary to wait for some tasks to complete before proceeding. Barrier is used to wait for the completion of previous tasks to avoid problems such as data contention. The dispatch_barrier_async function waits for all operations appended to the Concurrent Dispatch Queue to complete and then performs the appended processing of dispatch_barrier_async. The Concurrent Dispatch Queue does not resume operations until the appended processing of dispatch_barrier_async is complete.

Take an example: for example your company weekend with group travel, high-speed rest station, the driver said: everyone to go to the toilet, quick battle quick decision, after the toilet on the highway. The big public toilet, everybody goes at the same time, the program monkey is over quickly, but the program girl may be slower, even if you come back first, the driver will not start, the driver has to wait for everyone to come back, before starting. The dispatch_barrier_async function appends something like “go to the highway after going to the toilet”.

(Note: using dispatch_barrier_async, this function can only be used with custom parallel queue dispatch_queue_t. Do not use “dispatch_get_global_queue”, otherwise dispatch_barrier_async does exactly the same as dispatch_async.)

42. Why is Dispatch_get_current_queue being scrapped?

The dispatch_get_CURRENT_queue function often behaves differently from what developers expect. Since dispatch queues are hierarchically organized, this means that blocks in a queue are executed in their parent queue. The hierarchy between queues makes it not always effective to check whether the current queue is the one used to perform a synchronous dispatch. The dispatch_get_current_queue function is typically used to resolve deadlocks caused by code that cannot be reentrant, and can then be used to resolve problems with queue-specific data.

43. What is the result of the following code?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}
Copy the code

Only: 1 is printed. The main thread locks up.

44. AddObserver: forKeyPath: options: context: what are the effect of various parameters, which need to implement in the observer method to obtain KVO callback?

// Add key-value observation /* 1 observer, responsible for handling the listening event of the object 2 observation properties 3 observation options 4 context */ [self.person addObserver:selfforKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
Copy the code

The following methods need to be implemented in observer:

// All kVO listener events are called /* 1. Properties observed 2. Objects observed 3. Change Property change dictionary (new/old) 4. Context, */ - (void) observeforkeypath :(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;Copy the code

45. How do I manually trigger a VALUE’s KVO

The so-called “manual trigger” is different from “automatic trigger” :

Automatic triggering refers to a scenario where an initial value is set before registering KVO and a different value is set after registering.

To know how to trigger manually, you must know how to trigger KVO automatically:

Key-value observation notifications depend on two methods of NSObject: willChangeValueForKey: and didChangevlueForKey:. Before an observed property changes, willChangeValueForKey: must be called, which records the old value. When change happens, observeValueForKey: ofObject: change: context: is called, which in turn didChangeValueForKey: will be invoked. If you could implement these calls manually, you would be “manually triggered”.

So what are the use scenarios for manual triggering? We usually only do this when we want to control the “timing of callbacks.”

Specific practices are as follows:

If the value is self.now for time, the code looks like this: the last two lines are required.

The relevant code is already in the warehouse.

/ / / / m file Created by HTTP: / / https://github.com/ChenYilong / / weibo @ iOS application dogs yuan (http://weibo.com/luohanchenyilong/). / / a manual trigger value KVO, the last two lines of code are indispensable. //@property (nonatomic, strong) NSDate *now; - (void)viewDidLoad { [super viewDidLoad]; _now = [NSDate date]; [self addObserver:selfforKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
   NSLog(@"1");
   [self willChangeValueForKey:@"now"]; // "Manually trigger self.now KVO", mandatory. NSLog(@"2");
   [self didChangeValueForKey:@"now"]; // "Manually trigger self.now KVO", mandatory. NSLog(@"4");
}
Copy the code

But we don’t usually do that. We wait for the system to “trigger” itself. Implementation principle of “automatic trigger” :

For example, setNow: will somehow insert wilChangeValueForKey: didChangeValueForKey: And observeValueForKeyPath: ofObject: change: context: the call.

You might think this is because setNow: is a composition method, but sometimes we see people writing code like this:

- (void)setNow:(NSDate *)aDate {
   [self willChangeValueForKey:@"now"]; _now = aDate; [self didChangeValueForKey:@"now"]; // No need to}Copy the code

This is totally unnecessary, don’t do it, because the KVO code will be called twice. KVO always calls willChangeValueForKey: before calling the access method and didChangeValueForkey: after that. How did you do that? The answer is isa mash-up (ISa-swizzling). How does Apple implement KVO for an object? There will be more details.

Reference link: Manual Change Notification– Official Apple documentation

46. If a class has an instance variable NSString *_foo, can setValue:forKey: be called with foo or _foo as the key?

Will do.

47. How to use the set operator in keyPath of KVC?

  1. Must be used on a collection object or on a collection property of a normal object
  2. The simple set operators are @avg, @count, @max, @min, @sum,
  3. Format @”@sum.age” or @” collection [email protected]

48. Must the keyPath of KVC and KVO be attributes?

KVC supports instance variables, KVO can only manually support manually set instance variables KVO implementation listening

49. How do I turn off the default implementation of default KVO and enter the custom IMPLEMENTATION of KVO?

Please refer to:

  1. How to Implement KVO yourself
  2. KVO for manually implemented properties

50. How does Apple implement KVO for an object?

Apple’s documentation describes the KVO implementation as follows:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

As you can see from Apple’s documentation, Apple doesn’t want to reveal too much about the implementation details of KVO. However, if you dig deeper with the methods provided by The Runtime, all the hidden details will show up:

When you observe an object, a new class is created dynamically. This class inherits from the object’s original class and overwrites the setter methods for the property being observed. The overridden setter method is responsible for notifying all observed objects of value changes before and after the original setter method is called. Finally, isa-swizzling is used to point the isa pointer to the object (the ISA pointer tells the Runtime system what the object’s class is) to the newly created subclass, and the object magically becomes an instance of the newly created subclass. I drew a schematic diagram as follows:

KVO does have a bit of dark magic:

Apple uses ISa-Swizzling to implement KVO.

Here’s a detailed explanation:

Key-value observation notifications depend on two methods of NSObject :willChangeValueForKey: and didChangevlueForKey:. Before an observed property changes, willChangeValueForKey: must be called, which records the old value. When change happens, observeValueForKey: ofObject: change: context: is called, which in turn didChangeValueForKey: will be invoked. You can implement these calls manually, but few people do. We usually do this only when we want to control when the callback is called. In most cases, change notifications are invoked automatically.

For example, when setNow: is called, the system also inserts wilChangeValueForKey in the middle somehow: , didChangeValueForKey: and observeValueForKeyPath: ofObject: change: context: the call. You might think this is because setNow: is a composition method, but sometimes we see people writing code like this:

- (void)setNow:(NSDate *)aDate {
   [self willChangeValueForKey:@"now"]; _now = aDate; [self didChangeValueForKey:@"now"]; // No need to}Copy the code

This is totally unnecessary, don’t do it, because the KVO code will be called twice. KVO always calls willChangeValueForKey: before calling the access method and didChangeValueForkey: after that. How did you do that? The answer is isa mash-up (ISa-swizzling). For the first time for an object call addObserver: forKeyPath: options: context: when the framework will create new KVO subclasses of this class and will be observed object into a new subclass object. In this KVO special subclass, Cocoa creates setters for observation properties, which work roughly as follows:

- (void)setNow:(NSDate *)aDate {
   [self willChangeValueForKey:@"now"];
   [super setValue:aDate forKey:@"now"];
   [self didChangeValueForKey:@"now"];
}
Copy the code

This inheritance and method injection is implemented at run time, not compile time. That’s why it’s so important to get it right. KVO can do this only if it uses the KVC naming convention.

KVO implements isa mash-up (ISa-swizzling) by pointing the isa pointer to the object (isa tells the Runtime system what the object’s class is) to the newly created subclass, and the object magically becomes an instance of the newly created subclass. This can be confirmed in Apple’s documentation:

Automatic key-value observing is implemented using a technique called isa-swizzling… When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class …

However, KVO uses ISa-swizzling in its implementation, which is really not easy to see: Apple also overwrites, overrides, and returns the original class. Trying to trick us: this class is the same, it’s the same class…

However, given that the class object of “listened on” is MYClass, sometimes we see a reference to NSKVONotifying_MYClass instead of a reference to MYClass. This tells us that Apple uses ISa-swizzling. See this blog post for details.

So wilChangeValueForKey:, didChangeValueForKey: and observeValueForKeyPath: ofObject: change: context: the three methods of execution order is what kind of?

WilChangeValueForKey:, didChangeValueForKey: is easy to understand, observeValueForKeyPath: ofObject: change: context: the execution time is what time?

Let’s start with an example:

The code is in the warehouse.

- (void)viewDidLoad {
   [super viewDidLoad];
   [self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
   NSLog(@"1");
   [self willChangeValueForKey:@"now"]; // "Manually trigger self.now KVO", mandatory. NSLog(@"2");
   [self didChangeValueForKey:@"now"]; // "Manually trigger self.now KVO", mandatory. NSLog(@"4");
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   NSLog(@"3");
}
Copy the code

If just from the print of the following example,

The order seems wilChangeValueForKey:, observeValueForKeyPath: ofObject: change: context:, didChangeValueForKey:.

Actually otherwise, there is a observeValueForKeyPath: ofObject: change: context:, and didChangeValueForKey: Who first call problem: if observeValueForKeyPath: ofObject: change: context: in didChangeValueForKey: the operation of the internal trigger? Then the order is: wilChangeValueForKey:, didChangeValueForKey: and observeValueForKeyPath: ofObject: change: context:

Don’t believe you put didChangeValueForKey: watch out, see observeValueForKeyPath: ofObject: change: context: will execute.

It’s important to know this, as KVO of 45. How to manually trigger a value says:

What are the use scenarios for manual triggering? We usually only do this when we want to control the “timing of callbacks.”

The “call time for the callback” is when you call the didChangeValueForKey: method.

51. Why can the view attribute connected to IBOutlet be set to weak?

Should IBOutlets be strong or weak under ARC?

The article tells us:

Because if there’s an outer chain then the view must exist in the XIB or in the storyboard, the view already has a strong reference to it.

But the answer missed an important knowledge, use the storyboard xib (no) created by vc, there will be a private array called _topLevelObjectsToKeepAliveFromStoryboard strong reference all the top level object, So it doesn’t matter if your outlet is declared weak

52. How to use User Defined Runtime Attributes in IB?

It can configure properties via KVC that you can’t configure in Interface Builder. When you want to do as much as possible in IB, this feature can help you write more lightweight viewControllers

53. How do I debug BAD_ACCESS errors

  1. Override the respondsToSelector method of an object to implement the last object accessed before EXEC_BAD_ACCESS appears
  2. Through the Zombie
  3. Set a global breakpoint to quickly locate the line of problem code
  4. Xcode 7 has integrated BAD_ACCESS capture: Address Sanitizer. Usage is as follows: Select Enable Address Sanitizer in the configuration box

54. Common debugging commands for LLDB (GDB)

  • Breakpoint Sets breakpoints to locate a function
  • N Breakpoint pointer Next step
  • Po print object

You can view more LLDB (GDB) debugging commands

  1. The LLDB Debugger.
  2. Apple official documentation: iOS Debugging Magic.

The article source

“Recruitment of a reliable iOS” interview question reference answer (ii)