The RunLoop principle has been introduced before. If you are interested, you can read iOS: Decrypting RunLoop principle; This article introduces the practical application of RunLoop based on the application scenarios of RunLoop.

This article is quite long, the purpose of creation for their future review of knowledge, I hope this article can help you. If you find any mistakes, please leave a message to correct, thank you.

1. Event response

RunLoop is involved in event response on iOS devices. When it comes to event response on iOS devices, you probably have a general idea:

(1) The user triggers the event ->(2) the system transfers the event to the event queue of the corresponding APP ->(3) the APP takes out the event from the message queue header ->(4) Sends the event to Main Window for message distribution ->(5) Finds the appropriate Responder for processing. If not found, Then it goes back down the Responder chain to the APP layer and dismisses the event

Two questions are involved here. Steps (3) to (5) are handled in the process, while steps (1) to (2) involve the communication between device hardware, iOS operating system and target APP. What are the general steps of communication? The main RunLoop is in the mach_MSG_trap sleep state until our APP receives any event requests, so who wakes it up?

With LLDB, execute:

po [NSRunLoop mainRunLoop]

Copy the code

The following information is displayed:

<CFRunLoop 0x60000307c900 [0x1dc655ae0]>{wakeup port = 0x2203, stopped = false, ignoreWakeUps = false, 
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600000252b20 [0x1dc655ae0]>{type = mutable set, count = 2,
entries =>
    0 : <CFString 0x1d008c078 [0x1dc655ae0]>{contents = "UITrackingRunLoopMode"}
    2 : <CFString 0x1dc88d768 [0x1dc655ae0]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x60000023cb70 [0x1dc655ae0]>{type = mutable set, count = 9,
entries =>
    0 : <CFRunLoopSource 0x600003964300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18c03d28c)}}
    2 : <CFRunLoopObserver 0x600003d7c280 [0x1dc655ae0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1848adbd0), context = <CFRunLoopObserver context 0x60000277c620>}
    3 : <CFRunLoopObserver 0x600003d606e0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x184dc4888), context = <CFRunLoopObserver context 0x151004ae0>}
    4 : <CFRunLoopObserver 0x600003d60500 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x184dc488c), context = <CFRunLoopObserver context 0x151004ae0>}
    5 : <CFRunLoopSource 0x600003968000 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019568c0, callout = FBSSerialQueueRunLoopSourceHandler (0x18619d338)}}
    6 : <CFRunLoopSource 0x600003960300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000252e80, callout = __eventFetcherSourceCallback (0x184e38034)}}
    9 : <CFRunLoopObserver 0x600003d7c5a0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x188468b28), context = <CFRunLoopObserver context 0x0>}
    10 : <CFRunLoopSource 0x600003960180 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000377c0d0, callout = __eventQueueSourceCallback (0x184e37f7c)}}
    12 : <CFRunLoopSource 0x6000039606c0 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 24075, subsystem = 0x1d0773ff8, context = 0x600002879140}}
}
,
modes = <CFBasicHash 0x600000252c70 [0x1dc655ae0]>{type = mutable set, count = 4,
entries =>
    2 : <CFRunLoopMode 0x6000037681a0 [0x1dc655ae0]>{name = UITrackingRunLoopMode, port set = 0x4703, queue = 0x600002260400, source = 0x600002261280 (not fired), timer port = 0x4603, 
    sources0 = <CFBasicHash 0x60000023cc00 [0x1dc655ae0]>{type = mutable set, count = 4,
entries =>
    0 : <CFRunLoopSource 0x600003964300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18c03d28c)}}
    1 : <CFRunLoopSource 0x600003968000 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019568c0, callout = FBSSerialQueueRunLoopSourceHandler (0x18619d338)}}
    2 : <CFRunLoopSource 0x600003960180 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000377c0d0, callout = __eventQueueSourceCallback (0x184e37f7c)}}
    4 : <CFRunLoopSource 0x600003960300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000252e80, callout = __eventFetcherSourceCallback (0x184e38034)}}
}
,
    sources1 = <CFBasicHash 0x60000023c6c0 [0x1dc655ae0]>{type = mutable set, count = 1,
entries =>
    0 : <CFRunLoopSource 0x6000039606c0 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 24075, subsystem = 0x1d0773ff8, context = 0x600002879140}}
}
,
    observers = (
    "<CFRunLoopObserver 0x600003d7c280 [0x1dc655ae0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1848adbd0), context = <CFRunLoopObserver context 0x60000277c620>}",
    "<CFRunLoopObserver 0x600003d606e0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x184dc4888), context = <CFRunLoopObserver context 0x151004ae0>}",
    "<CFRunLoopObserver 0x600003d7c5a0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x188468b28), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x600003d60500 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x184dc488c), context = <CFRunLoopObserver context 0x151004ae0>}"
),
    timers = (null),
    currently 663695489 (6974909751689) / soft deadline in: 7.68614046e+11 sec (@ -1) / hard deadline in: 7.68614046e+11 sec (@ -1)
},

    3 : <CFRunLoopMode 0x600003768270 [0x1dc655ae0]>{name = GSEventReceiveRunLoopMode, port set = 0x3803, queue = 0x600002261300, source = 0x600002261400 (not fired), timer port = 0x3903, 
    sources0 = <CFBasicHash 0x60000023ce10 [0x1dc655ae0]>{type = mutable set, count = 1,
entries =>
    0 : <CFRunLoopSource 0x600003964300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18c03d28c)}}
}
,
    sources1 = <CFBasicHash 0x60000023ca20 [0x1dc655ae0]>{type = mutable set, count = 0,
entries =>
}
,
    observers = (null),
    timers = (null),
    currently 663695489 (6974909768440) / soft deadline in: 7.68614046e+11 sec (@ -1) / hard deadline in: 7.68614046e+11 sec (@ -1)
},

    4 : <CFRunLoopMode 0x6000037680d0 [0x1dc655ae0]>{name = kCFRunLoopDefaultMode, port set = 0x3103, queue = 0x600002260e00, source = 0x600002261080 (not fired), timer port = 0x5103, 
    sources0 = <CFBasicHash 0x60000023cc60 [0x1dc655ae0]>{type = mutable set, count = 4,
entries =>
    0 : <CFRunLoopSource 0x600003964300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18c03d28c)}}
    1 : <CFRunLoopSource 0x600003968000 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019568c0, callout = FBSSerialQueueRunLoopSourceHandler (0x18619d338)}}
    2 : <CFRunLoopSource 0x600003960180 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000377c0d0, callout = __eventQueueSourceCallback (0x184e37f7c)}}
    4 : <CFRunLoopSource 0x600003960300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000252e80, callout = __eventFetcherSourceCallback (0x184e38034)}}
}
,
    sources1 = <CFBasicHash 0x60000023cc30 [0x1dc655ae0]>{type = mutable set, count = 1,
entries =>
    0 : <CFRunLoopSource 0x6000039606c0 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 24075, subsystem = 0x1d0773ff8, context = 0x600002879140}}
}
,
    observers = (
    "<CFRunLoopObserver 0x600003d7c280 [0x1dc655ae0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1848adbd0), context = <CFRunLoopObserver context 0x60000277c620>}",
    "<CFRunLoopObserver 0x600003d606e0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x184dc4888), context = <CFRunLoopObserver context 0x151004ae0>}",
    "<CFRunLoopObserver 0x600003d7c5a0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x188468b28), context = <CFRunLoopObserver context 0x0>}",
    "<CFRunLoopObserver 0x600003d60500 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x184dc488c), context = <CFRunLoopObserver context 0x151004ae0>}"
),
    timers = <CFArray 0x60000287d920 [0x1dc655ae0]>{type = mutable-small, count = 0, values = ()},
    currently 663695489 (6974909768895) / soft deadline in: 7.68614046e+11 sec (@ -1) / hard deadline in: 7.68614046e+11 sec (@ -1)
},

    5 : <CFRunLoopMode 0x600003768b60 [0x1dc655ae0]>{name = kCFRunLoopCommonModes, port set = 0x5c07, queue = 0x600002262b80, source = 0x600002262c80 (not fired), timer port = 0x5d03, 
    sources0 = (null),
    sources1 = (null),
    observers = (null),
    timers = (null),
    currently 663695489 (6974909785136) / soft deadline in: 7.68614046e+11 sec (@ -1) / hard deadline in: 7.68614046e+11 sec (@ -1)
},

}
}

