This article will learn some knowledge points related to run loop in our daily development. When we use them, we may not think that the knowledge points are actually supported by Run loop.

Review the Run Loop Mode item

Let’s review the Source/Timer/Observer again, because it is through these Run Loop mode items that the Run loop provides external functionality.

  1. CFRunLoopSourceRef is where the event is generated. Source has two versions: Source0 and Source1.
  • Source0 contains only one callback (function pointer) and does not actively fire events. To use this, you need to call CFRunLoopSourceSignal(source), mark the source as pending, and then manually call CFRunLoopWakeUp(runloop) to wake up the Runloop to handle the event.
  • Source1 contains a mach_port and a callback (function pointer) that is used to send messages to each other (mach_msg) through the kernel and other threads. This Source actively wakes up the RunLoop thread.

Looking at their associated data structures, CFRunLoopSourceContext and CFRunLoopSourceContext1 have some of the same and different fields.

typedef struct {
    CFIndex version;
    void * info; // as an argument to perform
    const void *(*retain)(const void *info); / / retain functions
    void (*release)(const void *info); / / release function
    CFStringRef (*copyDescription)(const void *info); // Returns a function that describes a string
    Boolean (*equal)(const void *info1, const void *info2); // A function to determine whether the source object is equal
    CFHashCode (*hash)(const void *info); // hash function
} CFRunLoopSourceContext/1;
Copy the code

Version, info, retain, release, a function to describe strings, a function to determine whether a source object is equal, a hash function, The basic contents of CFRunLoopSourceContext and CFRunLoopSourceContext1 are identical. The main differences between CFRunLoopSourceContext and CFRunLoopSourceContext1 are as follows. They represent the different functions of source0 and source1.

typedef struct {.void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode); // The callback that is fired when source0 is added to the run loop (as seen in the CFRunLoopAddSource function)
    void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode); // The callback that is triggered when source0 is removed from the run loop
    void (*perform)(void *info); // The block of tasks to be performed by source0. The __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ function is called when the source0 event is fired
} CFRunLoopSourceContext;
Copy the code
typedef struct {.#if(TARGET_OS_MAC && ! (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t (*getPort)(void *info); // getPort function pointer, used to get the concrete mach_port_t object from the function to wake up the run loop when source1 is added to the run loop.
    void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info); // Perform refers to the callback to be executed by source1 when run loop is invoked
#else
    // Other platforms
    void * (*getPort)(void *info);
    void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
Copy the code

You can see that in Source0 only some callbacks are executed in this run loop, whereas in Source1 there are Mach ports available to actively wake up the Run loop.

  1. CFRunLoopTimerRef is a time-based trigger that is toll-free bridged and can be mixed with NSTimer. It contains a length of time and a callback (function pointer). When it is added to the Run loop, the Run loop registers the corresponding time point, and when that time point is reached, the Run loop is woken up to execute that callback.
  2. CFRunLoopObserverRef is the Observer, and each Observer contains a callback (function pointer) that the Observer receives when the state of the Run loop changes.

Observe the status change of run loop or the switch of Run Loop mode

The following is part of the sample code to observe the main thread run loop state change and the current run loop mode switch (kCFRunLoopDefaultMode and UITrackingRunLoopMode switch). The code for adding a scrollable tableView to the ViewController is self-explanatory:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    // Only the void * info field is passed an initial value, which will be used as the info parameter to the mainRunLoopActivitie callback below
    CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL.NULL.NULL};
    
    The kCFRunLoopAllActivities parameter observes all state changes in the Run loop
    // YES: observe the state change of run lop repeatedly
    // mainRunLoopActivitie corresponds to the _callout field of the __CFRunLoopObserver structure, which is a callback function to run loop status changes
    // 0 is the _order field corresponding to __CFRunLoopObserver. When multiple Run loop observers are added to a run loop, _order is used as the call order. The smaller the _order value is, the higher the priority is.
    // Context is used to pass info.
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, mainRunLoopActivitie, &context);
    if (observer) {
        // Add an observer to the kCFRunLoopCommonModes of main Run loop
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        CFRelease(observer); }}int count = 0; // Define global variables to calculate statistics for state transitions in a mode
void mainRunLoopActivitie(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    // Observer: CFRunLoopObserverRef instance added to main Run loop in viewDidLoad above
    // Activity: State change this time: KCFRunLoopEntry, kCFRunLoopBeforeTimers, kCFRunLoopBeforeSources, kCFRunLoopBeforeWaiting, kCFRunLoopAfterWaiting, and kCFRunLoop Exit, (kCFRunLoopAllActivities)
    // info: info member variable of CFRunLoopObserverContext instance in viewDidLoad above (__bridge void *)(self)
    
    ++count;
    switch (activity) {
        case kCFRunLoopEntry:
            count = 0;
            NSLog(@"🤫 - %d kCFRunLoopEntry about to enter: %@", count, CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"🤫 - %d kCFRunLoopBeforeTimers About to process timers", count);
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"🤫 - %d kCFRunLoopBeforeSources about to handle sources", count);
            break;
        case kCFRunLoopBeforeWaiting:
            count = 0; // Each time the Run loop is about to enter sleep, count is set to 0 to indicate the end of the run loop
            NSLog(@"🤫 - %d kCFRunLoopBeforeWaiting about to go to sleep", count);
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"🤫 - %d kCFRunLoopAfterWaiting is about to wake up from hibernation", count);
            break;
        case kCFRunLoopExit:
            count = 0;
            NSLog(@"🤫 - %d kCFRunLoopExit about to exit: %@", count, CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
            break;
        case kCFRunLoopAllActivities:
            NSLog(@"🤫 kCFRunLoopAllActivities");
            break; }} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%s",__func__);
}