Copy the code

Here’s a mess, and we’ve got three key pieces of code in the middle!!

A, kCFRunLoopDefaultMode

<CFRunLoopMode 0x6000037680d0 [0x1dc655ae0]>{name = kCFRunLoopDefaultMode, port set = 0x3103, queue = 0x600002260e00, source = 0x600002261080 (not fired), timer port = 0x5103, sources0 = <CFBasicHash 0x60000023cc60 [0x1dc655ae0]>{type = mutable set, count = 4, entries => 0 : <CFRunLoopSource 0x600003964300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18c03d28c)}} 1 : <CFRunLoopSource 0x600003968000 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019568c0, callout = FBSSerialQueueRunLoopSourceHandler (0x18619d338)}} 2 : <CFRunLoopSource 0x600003960180 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000377c0d0, callout = __eventQueueSourceCallback (0x184e37f7c)}} 4 : <CFRunLoopSource 0x600003960300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000252e80, callout = __eventFetcherSourceCallback (0x184e38034)}} } , sources1 = <CFBasicHash 0x60000023cc30 [0x1dc655ae0]>{type = mutable set, count = 1, entries => 0 : <CFRunLoopSource 0x6000039606c0 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 24075, subsystem = 0x1d0773ff8, context = 0x600002879140}} } , observers = ( "<CFRunLoopObserver 0x600003d7c280 [0x1dc655ae0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1848adbd0), context = <CFRunLoopObserver context 0x60000277c620>}", "<CFRunLoopObserver 0x600003d606e0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x184dc4888), context = <CFRunLoopObserver context 0x151004ae0>}", "<CFRunLoopObserver 0x600003d7c5a0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x188468b28), context = <CFRunLoopObserver context 0x0>}", "<CFRunLoopObserver 0x600003d60500 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x184dc488c), context = <CFRunLoopObserver context 0x151004ae0>}" ), timers = <CFArray 0x60000287d920 [0x1dc655ae0]>{type = mutable-small, count = 0, values = ()}, / soft deadline in: 7.68614046e+11 SEC (@-1)/hard deadline in: + SEC (@-1)};Copy the code