// Click the blank area of the screen from the static state of the App to see the following print:
 🤫 - 1KCFRunLoopAfterWaiting is about to wake up from hibernation 🤫 -2KCFRunLoopBeforeTimers About to process timers 🤫 -3KCFRunLoopBeforeSources about to handle sources 🤫 -0KCFRunLoopBeforeWaiting is going to sleep// run loop 1 discount ️
 
 🤫 - 1KCFRunLoopAfterWaiting is about to wake up from hibernation 🤫 -2KCFRunLoopBeforeTimers About to process timers 🤫 -3KCFRunLoopBeforeSources will handle sources -[ViewController touchesBegan:withEvent:]// The touche event starts from the static state of the App clicking the screen, and the loop is fixed twice before entering the Touche event
 🤫 - 4KCFRunLoopBeforeTimers About to process timers 🤫 -5KCFRunLoopBeforeSources about to handle sources 🤫 -0KCFRunLoopBeforeWaiting is going to sleep// run loop 2 discount ️
 
 🤫 - 1KCFRunLoopAfterWaiting is about to wake up from hibernation 🤫 -2KCFRunLoopBeforeTimers About to process timers 🤫 -3KCFRunLoopBeforeSources about to handle sources 🤫 -4KCFRunLoopBeforeTimers About to process timers 🤫 -5KCFRunLoopBeforeSources about to handle sources 🤫 -0KCFRunLoopBeforeWaiting is going to sleep// run loop 3 discount ️
 
 🤫 - 1KCFRunLoopAfterWaiting is about to wake up from hibernation 🤫 -2KCFRunLoopBeforeTimers About to process timers 🤫 -3KCFRunLoopBeforeSources about to handle sources 🤫 -4KCFRunLoopBeforeTimers About to process timers 🤫 -5KCFRunLoopBeforeSources about to handle sources 🤫 -0KCFRunLoopBeforeWaiting is going to sleep// run loop 4 discount ️
 // The App enters the static state after two fixed cycles.
 
 🤫 - 1KCFRunLoopAfterWaiting is about to wake up from hibernation 🤫 -2KCFRunLoopBeforeTimers About to process timers 🤫 -3KCFRunLoopBeforeSources about to handle sources 🤫 -0KCFRunLoopBeforeWaiting is going to sleep// run loop 5 discount ️
 
 🤫 - 1KCFRunLoopAfterWaiting is about to wake up from hibernation 🤫 -2KCFRunLoopBeforeTimers About to process timers 🤫 -3KCFRunLoopBeforeSources about to handle sources 🤫 -0KCFRunLoopBeforeWaiting is going to sleep// run loop 6 discount ️
 // Then run loop goes to long sleep
Copy the code

🤫 -0 kCFRunLoopExit is about to exit when we scroll the tableView from rest: KCFRunLoopDefaultMode and 🤫 -0 kCFRunLoopEntry: UITrackingRunLoopMode, 🤫 -0 kCFRunLoopExit: UITrackingRunLoopMode and 🤫 -0 kCFRunLoopEntry to enter: kCFRunLoopDefaultMode. Exit from Default to enter UITracking, and then slide to stop to exit UITracking and enter Default.

State toggle if yes, from the application rest state, click on the blank area of the screen, it is fixed AfterWaiting -> BeforeTimers -> BeforeSources and then go to sleep BeforeWaiting, AfterWaiting -> BeforeTimers -> BeforeSources before executing touchesBegan:withEvent: The first callback does not execute the touch event, and the second callback will execute the touch event. The second callback will execute the touch event, and the second callback will execute the touch event. Then there are two fixed cycles before the program goes into long sleep.

The mainRunLoopActivitie function is called when the state of the mainRun loop changes, where we can do whatever we want with the activity. The CFRunLoopObserverCreate and CFRunLoopAddObserver functions have been analyzed in the preceding process, you can see the iOS from the source parsing RunLoop (4) : The process of creating and adding Source, Timer, and Observer to mode

Thread to keep alive

Why do threads need to be kept alive? In fact, the bottleneck of performance lies in the application and release of space. When we create a thread during the execution of a task, the thread will be released when the task is finished. If the task frequency is relatively high, then an active thread to execute our task will save the time and performance of application and release of space. You’ve already talked about the run loop requires source0 / source1 / timer/block (__CFRunLoopModeIsEmpty function in detail before) can not quit, always can’t let him perform directly while (1). This method is obviously not correct, according to the source code, when there is a monitor port (i.e., source1), it does not exit and does not affect performance. So the thread initialization can use [[NSRunLoop currentRunLoop] addPort: [NSPort port] forMode: NSRunLoopCommonModes]; To ensure that the run loop is alive after it starts. (A CFRunLoopRunSpecific call to __CFRunLoopModeIsEmpty that returns true will return kCFRunLoopRunFinished.)