B, UITrackingRunLoopMode

x<CFRunLoopMode 0x6000037681a0 [0x1dc655ae0]>{name = UITrackingRunLoopMode, port set = 0x4703, queue = 0x600002260400, source = 0x600002261280 (not fired), timer port = 0x4603, sources0 = <CFBasicHash 0x60000023cc00 [0x1dc655ae0]>{type = mutable set, count = 4, entries => 0 : <CFRunLoopSource 0x600003964300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x18c03d28c)}} 1 : <CFRunLoopSource 0x600003968000 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000019568c0, callout = FBSSerialQueueRunLoopSourceHandler (0x18619d338)}} 2 : <CFRunLoopSource 0x600003960180 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000377c0d0, callout = __eventQueueSourceCallback (0x184e37f7c)}} 4 : <CFRunLoopSource 0x600003960300 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600000252e80, callout = __eventFetcherSourceCallback (0x184e38034)}} } , sources1 = <CFBasicHash 0x60000023c6c0 [0x1dc655ae0]>{type = mutable set, count = 1, entries => 0 : <CFRunLoopSource 0x6000039606c0 [0x1dc655ae0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 24075, subsystem = 0x1d0773ff8, context = 0x600002879140}} } , observers = ( "<CFRunLoopObserver 0x600003d7c280 [0x1dc655ae0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1848adbd0), context = <CFRunLoopObserver context 0x60000277c620>}", "<CFRunLoopObserver 0x600003d606e0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x184dc4888), context = <CFRunLoopObserver context 0x151004ae0>}", "<CFRunLoopObserver 0x600003d7c5a0 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x188468b28), context = <CFRunLoopObserver context 0x0>}", "<CFRunLoopObserver 0x600003d60500 [0x1dc655ae0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x184dc488c), context = <CFRunLoopObserver context 0x151004ae0>}" ), timers = (null), / soft deadline in: 7.68614046e+11 SEC (@-1)/hard deadline in: + SEC (@-1)};Copy the code