If you want to keep the child thread permanently active, you call the run function of its run loop instance within the child thread. If you want to freely control when the thread’s run loop ends, you use a variable to control the do while loop. Calls runMode: beforeDate: the function that calls CFRunLoopStop(CFRunLoopGetCurrent()) in the child thread when it needs to stop the child thread’s run loop instance within the loop; And end the do while Loop, detailed content can refer to the previous iOS source parsing RunLoop (a) : RunLoop basic concept understanding and NSRunLoop documentation

Controls the automatic release of pool push and POP

So when does the auto release pool pop and release all the objects in the pool? There are two cases:

  • One is that we do it manually@autoreleasepool {... }The form of auto-release pool added using clang-rewrite-objc after conversion to C++ is actually
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(a); } ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); }void * atautoreleasepoolobj;
};

/* @autoreleasepool */ 
{ 
    // Directly build an __AtAutoreleasePool instance,
    // The constructor calls the push function of AutoreleasePoolPage to build an automatic release pool.
    __AtAutoreleasePool __autoreleasepool;
    // ...
}
Copy the code

As you can see, __autoreleasepool is wrapped in a pair of {}, and the pool is automatically released when the right curly bracket is out. This can also be interpreted as the following code:

void *pool = objc_autoreleasePoolPush(a);// the code in {}
objc_autoreleasePoolPop(pool);
Copy the code

In the original main function, break a breakpoint and enable Debug Workflow’s Always Show Disassembly to see the assembly code:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        // appDelegateClassName = NSStringFromClass([AppDelegate class]);
    } // ⬅️ make a breakpoint here
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code

Since the auto-release pool didn’t put anything in the code above, the Pop was followed after the Push.

.0x101319b78 <+32>:  bl     0x101319eb8               ; symbol stub for: objc_autoreleasePoolPush
0x101319b7c <+36>:  bl     0x101319eac               ; symbol stub for: objc_autoreleasePoolPop
...
Copy the code
  • One is an automatic release pool created by run Loop. From ibireme:

The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler (). The first Observer monitors an event called Entry(about to enter Loop), which creates an automatic release pool within its callback by calling _objc_autoreleasePoolPush(). Its order is -2147483647, the highest priority, ensuring that the release pool is created before all other callbacks. The second Observer monitors two events: calling _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() while waiting (ready to sleep) torelease the old pool and create a new one; _objc_autoreleasePoolPop() is called upon Exit(about to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, the lowest priority, ensuring that its release pool occurs after all other callbacks. The code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by AutoreleasePool created by RunLoop, so there is no memory leak and the developer does not have to show that the Pool was created. AutoreleasePool is a RunLoop tool that can be used to automatically release a pool

Here we try to verify the above conclusions, in application: didFinishLaunchingWithOptions: Po [NSRunLoop mainRunLoop] is now running in kCFRunLoopDefaultMode. In other modes, there are six observers, including the highest and lowest priority cfrunloopobservers:

    observers = (
    "<CFRunLoopObserver 0x282638640 [0x20e729430]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = <redacted> (0x20af662ec), context = <CFArray 0x28197def0 [0x20e729430]>{type = mutable-small, count = 1, values = (\n\t0 : <0x1006ec048>\n)}}"."<CFRunLoopObserver 0x2826385a0 [0x20e729430]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = <redacted> (0x20af662ec), context = <CFArray 0x28197def0 [0x20e729430]>{type = mutable-small, count = 1, values = (\n\t0 : <0x1006ec048>\n)}}"
)
Copy the code

The CFRunLoopObserver whose order is -2147483647 has the highest priority and will be called back before all other CFRunloopObservers, and then its activities will be 0x1, KCFRunLoopEntry = (1UL << 0) Add a breakpoint _wrapRunLoopWithAutoreleasePoolHandler symbols, add a breakpoint objc_autoreleasePoolPush symbols, to run the program, and in the console bt stack print function, and can actually see the following function calls:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
  * frame #0: 0x00000001dd971864 libobjc.A.dylib`objc_autoreleasePoolPush // push builds the automatic release pool
    frame #1: 0x00000001de78d61c CoreFoundation`_CFAutoreleasePoolPush + 16
    frame #2: 0x000000020af66324 UIKitCore`_wrapRunLoopWithAutoreleasePoolHandler + 56
    frame #3: 0x00000001de7104fc CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32 // Execute the run loop Observer callback,
    frame #4: 0x00000001de70b224 CoreFoundation`__CFRunLoopDoObservers + 412
    frame #5: 0x00000001de70af9c CoreFoundation`CFRunLoopRunSpecific + 412
    frame #6: 0x00000001e090c79c GraphicsServices`GSEventRunModal + 104
    frame #7: 0x000000020af6cc38 UIKitCore`UIApplicationMain + 212
    frame #8: 0x0000000100a75b90 Simple_iOS`main(argc=1, argv=0x000000016f38f8e8) at main.m:77:12
    frame #9: 0x00000001de1ce8e0 libdyld.dylib`start + 4
(lldb) 
Copy the code

The main thread does see __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ executing the CFRunLoopObserver callback _wrapRunLoopWithAutoreleasePoolHandler function then calls the objc_autoreleasePoolPush to create large pools of automatic release.

The CFRunLoopObserver whose order is 2147483647 has the lowest priority and will be called back after all other CFRunloopObservers, and then its activities will be 0xA0 (0b10100000), Corresponding to kCFRunLoopBeforeWaiting = (1UL << 5) and kCFRunLoopExit = (1UL << 7), that is, observe two state changes of run loop about to go to sleep and run loop about to exit. Callout = (0x20AF662EC), add an objc_autoreleasePoolPop breakpoint, add some test code, add a main Run loop observer, Add a timer to the main run loop for the main thread. After the program starts, we can see the console print the following loop:

🎯... KCFRunLoopAfterWaiting ⏰⏰⏰ timer callback... 🎯... KCFRunLoopBeforeTimers 🎯... KCFRunLoopBeforeSources 🎯... KCFRunLoopBeforeWaiting 🎯... KCFRunLoopAfterWaiting ⏰⏰⏰ timer callback...Copy the code

Main thread into a kind of “dormant – be awakened the timer callback execution – hibernation cycle, at the moment we open the _wrapRunLoopWithAutoreleasePoolHandler breakpoint found into the program, Then open the objc_autoreleasePoolPop breakpoint and click Continue Program Execution. The breakpoint will enter the objc_autoreleasePoolPop breakpoint and print the function call stack on the console:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001dd9718f8 libobjc.A.dylib`objc_autoreleasePoolPop
    frame #1: 0x00000001de78cba0 CoreFoundation`_CFAutoreleasePoolPop + 28
    frame #2: 0x000000020af66360 UIKitCore`_wrapRunLoopWithAutoreleasePoolHandler + 116
    frame #3: 0x00000001de7104fc CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
    frame #4: 0x00000001de70b224 CoreFoundation`__CFRunLoopDoObservers + 412
    frame #5: 0x00000001de70b7a0 CoreFoundation`__CFRunLoopRun + 1228
    frame #6: 0x00000001de70afb4 CoreFoundation`CFRunLoopRunSpecific + 436
    frame #7: 0x00000001e090c79c GraphicsServices`GSEventRunModal + 104
    frame #8: 0x000000020af6cc38 UIKitCore`UIApplicationMain + 212
    frame #9: 0x0000000100bc9b2c Simple_iOS`main(argc=1, argv=0x000000016f23b8e8) at main.m:76:12
    frame #10: 0x00000001de1ce8e0 libdyld.dylib`start + 4
(lldb)
Copy the code

Do see _wrapRunLoopWithAutoreleasePoolHandler objc_autoreleasePoolPop calls.

This is integrated: Entry–>push –> BeforeWaiting– >pop–>push –>Exit–> POP, in this order, ensuring that every push has a pop.

From the above work in the Run Loop Observer, we know that for each loop, there is a pop and push, so we can get:

  1. If you add autoreleasePool manually, autoreleasePool objects in the autoreleasePool scope are released the moment they exit the pool scope.
  2. If the autoreleasePool is automatically added by the Run loop, at the end of each run loop, the autoreleasePool performs a POP operation torelease all the autoreleasePool objects in the loop. Push the new automatic release pool when the Run loop is started to ensure that the objects in each run loop are released.

NSTimer implementation process

NSTimer. H provides a set of NSTimer invocation methods. The NSInvocation, SEL, and block parameters of the constructor represent the invocation of the NSTimer object. The block callback form is new in iOS 10.0 to help us avoid cyclic references to NSTimer objects and their target parameters, timerWithTimeInterval… And initWithFireDate NSTimer object is returned, we need to manually add to the current thread’s run loop, scheduledTimerWithTimeInterval… The built NSTimer object is added by default to the NSDefaultRunLoopMode of the current thread’s Run loop.

Block callbacks are in the form of an API_AVAILABLE(MacOSx (10.12), ios(10.0), Watchos (3.0), TVOs (10.0)); .

NSTimer creates the function

The NSTimer object returned by the following five methods requires a manual call to NSRunLoop -(void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode; Adds the function to the specified mode of the specified run loop.

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
Copy the code

NSTimer objects returned by the following three methods are automatically added to the default mode of the current thread’s Run loop.

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
Copy the code

If you use scheduledTimerWithTimeInterval… It should be noted that when the mode of run loop switches to UITrackingRunLoopMode, the timer will stop calling back, and when the run loop is stopped by sliding and switches back to kCFRunLoopDefaultMode, the timer will start calling back normally. When manually added to the Run loop, try to add it to NSRunLoopCommonModes to ensure that the mode switch of the Run loop does not affect the timer callback (the timer object will be added to multiple Common tags of the Run loop at the same time In _timers of mode).

Also note that NSTimer added to the run loop mode is held by the mode because it is added to _timers of the Run loop mode. If the mode name is nsRunLoopCommonMode, it will also be added to the _commonModeItems of the Run loop. Therefore, when the NSTimer object is no longer needed for timing, the invalidate function must be called to remove it from the _timers and _commonModeItems collections. This can be verified by printing the reference count for each timer under ARC:

// The timer is added to the NSDefaultRunLoopMode of the run loop by default. The reference count should be 2, but the print is 3. The following manual additions are normal +1
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { }]; / / 3

// The starting reference count is 1
NSTimer *timer2 = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { }]; / / 1
// Add timer2 to NSDefaultRunLoopMode of run loop reference count +1
// _timers held by timer2 and NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:NSDefaultRunLoopMode]; / / 2

NSTimer *timer3 = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { }]; / / 1
[[NSRunLoop currentRunLoop] addTimer:timer3 forMode:NSDefaultRunLoopMode]; / / 2

// Add timer3 to NSRunLoopCommonModes of run loop with reference count +3
// Held by timer3, _timers of UITrackingRunLoopMode, _timers of NSDefaultRunLoopMode, and _commonModeItems of run loop
[[NSRunLoop currentRunLoop] addTimer:timer3 forMode:NSRunLoopCommonModes]; / / 4