Source0 event sources registered under kCFRunLoopDefaultMode, UITrackingRunLoopMode:

<CFRunLoopSource 0x600003960180 [0x1dc655ae0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60000377c0d0, callout = __eventQueueSourceCallback (0x184e37f7c)}}

Copy the code

His callback is __eventQueueSourceCallback, which, yes, is how the APP handles event queues.

Note, however, that __eventQueueSourceCallback has a source type of 0, which means that it does not wake up the main RunLoop itself, nor can the main thread itself wake up when it is asleep

So there must be a child thread that receives events, wakes up the main Thread, and passes the events to the main Thread!!

com.apple.uikit.eventfetch-thread

Copy the code

As you can see from the name of the thread, it is the thread created by UIKit to receive events (hereinafter referred to as the Event Fetch Thread). We print out com. Apple. Uikit. Eventfetch – the RunLoop kCFRunLoopDefaultMode thread.

<CFRunLoopMode 0x604000190740 [0x107f54bb0]>{name = kCFRunLoopDefaultMode, port set = 0x1d03, queue = 0x6040001569a0, source = 0x604000190810 (not fired), timer port = 0x2b03, sources0 = <CFBasicHash 0x60800005f8f0 [0x107f54bb0]>{type = mutable set, count = 1, entries => 0 : <CFRunLoopSource 0x608000167440 [0x107f54bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600000131940, callout = _UIEventFetcherTriggerHandOff (0x108d71599)}} } , sources1 = <CFBasicHash 0x608000240b10 [0x107f54bb0]>{type = mutable set, count = 3, entries => 0 : <CFRunLoopSource 0x604000167a40 [0x107f54bb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x608000156dc0 [0x107f54bb0]>{valid = Yes, port = 300f, source = 0x604000167a40, callout = __IOHIDEventSystemClientQueueCallback (0x10b17904d), context = <CFMachPort context 0x7f8b28d02ae0>}} 1 : <CFRunLoopSource 0x6040001678c0 [0x107f54bb0]>{signalled = No, valid = Yes, order = 1, context = <CFMachPort 0x608000157080 [0x107f54bb0]>{valid = Yes, port = 4e0f, source = 0x6040001678c0, callout = __IOMIGMachPortPortCallback (0x10b185692), context = <CFMachPort context 0x6080000d3780>}} 2 : <CFRunLoopSource 0x604000167980 [0x107f54bb0]>{signalled = No, valid = Yes, order = 0, context = <CFMachPort 0x608000156f20 [0x107f54bb0]>{valid = Yes, port = 4c07, source = 0x604000167980, callout = __IOHIDEventSystemClientAvailabilityCallback (0x10b179281), context = <CFMachPort context 0x7f8b28d02ae0>}} } , observers = (null), timers = (null), Currently 547998693 (173591812879054)/soft deadline in: 1.84465705e+10 SEC (@-1)/hard deadline in: 1.84465705e+10 SEC (@-1)},}Copy the code