// timer3 calls the invalidate function and the reference count changes back to 1
-3 after being removed from the two _timers and _commonModeItems
[timer3 invalidate]; / / 1
Copy the code

NSTimer is created to hold the target passed in:

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
Copy the code

When you build or initialize an NSTimer object using the above three functions, the NSTimer object holds the target that was passed in because the NSTimer object calls back to target’s aSelector function, If the target holds the NSTimer object at this time, it will create a circular reference and cause a memory leak. This problem is usually encountered when adding the NSTimer property to a ViewController. There are usually two ways to solve this problem: one is to separate the target into an object (in this object weakly reference NSTimer and the object itself as the target of NSTimer), and the controller indirectly uses NSTimer through this object; Another way to think about it is to shift the target, but you can add the NSTimer extension directly, making the NSTimer object the target, and encapsulating the action selector into a block, as shown in the following code. (Class objects are globally unique and do not need and cannot be freed.

#import "NSTimer+Block.h"

@implementation NSTimer (Block)

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(void (^)(void))block {
    // Target passes in self.class, which is the NSTimer object, and the timer's callback is the NSTimer object's runBlock: function, runBlock is a class method,
    // Place the block of the callback in userInfo, and then read the userInfo from the NSTimer object in runBlock:.
    return [self initWithFireDate:date interval:seconds target:self.class selector:@selector(runBlock:) userInfo:block repeats:repeats];
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(void(^) (void))block {
    // self is an NSTimer class object
    return [self scheduledTimerWithTimeInterval:seconds target:self selector:@selector(runBlock:) userInfo:block repeats:repeats];
}

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(void(^) (void))block {
    // self is an NSTimer class object
    return [self timerWithTimeInterval:seconds target:self selector:@selector(runBlock:) userInfo:block repeats:repeats];
}

#pragma mark - Private methods

+ (void)runBlock:(NSTimer *)timer {
    // Read the block execution from the input timer object
    if ([timer.userInfo isKindOfClass:NSClassFromString(@"NSBlock")]) {
        void (^block)(void) = timer.userInfo;
        block(a); } } @endCopy the code

After iOS 10.0, Apple also provides the NSTimer constructor in block form, which we can use directly. (Are there any pre-ios 10.0 users?)

Timers cannot be paused. The invalidate function removes the use of the counter, so the invalidate method invalidates both repeated timers and one-time timers. It’s just a one-time timer that automatically calls the invalidate method when it’s done. So the only way to pause and resume a timer is to invalidate the old timer and then create a new one, and we must call the invalidate method when the timer is no longer needed.

NSTimer executes the process

CFRunLoopTimerRef and nS Index mer can be toll-free bridged. When the timer is added to the Run loop, the Run loop will register the corresponding trigger time. When the time is up, the Run loop will wake up if it is in sleep and execute the corresponding callback function of the timer. Below we follow the CFRunLoopTimerRef source code to a complete analysis of the timer process.

CFRunLoopTimerRef create

CFRunLoopTimerRef () : CFRunLoopTimerRef () : CFRunLoopTimerRef ()

CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context);
Copy the code

Allocator is an allocator that allocates memory for new objects under CF. The allocator can be NULL or kCFAllocatorDefault.

FireDate is the point at which the timer first triggers a callback, followed by successive callbacks along interval intervals.

Interval is the interval between consecutive callbacks to the timer. If it is 0 or negative, the timer will fire once and then automatically expire.

Order Priority index indicating the order of callback execution for different timers in _timers of CFRunLoopModeRef. This parameter is ignored for the time being and 0 is passed.

The callback function that is invoked when the callout timer is triggered.

Context Holds the structure of the timer’s context information. This function copies the information out of the structure, so the memory pointed to by the context does not need to continue to exist after the function call. NULL if the callback function does not need the context’s information pointer to track state. The void * info field is the argument to the callout function.

The most important thing in the CFRunLoopTimerCreate function is the setting of the trigger time:

./ / # define TIMER_DATE_LIMIT 4039289856.0
// If fireDate is too large, set it to TIMER_DATE_LIMIT
if (TIMER_DATE_LIMIT < fireDate) fireDate = TIMER_DATE_LIMIT;

// Next trigger time
memory->_nextFireDate = fireDate;
memory->_fireTSR = 0ULL;

// Get the current time
uint64_t now2 = mach_absolute_time(a); CFAbsoluteTime now1 =CFAbsoluteTimeGetCurrent(a);if (fireDate < now1) {
    // Set _fireTSR to current if the first trigger time has passed
    memory->_fireTSR = now2;
} else if (TIMER_INTERVAL_LIMIT < fireDate - now1) {
    // If the gap between the first time triggered and the current time exceeds TIMER_INTERVAL_LIMIT, set _fireTSR to TIMER_INTERVAL_LIMIT
    memory->_fireTSR = now2 + __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
} else {
    If the first trigger time has not arrived, set the trigger time to the difference between the current time and the first trigger timememory->_fireTSR = now2 + __CFTimeIntervalToTSR(fireDate - now1); }...Copy the code

This part of the code ensures that the timer first fires correctly. Add CFRunLoopModeRef to the specified Run loop mode of the specified run loop.

CFRunLoopAddTimer

The CFRunLoopAddTimer function inserts CFRunLoopTimerRef RLT into the _timer set in CFStringRef modeName mode of CFRunLoopRef RL. If modeName is kCFRunLoopCommonModes, insert RLT into the _commonModeItems of RL. And then call the RLT __CFRunLoopAddItemToCommonModes function is added to all marked as common mode of _timer, it will also add modeName to RLT _rlModes, Record RLT can be executed in that run loop mode.

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName);
Copy the code