There are sources1, the callout: __IOHIDEventSystemClientQueueCallback

Since this is the type of source1, the system is through the Mach port wake-up event fetch the RunLoop thread, to perform __IOHIDEventSystemClientQueueCallback callback.

Here to solve the case!! The conclusions are as follows:


The IOKit. Framework generates an IOHIDEvent event and receives it from the SpringBoard, which uses the Mach port to generate source1, To awaken the target APP com. Apple. Uikit. RunLoop eventfetch – thread. Signaled == Yes The Eventfetch Thread sets Source0 from __handleEventQueue on the main Runloop to wait == Yes. MainRunLoop calls __eventQueueSourceCallback for event queue processing.


2. Gesture recognition

IOS also relies on RunLoop for gesture recognition.

When the system recognizes a gesture, it interrupts the touch series callback and updates the UIGestureRecognizer status of the corresponding gesture.

UIKit registers an observer with the main RunLoop (tracking mode, Default mode, and Common Modes) :

<CFRunLoopObserver 0x600003d7c280 [0x1dc655ae0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x1848adbd0), context = <CFRunLoopObserver context 0x60000277c620>}

Copy the code

Here’s a look at its Activities property 0x20, combined with the bit tag definition in CF:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

Copy the code

The Observer listens for the kCFRunLoopBeforeWaiting event of the main RunLoop. Whenever the main RunLoop the dormancy, the observer is triggered, at the same time, the callback function is invoked _UIGestureRecognizerUpdateObserver. _UIGestureRecognizerUpdateObserver will detect the current state of the need to be updated recognizer (create, trigger, destroyed).

If there is a signal is triggered, in _UIGestureRecognizerUpdateObserver callback can use UIKit UIGestureEnvironment an inner class to carry out a series of processing. A Gesture event will be posted to the APP event queue. The processing process of this Gesture event should be similar to that of the above event processing. __handleEventQueueInternal will be called to process this Gesture event. We handle this Gesture Event through the UIKit internal class UIGestureEnvironment and eventually call back to our own Gesture callback.

The process of gesture recognition can be summarized as follows:


When the above _UIApplicationHandleEventQueue () to identify a gesture, the first call will Cancel the current touchesBegin/Move/End series callback to interrupt. The system then marks the corresponding UIGestureRecognizer as pending.

Apple registered a Observer monitoring BeforeWaiting (Loop entering hibernation) events, the callback function Observer is _UIGestureRecognizerUpdateObserver (), Internally it gets all GestureRecognizer that has just been marked for processing and executes the GestureRecognizer callback.

This callback is handled when there are changes to the UIGestureRecognizer (create/destroy/state change).


3. Page rendering

  • When we call [UIView setNeedsDisplay], the [view.layer setNeedsDisplay] method of the current view.layer is called.

  • This marks the current layer without drawing it directly. Instead, it does not draw until the current Runloop is about to sleep, which is beforeWaiting.

  • This is followed by a call to the [CALayer display] to do the actual drawing. The CALayer layer determines whether its delegate implements the asynchronous drawing delegate method displayer:, which is the entry point for asynchronous drawing. If it does not implement this method, the system drawing process will continue, and then the drawing process will end.

  • A Backing Store is created inside CALayer to get the graphics context. The next step is to determine whether the layer has a delegate.

  • If so, it calls [layer.delegate drawLayer:inContext:] and gives us a callback to [UIView DrawRect:], which lets us do a few more things on top of the system drawing.

  • If there is no delegate, then [CALayer drawInContext:] is called.

  • For both branches, CALayer will eventually commit the bitmap to the Backing Store and then to the GPU.

  • This is the end of the drawing process.

4, NSTimer