After adding the above, the __CFRepositionTimerInMode function is called, and then __CFArmNextTimerInMode is called, Then call mk_timer_arm to register _timerPort and a time point of CFRunLoopModeRef in the system, and wait for mach_MSG to send a message to wake up the run loop in sleep to execute the timer of arrival time.

__CFArmNextTimerInMode

Multiple timers in the same run loop mode share the same _timerPort, which is a circular process: Register timer(mk_timer_arm) – Receive timer(mach_msg) – Calculate the next handle time nearest the current time based on multiple timers – Register timer(mk_timer_arm).

The call stack for adding a timer with CFRunLoopAddTimer looks like this:

CFRunLoopAddTimer
__CFRepositionTimerInMode
    __CFArmNextTimerInMode
        mk_timer_arm
Copy the code

Then mach_msg receives a timer event and the call stack looks like this:

__CFRunLoopRun
__CFRunLoopDoTimers
    __CFRunLoopDoTimer
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
__CFArmNextTimerInMode
    mk_timer_arm 
Copy the code

The __CFArmNextTimerInMode function is called each time the timer registers the next callback to the timer. After the run loop is awakened through the _timerPort of the current Run Loop mode, In this run loop, the __CFRunLoopDoTimer function is called in the __CFRunLoopDoTimers function, and the _callout function of the timer that has reached the trigger time is executed. __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info); Is the _callout function that executes the timer.

NSTimer problem

From the NSTimer execution process above, you can see that the timer trigger callback is completely dependent on the run loop (mk_timer is used to wake up the Run loop on both macOS and iOS). Before using NSTimer, you must register with the Run loop. However, run Loop does not call the timer at precisely the right time to save resources. If a task takes a long time to execute (for example, the source0 event in the run Loop takes too long or the timer’s own callback takes too long), Will cause the timer callback to be delayed at the next normal point in time, or too long to be ignored (the current state of execution is determined before the timer callback executes! __CFRunLoopTimerIsFiring(RLT), if the callback execution time of the timer itself is too long, the next callback is ignored. NSTimer provides a tolerance property to set tolerance, that is, the current time point has passed the trigger point of the timer, but if the elapsed time is less than tolerance, then the timer callback can still be executed normally, but with an untimely delay. The default value of tolerance is 0, and the maximum value is half of the timer interval _interval. The value of tolerance can be set according to its own situation. Or if the main thread is really not optimized, put the timer in the child thread)).

(NSTimer is not a real-time mechanism. In the case of main Run loop, NSTimer is responsible for all main thread events, such as UI operations, so that the current run loop lasts longer than the timer interval, and the timer’s next callback is delayed. This leads to the unpunctuality of the timer. The timer has a property called tolerance, which indicates the maximum error allowed when the time point is up. A delay too long will cause the timer callback to be ignored.

You can see a description of timing accuracy in Apple’s Timer documentation: Timer Programming Topics

Timing Accuracy A timer is not A real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the Timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a Timer’s firing time occurs while the run loop is in a mode that is not monitoring the timer or during a long callout, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing A repeating timer reschedules itself based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.

Timers are not real-time mechanics; It fires only if one of the run Loop modes to which the timer has been added is running and can check whether the timer’s trigger time has passed. Because a typical Run loop manages a variety of input sources, the effective resolution of the timer interval is limited to the order of 50-100 milliseconds. If the timer’s trigger time occurs when the run loop is in unmonitored timer mode or during an extended invocation, the timer will not start until the next time the loop checks the timer. As a result, the actual time the timer may trigger may be a considerable time after the scheduled trigger time.

Repeat timers reschedule themselves based on the scheduled trigger time rather than the actual trigger time. For example, if you plan to trigger the timer at a specific time and then every 5 seconds, the scheduled trigger time will always fall on the original 5-second interval, even if the actual trigger time is delayed. If the trigger time is delayed so far that one or more of the planned trigger times are exceeded, the timer fires only once during that time period; The timer is rescheduled for the next scheduled trigger time in the future.

The following code applies for a thread and starts its Run loop to observe the time of the timer callback.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
    [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    // sleep(1);
        NSLog(@"⏰⏰⏰ timer callback...");
    }];
    
    // Execute caculate in thread after 2 seconds
    [self performSelector:@selector(caculate) withObject:nil afterDelay:2];
    
    [[NSRunLoop currentRunLoop] run];
    }];
    [thread start];
}

- (void)caculate {
    NSLog(@"👘 👘 % @", [NSThread currentThread]);
    sleep(2);
}
Copy the code

The running code will see that the first two seconds of the timer execute normally based on the printing time. Then caculate’s execution will cause the timer to be delayed by two seconds. After two seconds, the timer will continue to execute normally once per second. If you turn on the sleep(1) annotation in the timer’s callback, you see that the timer executes every two seconds.

PerformSelector family of functions

When calling NSObject performSelecter: afterDelay: after its internal will actually create a Timer and add to the current thread run in the loop. So if the current thread does not have a Run loop, this method will fail.

NSObject + NSDelayedPerforming

The following functions are declared under the NSDelayedPerforming category of NSObject.

@interface NSObject (NSDelayedPerforming)
/ / specified NSRunLoopMode
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
// The default is NSDefaultRunLoopMode
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end
Copy the code

PerformSelector: withObject: afterDelay: inModes: after the delay using the specified mode on the current thread calls the receiver (NSObject object and its subclasses) method. ASelector: aSelector that identifies the method to call. The method should have no obvious return value (void), and should take a single argument of type ID, or no arguments.

AnArgument: The argument passed to the method when called. If the method does not accept arguments, nil is passed.

Delay: Minimum time before sending a message. Specifying a delay of 0 does not necessarily cause the selector to execute immediately. The selector is still queued in the thread’s Run loop and executed as soon as possible.

Modes: An array of strings that identify the modes associated with the timer that executes the selector. This array must contain at least one string. If you specify nil or an empty array for this argument, this method returns without executing the specified selector.

This method sets up a timer to execute aSelector messages on the run loop of the current thread. The timer configuration runs in the modes specified by the modes parameter. When the timer fires, the thread attempts to fetch the message from the Run loop and execute the selector. If run loop is running and in one of the specified modes, it succeeds; Otherwise, the timer will wait until the Run loop is in one of these modes.

If you want to run the loop in the default mode of make out a message, please use the performSelector: withObject: afterDelay: inModes: method. If you are unsure whether the current thread is given priority to the thread, you can use performSelectorOnMainThread: withObject: waitUntilDone: Or performSelectorOnMainThread: withObject: waitUntilDone: modes: method to ensure that the selector on the main thread. To cancel the line message, please use the cancelPreviousPerformRequestsWithTarget: or cancelPreviousPerformRequestsWithTarget: selector: object: method.

This method registers with the Runloop of its current context and depends on runloops that run periodically for proper execution. A common context is when scheduling queue calls, you might call this method and end up registering with a Runloop that doesn’t automatically run periodically. If such functionality is required when running on a dispatch queue, use dispatch_after and related methods to obtain the desired behavior. (Similarly, NSTimer is not always available, you can use dispatch_source instead)

NSRunLoop + NSOrderedPerform

@interface NSRunLoop (NSOrderedPerform)
- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;
@end
Copy the code

PerformSelector: target: argument: order: modes: in the receiver (NSRunLoop instance objects) sending a message.

ASelector: aSelector that identifies the method to call. This method should have no obvious return value and should take a single parameter of type ID.

Target: The object that defines the selector in aSelector.

AnArgument: The argument passed to the method when called. If the method does not accept arguments, nil is passed.

Order: priority of the message. If multiple messages are planned, the message with the lower order value is sent before the message with the higher order value.

Modes: Array of input modes for which messages can be sent. You can specify custom Modes or use one of the Modes listed in Run Loop Modes.

This method sets up a timer to execute the aSelector message on target at the start of the next run Loop iteration. The timer is configured to run in the modes specified by the modes parameter. When the timer fires, the thread will attempt to unqueue the message from the Run loop and execute the selector. If the run loop is running and in one of the specified modes, it succeeds; Otherwise, the timer will wait until the Run loop is in one of these modes. It will add a timer under the run loop mode of the current run loop, which can be verified by the following code:

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"🧗 ‍ ♀ ️ 🧗 ‍ ♀ ️...");

        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"⏰⏰⏰ timer callback...");
        }];

        [self performSelector:@selector(caculate) withObject:nil afterDelay:2]; // ⬅️ breakpoint 1
        
        NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // ⬅️ breakpoint 2
        [runloop run];
    }];
    [thread start];
Copy the code

When both breakpoints are executed, print from the console via Po [NSRunLoop currentRunLoop] :

// at breakpoint 1: Po [NSRunLoop currentRunLoop]. timers = <CFArray0x28314e9a0 [0x20e729430]>{type = mutable-small, count = 1, values = (
    0 : <CFRunLoopTimer 0x28204df80 [0x20e729430]>{valid = Yes, firing = No, interval = 1, tolerance = 0, next fire date = 631096717 (14.273319 @ 16571855540445), callout = (NSTimer) [_NSTimerBlockTarget fire:] (0x1df20764c / 0x1df163018) (/System/Library/Frameworks/Foundation.framework/Foundation), context = <CFRunLoopTimer context 0x28154b900>})...// at breakpoint 2: Po [NSRunLoop currentRunLoop]. timers = <CFArray0x28314e9a0 [0x20e729430]>{type = mutable-small, count = 2, values = (
    0 : <CFRunLoopTimer 0x28204df80 [0x20e729430]>{valid = Yes, firing = No, interval = 1, tolerance = 0, next fire date = 631096717 (32.979197 @ 16571855540445), callout = (NSTimer) [_NSTimerBlockTarget fire:] (0x1df20764c / 0x1df163018) (/System/Library/Frameworks/Foundation.framework/Foundation), context = <CFRunLoopTimer context 0x28154b900>}
    1 : <CFRunLoopTimer 0x28204db00 [0x20e729430]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 631096747 (2.84795797 @ 16572578697099), callout = (Delayed Perform) ViewController caculate (0x1df1f4094 / 0x10093ab88) (/var/containers/Bundle/Application/C2E33DEA-1FB0- 48A0-AEDD2 -D13AF564389/Simple_iOS.app/Simple_iOS), context = <CFRunLoopTimer context 0x28003d4c0>})...Copy the code