NSTimer is essentially a CFRunLoopTimer, with a toll-free bridged “yard” between them

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef; struct __CFRunLoopTimer { CFRuntimeBase _base; uint16_t _bits; pthread_mutex_t _lock; CFRunLoopRef _runLoop; // The timer corresponds to runloop CFMutableSetRef _rlModes; // Timer corresponding runloop modes CFAbsoluteTime _nextFireDate; CFTimeInterval _interval; /* immutable */ CFTimeInterval _tolerance; /* mutable */ uint64_t _fireTSR; /* timer Specifies the amount of time to trigger, TSR units */ CFIndex _order; /* immutable */ CFRunLoopTimerCallBack _callout; /* timer callback immutable */ CFRunLoopTimerContext _context; /* immutable, except invalidation */ };Copy the code

The triggering process of a Timer looks something like this:

  • The user adds a timer to one or more modes of the runloop;
  • According to whether the timer is set to tolerance, if not, call mk_timer of the underlying XNU kernel to register a Mach-port event; if tolerance is set, register a GCD timer.
  • When the fire time of timer managed by XNU kernel or GCD is reached, the RunLoop is aroused by the corresponding Mach port (mk_timer corresponds to RLM _timerPort, GCD timer corresponds to GCD queue port).
  • The RunLoop executes __CFRunLoopDoTimers, which calls __CFRunLoopDoTimer. The DoTimer method is based on the current Mach time and Timer fireTSR properties. Check whether fireTSR is less than the current Mach time. If it is less than the current Mach time, the timer is triggered and the next fire time fireTSR is updated.

5. RunLoop and threading scenarios

  • Threads and runloops are one-to-one, and their mappings are stored in a global Dictionary
  • Self-created threads do not have RunLoop enabled by default

How do I create a resident thread?

  1. Start a RunLoop for the current thread (the first call to the [NSRunLoop currentRunLoop] method actually creates a RunLoop first)
  2. Add a Port/Source or other event loop to the current RunLoop (if the mode of the RunLoop has no item, the RunLoop will exit)
  3. Start the RunLoop
  @autoreleasepool {

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

        [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

        [runLoop run];

    }

Copy the code

How do you make sure that the child thread data comes back to update the UI without breaking the user’s slide?

When we swipe through the current page while subrequesting data, if the data request is successful and we cut back to the main thread to update the UI, it will affect the current swiping experience.

We can put the UI update event on the NSDefaultRunLoopMode of the main thread, so that the UI will be updated when the user stops sliding and the main thread RunLoop switches from UITrackingRunLoopMode to NSDefaultRunLoopMode

[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];

Copy the code

Error-prone code analysis

NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2");
    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    NSLog(@"3");
});
NSLog(@"4");

- (void)test{
    NSLog(@"5");
}

Copy the code

1423, the test method is not executed.

An afterDelay function creates an NSTimer internally and adds it to the RunLoop of the current thread. Since RunLoop is not enabled for the current thread, this method is invalidated.

NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2");
    [[NSRunLoop currentRunLoop] run];
    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    NSLog(@"3");
});
NSLog(@"4");

- (void)test{
    NSLog(@"5");
}

Copy the code

1423, the test method is not executed.

Cause: If the mode of a RunLoop has no item, the RunLoop exits. After the RunLoop’s run method is called, the RunLoop exits because no item is added to its mode to maintain the RunLoop’s time loop. So let’s start RunLoop ourselves, and make sure we add items.

NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"2");
    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"3");
});
NSLog(@"4");

- (void)test{
    NSLog(@"5");
}

Copy the code

Print result: 14253, the test method will execute.

6. PerformSelector related

How does PerformSelector work?

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

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 no RunLoop this method will fail.

PerformSelector: afterDelay: this method is in the child thread work? Why is that? How to solve it?

It doesn’t work. The child thread has no Runloop by default and therefore no Timer. The solution is to use GCD: Dispatch_after

  • 1.BAT and other major manufacturers iOS interview questions + answers

  • 2.Must-read books for Advanced Development in iOS (Classics must Read)

  • 3.IOS Development Advanced Interview “resume creation” guide

  • (4)IOS Interview Process to the basics