Can see performSelector: withObject: afterDelay: added a timer.

This method returns before sending the aSelector message. The receiver holds the target and anArgument objects until the selector’s timer fires, and then releases them as part of the cleanup.

Use this method if you want to send multiple messages after processing the current event, and you want to ensure that the messages are sent in a specific order.

NSObject + NSThreadPerformAdditions

When the performSelector: onThread: when, in fact, it will create a timer is added to the corresponding thread, in the same way, if the corresponding thread didn’t run loop this method will fail.

@interface NSObject (NSThreadPerformAdditions)

/ / main thread
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; // equivalent to the first method with kCFRunLoopCommonModes

// Specify a thread
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait; // equivalent to the first method with kCFRunLoopCommonModes

// background threads
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;

@end
Copy the code

PerformSelectorOnMainThread: withObject: waitUntilDone: modes: using the specified mode on the main thread to invoke the method of the receiver.

emsp; ASelector: aSelector that identifies the method to call. The method should have no obvious return value and should take a single argument of type ID or no argument.

Arg: Arguments passed to aSelector when called. If the method does not accept arguments, nil is passed.

Wait: A Boolean value that specifies whether the current thread blocks after executing the specified selector on the sink on the main thread. Specifies YES to block the thread; Otherwise, specify NO for this method to return immediately. If the current thread is also the main thread and you pass YES, the message is executed immediately, otherwise it will be queued up for the next run through a Run loop.

Array: An array of strings identifying the mode that allows the specified selector to be executed. The array must contain at least one string. If you specify nil or an empty array for this argument, this method returns without executing the specified selector.

You can use this method to pass messages to the main thread of your application. The main thread contains the application’s main Run loop and is where the NSApplication object receives events. In this case, the message is the method of the current object that you want to execute on the thread.

This method queues messages in the run loop of the main thread using the run loop mode specified in the array argument. As part of its normal Run loop processing, the main thread queues the message (assuming it is running in one of the specified modes) and invokes the desired method. Assuming that each selector’s associated Run loop pattern is the same, multiple calls to the method from the same thread will cause the corresponding selectors to be queued and executed in the same order as the calls. If you specify a different mode for each selector, all selectors whose association mode does not match the current Run Loop mode will be skipped until the Runloop is subsequently executed in that mode.

You cannot unqueue messages using this method. If you want to cancel the current thread messages on the options, you must use the performSelector: withObject: afterDelay: or performSelector: withObject: afterDelay: inModes: method.

This method registers with the run loop of its current context and relies on the run loop to be run periodically for proper execution. A common context is when the Dispatch Queue is called, which might call this method and end up registering with a Run loop that doesn’t run automatically on a regular basis. If you need this functionality when running on a Dispatch queue, you should use dispatch_after and related methods to get the desired behavior.

PerformSelector method in the NSObject protocol

The performSelector methods of the NSObject protocol, implemented in the NSObject class, are independent of run loop. They are equivalent to calling recevier’s methods directly.

@protocol NSObject ... - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; . @endCopy the code

The implementation under NSObject, as you can see, is equivalent to calling the function directly.

- (id)performSelector:(SEL)sel {
    if(! sel) [self doesNotRecognizeSelector:sel];return ((id(*)(id, SEL))objc_msgSend)(self, sel);
}

- (id)performSelector:(SEL)sel withObject:(id)obj {
    if(! sel) [self doesNotRecognizeSelector:sel];return ((id(*)(id, SEL, id))objc_msgSend)(self, sel, obj);
}

- (id)performSelector:(SEL)sel withObject:(id)obj1 withObject:(id)obj2 {
    if(! sel) [self doesNotRecognizeSelector:sel];return ((id(*)(id, SEL, id, id))objc_msgSend)(self, sel, obj1, obj2);
}
Copy the code

This article mainly analyzes the automatic release pool and timer and run loop related content, timer related content is quite common in our daily development, need to learn the summary, next we continue to learn iOS system and run loop related content.

Refer to the link

Reference link :🔗

  • Runloop source
  • Run Loops official documentation
  • Complete guide to iOS RunLoop
  • IOS source code parsing: Runloop underlying data structure
  • IOS source code: Runloop operating principle
  • Understand RunLoop in depth
  • IOS Basics – Dive into RunLoop
  • A core runloop source code analysis
  • NSRunLoop
  • Get to the bottom of iOS – Understand RunLoop in depth
  • RunLoop summary and interview
  • Runloop- Actually develop the application scenario you want to use
  • RunLoop source code read
  • do {… } The role of while (0) in the macro definition
  • CFRunLoop (cF-1151.16)
  • Operating system big-endian mode and small-endian mode
  • CFBag
  • Mach_absolute_time use
  • IOS probe mach_absolute_time
  • IOS multithreading — RunLoop and GCD, AutoreleasePool You should know about iOS multithreading, NSThread, GCD, NSOperation, and RunLoop
  • Mach primitives: Everything is message-mediated
  • Operating system dual mode and interrupt mechanism and timer concept
  • RunLoop life cycle –(9)
  • From the NSRunLoop
  • When will runloop and Autorelase objects and Autorelease pools be released
  • Memory management: Autoreleasepool and Runloop
  • Objective-c AutoreleasePool association with Runloop