1. Principle of SDWebImage

2. What is a Block?

3. RunLoop profiling


I. Principle of SDWebImage


A library that provides a taxonomy for UIImageView to support remote server image loading.

Features:

1. A UIImageView classification that adds web image loading and cache management 2. An asynchronous picture downloader 3 7, ensure that the same image URL will not be downloaded multiple times 8, ensure that forged image URL will not be downloaded repeatedly 9, ensure that the main thread will not blockCopy the code

The working process

1, the entrancesetImageWithURL: placeholderImage: options: first, according to the placeholderImage then SDWebImageManager according to URL starts processing images. 2, enter SDWebImageManager - downloadWithURL: delegate: options: the userInfo:, To SDImageCache download cache lookup images are from queryDiskCacheForKey: delegate: the userInfo: 3, the first images from memory cache to find if there is a picture, if have image cache in memory, SDImageCacheDelegate callback imageCache: didFindImage:forKey: the userInfo: to SDWebImageManager. 4, SDWebImageManagerDelegate callback webImageManager: didFinishWithImage: to UIImageView + WebCache front-end display pictures, etc. 5. If the image is not in the memory cache, NSInvocationOperation is generated and added to the queue to find whether the image has been cached from the disk. 6, according to the URLKey in the disk cache directory try to read the picture file. This step is performed in NSOperation, so the result callback to the main thread is notifyDelegate:. 7. If the previous operation read the image from the hard disk, add the image to the memory cache (if the free memory is too small, the memory cache will be emptied first). SDImageCacheDelegate callback imageCache: didFindImage:forKey: the userInfo:. Then call back to the display image. 8, if you do not read from the hard disk cache directory images, point out the picture does not exist in all cache, need to download the pictures, the callback imageCache: didNotFindImageForKey: the userInfo:. 9. Share or rebuild a download SDWebImageDownloader to start downloading images. 10, image download by NSURLConnection to do, implement the relevant delegate to determine the image download, download complete and download failure. 11, the connection: didReceiveData: use ImageIO made according to the pictures download progress load effect. After completion of the connectionDidFinishLoading: data download to SDWebImageDecoder do image decoding process. 12. Image decoding is done in an NSOperationQueue without slowing down the main thread UI. If there is a need to download images for secondary processing, it is best to do it here, much more efficient. 13, in the main thread notifyDelegateOnMainThreadWithInfo: declare decoding, imageDecoder: didFinishDecodingImage: the userInfo: Callback to SDWebImageDownloader. ImageDownloader: didFinishWithImage: callback told SDWebImageManager image download is complete. Notify all downloadDelegates to download complete, call-back to display the image where it is needed. Save the image to SDImageCache, memory cache and hard disk cache at the same time. Writing files to hard disk is also done as a separate NSInvocationOperation to avoid slowing down the main thread. 15. SDImageCache registers some message notifications during initialization, clears memory image cache during memory warning or backtracking, and clears expired images at the end of application. SDWI also provides UIButton+WebCache and MKAnnotationView+WebCache for easy use. 17. SDWebImagePrefetcher can download images in advance for subsequent use.Copy the code

Source code analysis

The main object used

First, picture download

1、 SDWebImageDownloader

  • 1. Singleton, picture downloader, responsible for asynchronous picture downloading, and optimized image loading

  • 2. The image download is placed in an NSOperationQueue with a default maximum of 6 concurrent operations

  • 3. Each image corresponds to some callbacks (download progress, complete callback, etc.). The callback information will be stored in the URLCallbacks of the downloader (a dictionary, key is url, value is image download callback array). So the downloader places the download task in a barrierQueue and sets a barrier to ensure that only one thread accesses the URLCallbacks at a time. An NSOperation is created in the block that creates the URLCallbacks and added to the NSOperationQueue.

  • 4. Each image download is an Operation class, which is added to a queue after creation. SDWebimage defines a protocol SDWebImageOperation as the basic protocol for image download operations and declares a cancel method to cancel the operation.

@protocol SDWebImageOperation <NSObject>
-(void)cancel;
@end
Copy the code
  • 5. To download images, SDWebImageDownloaderOperation depend entirely on the NSURLConnection class, inheritance, and realized NSURLConnectionDataDelegate agreement method
connection:didReceiveResponse: connection:didReceiveData: connectionDidFinishLoading: connection:didFailWithError: connection:willCacheResponse: connectionShouldUseCredentialStorage: - the connection: willSendRequestForAuthenticationChalleng - the connection: didReceiveData: method, receive data, create a CGImageSourceRef object, In the first time to obtain data (image width, height), before the image is downloaded, create an image object with CGImageSourceRef object, and generate a UIImage object for callback after scaling and decompression, as well as download progress processing. Note: Zoom: SDScaledImageForKey function in SDWebImageCompat unzip: SDWebImageDecoder file decodedImageWithImageCopy the code

2, SDWebImageDownloaderOption

  • 1. Inherits from NSOperation class, does not simply implement the main method, but uses the more flexible start method, in order to manage their own download status

  • 2. The start method creates the NSURLConnections object for the download, starts the download of the image, and throws a notification of the start of the download.

  • 3. Summary: The core of download is to use NSURLSession to load data. Each image download has an operation operation to complete, and these operations are put into an operation queue, so that concurrent download of images can be realized.

3. SDWebImageDecoder (asynchronous decoding of pictures)

Second, the cache

Reduces network traffic. After downloading an image, you can save it to the local PC. When you download another image, you can directly obtain it from the local PC, which improves user experience. SDWebImage provides image caching, mainly done by SDImageCache. This class handles the memory cache as well as an optional disk cache, where writes to the disk cache are asynchronous and do not affect the UI.

1. Memory cache and disk cache

  • 1. The processing of memory cache is realized by NSCache object. NSCache is similar to a collection container, which stores key-value pairs, similar to nsDictionary class. At the same time, these objects are not important to the program and will be released automatically if memory is tight.

  • 2. The processing of disk cache using NSFileManager object, image storage location in the cache folder, in addition to SDImageCache also defines a serial queue to asynchronously store images.

  • 3.SDImageCache provides a number of methods to cache, get, remove, and empty images. In memory, we use this key as the key of the NSCache. On disk, we use this key as the file name of the image. The URL of a remote downloaded image is the best choice for this key.

2, store image In memory to place a cache first, if you need to cache to disk, disk cache to operate as a task in the serial queue processing, will check the format is a jpeg or PNG images, converts it to response to the image data, and finally it data to disk (file name is the key value after the MD5 string)

3, query picture memory and disk query picture API:

- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key;
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key;

Copy the code

To check whether the image specified by key exists locally, use the following API:

- (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock;
Copy the code

4, Remove image remove image API:

- (void)removeImageForKey:(NSString *)key;
- (void)removeImageForKey:(NSString *)key withCompletion:(SDWebImageNoParamsBlock)completion;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk;
- (void)removeImageForKey:(NSString *)key fromDisk:(BOOL)fromDisk withCompletion:(SDWebImageNoParamsBlock)completion;

Copy the code

5. Clean up pictures (disk)

Clear disk images can be completely empty or partially empty, completely empty is the cache folder delete.

- (void)clearDisk;
- (void)clearDiskOnCompletion:(SDWebImageNoParamsBlock)completion;
Copy the code

Partial cleanup removes files based on the parameters set: the cache validity period of the file (maxCacheAge: 1 week by default) and the maximum cache size (maxCacheSize: If all the file size is greater than the maximum value, you will be in accordance with the reverse order of file was last modified time, to each half of the recursion remove what files, early know cache file total size is less than the maximum), specific code reference – (void) cleanDiskWithCompletionBlock;

SDImageCache processing provides the above API, also provides to obtain the cache size, the number of images in the cache API, commonly used interfaces and attributes:

(1) -getSize: obtain the disk cache size (2) -getDiskCount: obtain the disk cache number of images (3) -clearMemory: Remove all memory images (4) - removeImageForKey:(NSString *) Key series methods: remove images from memory or hard disk as specified on request (5) maxMemoryCost: Sum of pixels held in memory (6) maxCacheSize: Maximum cache size is in bytes. There is no default setting, which is 0, and the prerequisite for cleaning the disk cache is self.maxCacheSize > 0, so 0 means unlimited. (7) maxCacheAge: The maximum time to be retained in memory is in seconds. The default value is one weekCopy the code

Third, SDWebImageManager

In actual use, SDWebImageDownloader and SDImageCache classes are not directly used to download and store images, but use SDWebImageManager to manage. Including the usual use of UIImageView+WebCache control classification, are using SDWebImageManager to handle, the object internal definition of a picture downloader (SDWebImageDownloader) and picture cache (SDImageCache)

@interface SDWebImageManager : NSObject

@property (weak, nonatomic) id <SDWebImageManagerDelegate> delegate;

@property (strong, nonatomic, readonly) SDImageCache *imageCache;
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader; . @endCopy the code

The SDWebImageManager declares a delegate property, which is actually an ID object, and the proxy declares two methods

// Control if the image is not found in the cache, Should download which image - (BOOL) imageManager: (SDWebImageManager *) imageManager shouldDownloadImageForURL (imageURL NSURL *); (UIImage *)imageManager:(SDWebImageManager *)imageManager:(UIImage *)imageManager transformDownloadedImage:(UIImage *)image withURL:(NSURL *)imageURL;Copy the code

These two methods will be SDWebImageManager – downloadImageWithURL: options: progress: completed: method calls, and this method is the core of the SDWebImageManager class (specific see the source code)

SDWebImageManager API:

(1) - (void)cancelAll: cancels all operations in runningOperations (2) - (BOOL)isRunning: Check for running operation, in this case, the operation is to download and cache of combination operation (3) - downloadImageWithURL: options: progress: completed: Core method (4) - (BOOL)diskImageExistsForURL:(NSURL *) URL: Specifies whether the image of the URL is cached on diskCopy the code

View extension

When we use SDWebImage, the most used extension is UIImageView+WebCache for UIImageView, Core method is sd_setImageWithURL: placeholderImage: options: progress: completed:, its use SDWebImageManager singleton download and cache images.

In addition to extending UIImageView, SDWebImage also extends UIView, UIButton, MKAnnotationView and other view classes. For details, please refer to the source code. In addition to using the extension method to download images, Images can also be downloaded using the SDWebImageManager.

UIView+WebCacheOperation: Stores the image operation object corresponding to the current view (via runtime Settings) in the base class: a loadOperationKey property, value is a dictionary. UIImageViewAnimationImages or UIImageViewImageLoad, the value is operation array (dynamic) or objects)

ImageURLStorageKey :{state: URL} imageURLStorageKey:{state:url}

Five, technical points

  • 1. The dispatch_barrier_sync function is used to set the sequence of operations and ensure that subsequent operations are performed after tasks are completed. Often used to ensure thread-safety operations
  • 2.NSMutableURLRequest: Used to create a network request object. You can configure the request header and other information as required
  • 3. The NSOperation and NSOperationQueue: Operation queue is a kind of warning OC concurrent processing method, based on the GCD, relative to the GCD, operation queue has the advantage that you can cancel the task in the task processing queue management in the preparation of dependency on easier, we see how to use dependency in the SDWebImage will download sequence set to last in, first out of order
  • 4.NSURLSession: used for network request and corresponding processing
  • 5. Start background tasks
  • 6.NSCache class: a collection-like container that stores key-value pairs. Similar to the NSDictionary class, we often use caching to temporarily store short-lived but expensive objects. Reusing these objects optimizes performance because their values do not need to be recalculated. On the other hand, these objects are not critical to the program and are discarded when memory is tight
  • 7. Policies for clearing cached images: in particular, setting the maximum cache size. If the total size of all cached files exceeds this size, the premature files are removed recursively half at a time, in reverse order of the last modification time of the files, until the actual size of the cache is less than the maximum size we set.
  • 8. Image decompression operation: this operation can be seen in SDWebImageDecoder. M +decodedImageWithImage method implementation.
  • 9. Processing of GIF images
  • 10. Processing of WebP pictures.

What is a Block?


  • A Block is an object that encapsulates a function and its execution context.

Such as:

NSInteger num = 3;
    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
        return n*num;
    };

    block(2);

Copy the code

To compile the.m file with the clang-rewrite-objc wytest. m command, the block is compiled to this form:

    NSInteger num = 3;

    NSInteger(*block)(NSInteger) = ((NSInteger (*)(NSInteger))&__WYTest__blockTest_block_impl_0((void *)__WYTest__blockTest_block_func_0, &__WYTest__blockTest_block_desc_0_DATA, num));

    ((NSInteger (*)(__block_impl *, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2);

Copy the code

WYTest is the file name and blockTest is the method name, which can be ignored. Where the __WYTest__blockTest_block_impl_0 structure is

struct __WYTest__blockTest_block_impl_0 { struct __block_impl impl; struct __WYTest__blockTest_block_desc_0* Desc; NSInteger num; __WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

The __block_impl structure is

struct __block_impl { void *isa; //isa pointer, so Block is the object int Flags; int Reserved; void *FuncPtr; // function pointer};Copy the code

A block contains an ISA pointer, so it is essentially an OC object.

static NSInteger __WYTest__blockTest_block_func_0(struct __WYTest__blockTest_block_impl_0 *__cself, NSInteger n) {
  NSInteger num = __cself->num; // bound by copy

        return n*num;
    }

Copy the code

So a Block is an object that encapsulates a function and its execution context and since a Block encapsulates a function, it also has parameters and return values.

2. Block variable interception

1. Local variable intercept is value intercept. Such as:

    NSInteger num = 3;

    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

        return n*num;
    };

    num = 1;

    NSLog(@"%zd",block(2));

Copy the code

The output here is 6 instead of 2 because the interception of the local variable num is value interception. Similarly, changing num in a block is invalid, and the compiler may even report an error.

2. Local static variable intercept is pointer intercept.

   static  NSInteger num = 3;

    NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){

        return n*num;
    };

    num = 1;

    NSLog(@"%zd",block(2));

Copy the code

The output is 2, which means that num = 1. It is also valid to modify m in a block.

3, global variables, static global variable interception: not interception, direct value.

Let’s do the same with Clang and see what happens.

static NSInteger num3 = 300;

NSInteger num4 = 3000;

- (void)blockTest
{
    NSInteger num = 30;

    static NSInteger num2 = 3;

    __block NSInteger num5 = 30000;

    void(^block)(void) = ^{

        NSLog(@"%zd",num); // local variable NSLog(@"%zd",num2); // Static variable NSLog(@"%zd",num3); // the global variable NSLog(@"%zd",num4); // global static variable NSLog(@"%zd",num5); //__block modifiers}; block(); }Copy the code

The compiled

struct __WYTest__blockTest_block_impl_0 { struct __block_impl impl; struct __WYTest__blockTest_block_desc_0* Desc; NSInteger num; // local variable NSInteger *num2; // static variable __Block_byref_num5_0 *num5; Void *fp, struct __WYTest__blockTest_block_impl_0(void *fp, struct __WYTest__blockTest_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};Copy the code

(impl. Isa = & _NSConcreteStackBlock; You can see that local variables are compiled as values, while static variables are compiled as Pointers. Global variables are not captured. The __block variable is also intercepted as a pointer, and a new struct object is generated:

struct __Block_byref_num5_0 {
  void *__isa;
__Block_byref_num5_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger num5;
};

Copy the code

This object has an attribute: num5, which is the variable we decorate with __block. Here __forwarding points to itself (stack block). In general, __block modifiers are required if we want to assign to local variables intercepted by a block, while __block modifiers are not required for static variables. In addition, any access to self or a member variable in a block intercepts self.

Several forms of Block

  • There are three forms: global Block(_NSConcreteGlobalBlock), stack Block(_NSConcreteStackBlock), and heap Block(_NSConcreteMallocBlock)

    Stack blocks are stored in the stack, heap blocks are stored in the heap, and global blocks are stored in the.data area

1. Blocks that do not use external variables are global blocks

Such as:

    NSLog(@"% @",[^{
        NSLog(@"globalBlock");
    } class]);

Copy the code

Output:

__NSGlobalBlock__

Copy the code

2. A block that uses external variables and does not copy is a stack block

Such as:

  NSInteger num = 10;
    NSLog(@"% @",[^{
        NSLog(@"stackBlock:%zd",num);
    } class]);

Copy the code

Output:

__NSStackBlock__

Copy the code

Daily development is often used in this case:

[self testWithBlock:^{
    NSLog(@"% @",self);
}];

- (void)testWithBlock:(dispatch_block_t)block {
    block();

    NSLog(@"% @",[block class]);
}

Copy the code

3. Copy a stack block, that is, a heap block. Copy a global block, that is, a global block

  • For example, the global copy operation in heap 1 is assigned:
void (^globalBlock)(void) = ^{
        NSLog(@"globalBlock");
    };

 NSLog(@"% @",[globalBlock class]);

Copy the code

Output:

__NSGlobalBlock__

Copy the code

It’s still a global block

  • Assign to stack block 2:
NSInteger num = 10;

void (^mallocBlock)(void) = ^{

        NSLog(@"stackBlock:%zd",num);
    };

NSLog(@"% @",[mallocBlock class]);

Copy the code

Output:

__NSMallocBlock__

Copy the code

The mallock on the left is a heap block, and the copy on the right is still a stack block.

[self testWithBlock:^{

    NSLog(@"% @",self);
}];

- (void)testWithBlock:(dispatch_block_t)block
{
    block();

    dispatch_block_t tempBlock = block;

    NSLog(@"% @, % @",[block class],[tempBlock class]);
}

Copy the code

Output:

__NSStackBlock__,__NSMallocBlock__

Copy the code
  • That is, if you copy a stack Block, it’s going to be copied to the heap, if you copy a heap Block, it’s going to increase the reference count, it’s going to copy a global Block, because it’s already initialized, so it doesn’t do anything.

In addition, when a __block variable is copied, since __forwarding exists, the __forwarding pointer on the stack points to the __forwarding variable on the heap, and the __forwarding pointer on the heap points to itself. Therefore, if __block is changed, You are actually modifying a __block variable on the heap.

The purpose of the __forwarding pointer is to access the same __block variable from any memory location.

  • In addition, since the __block-modified variable captured by the block will hold the variable, if self is modified by __block and self holds the block, and self holds the block inside the block, it will cause a multi-loop reference, that is, self holds the block. Block holds a __block variable, which holds self, causing a memory leak. Such as:
  __block typeof(self) weakSelf = self;

    _testBlock = ^{

        NSLog(@"% @",weakSelf);
    };

    _testBlock();

Copy the code

If you want to solve this circular reference, you can actively disconnect the __block variable from holding self, that is, after using WeakSelf inside the block, set it to nil, but there is a problem with this approach, if the block is never called, then the circular reference will always exist. So, we’re better off decorating self with __weak


RunLoop analysis


RunLoop is maintained internallyEvent LoopCome to the rightManage events/messagesAn object of.

1. When there is no message processing, the hibernation avoids resource occupation and switches from the user mode to the kernel mode (CPU-kernel mode and user mode). 2

Why doesn’t main exit?

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

UIApplicationMain internally turns on the main thread RunLoop by default and executes an infinite loop (not a simple for loop or while loop).

Int main(int argc, char * argv[]) {BOOL running = YES;do{// Perform various tasks, handle various events //...... }while (running);

    return 0;
}

Copy the code

The UIApplicationMain function never returns, but is constantly receiving processing messages and waiting to sleep, so it keeps running after running the program.

RunLoop data structure

NSRunLoop(Foundation) encapsulates CFRunLoop(CoreFoundation) and provides an object-oriented API for RunLoop.

CFRunLoop: RunLoop object CFRunLoopMode: running mode CFRunLoopSource: input source/event source CFRunLoopTimer: timing source CFRunLoopObserver: observer

1, CFRunLoop

By pThread (thread object, RunLoop is one-to-one with thread), currentMode(current running mode), modes(collection of multiple running modes), commonModes(collection of mode name strings), and commonModelItems(Observer,Timer,Sourc) E set)

2, CFRunLoopMode

Consisting of name, source0, source1, observers, and timers

3, CFRunLoopSource

There are source0 and source1

  • source0: that is, non-port-based events, which are user-triggered events. You need to manually wake up the thread and switch the current thread from kernel state to user state
  • source1: Port-based, containing a mach_port and a callback, listens for system ports and messages sent through the kernel and other threads, and proactively wakes up runloops to receive dispatch system events. Ability to wake up threads

4, CFRunLoopTimer

Time-based triggers are basically NSTimer. Wake up RunLoop at a preset point in time to perform a callback. Since it is runloop-based, it is not real-time (i.e. NSTimer is inaccurate). Because RunLoop is only responsible for distributing messages from the source. If the thread is currently working on a heavy task, the Timer may be delayed or executed less than once.

5, CFRunLoopObserver

Listen for the following times :CFRunLoopActivity

  • kCFRunLoopEntryRunLoop is ready to start
  • kCFRunLoopBeforeTimersRunLoop will handle some timer-related events
  • kCFRunLoopBeforeSourcesRunLoop will handle some Source events
  • kCFRunLoopBeforeWaitingRunLoop will go to sleep, switching from user mode to kernel mode
  • kCFRunLoopAfterWaitingRunLoop is woken up after switching from kernel to user mode
  • kCFRunLoopExitRunLoop exit
  • kCFRunLoopAllActivitiesListening for all states

6. Relationships between data structures

Threads correspond to runloops one by one. Runloops are one-to-many with modes, and modes are one-to-many with sources, timers, and observers

3, RunLoop Mode

The first thing to know about modes is that a RunLoop object can contain multiple modes, and only one of them (CurrentMode) can be specified each time the main function of RunLoop is called. To switch Mode, you need to specify a new Mode. The main purpose is to separate different sources, timers, and observers so that they do not affect each other.

It is not acceptable to handle Source, Timer, Observer events on Mode2 or Mode3 while RunLoop is running on Mode1

There are five types of CFRunLoopMode:

  • KCFRunLoopDefaultMode: The default mode in which the main thread runs

  • UITrackingRunLoopMode: tracking user interaction events (used in ScrollView to track touch sliding, ensuring interface sliding is not affected by other modes)

  • UIInitializationRunLoopMode: in the first to enter the first Mode when just start the App, start after the completion of the will no longer be used

  • GSEventReceiveRunLoopMode: accept system internal event, is usually not used

  • Kcfrunloopcommonmode: pseudo-mode, not a true operation Mode, is a solution for synchronizing Source/Timer/Observer modes into multiple modes

Implementation mechanism of RunLoop

This picture is widely circulated on the Internet. The most important thing for RunLoop is to ensure that threads sleep when there are no messages and wake up when there are messages to improve application performance. RunLoop is a mechanism that relies on the kernel (Mach in Darwin, the core component of Apple’s operating system).

RunLoop receives and sends messages using the mach_msg() function. It essentially calls the function mach_MSg_trap (), which is equivalent to a system call that triggers a kernel state switch. Mach_msg_trap () switches to kernel mode when called in user mode; The mach_msg() function implemented by the kernel in the kernel state does the actual work. The port-based source1 listens on the port and triggers a callback if there is a message on the port. For source0, manually mark as pending and manually wake up RunLoop

1. Notify the observer that RunLoop is about to start. 2. Notify the observer that the Timer event is about to be processed. Notify the observer that the source0 event is about to be processed. 4. Handle the source0 event. 5. If the port-based source (Source1) is ready and in the waiting state, go to Step 9. 6. Notify the observer that the thread is about to go to sleep. 7. Put the thread to sleep and switch from user to kernel mode until any of the following events occur.

  • A port-based Source1 event (source0).
  • A Timer is out of time.
  • RunLoop’s own timeout is up.
  • Manually awakened by another caller.

8. Notify the observer that the thread will wake up. 9. Handle events received upon awakening.

  • If the user-defined timer starts, process the timer event and restart RunLoop. Go to Step 2.
  • If the input source is started, the corresponding message is passed.
  • If RunLoop is awakened and the time has not expired, restart the RunLoop. Enter Step 2.

10. Notify the observer of the end of RunLoop.

RunLoop and NSTimer

A common question: Does the timer still work when you slide the tableView? By default RunLoop runs in kCFRunLoopDefaultMode, and when sliding tableView, RunLoop switches to UITrackingRunLoopMode, while Timer is in kCFRunLoopDefaultMode. It cannot accept the event that handles the Timer. How to solve this problem? Adding a Timer to the UITrackingRunLoopMode does not solve the problem because Timer events are not accepted by default. So we need to add the Timer to both UITrackingRunLoopMode and kCFRunLoopDefaultMode. How do you add timers to multiple modes at the same time? We’re going to use NSRunLoopCommonModes

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code

Timers are added to multiple modes, so that even if RunLoop switches from kCFRunLoopDefaultMode to UITrackingRunLoopMode, Timer events are not affected

Runloops and threads

  • 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 [NSRunLoop currentRunLoop] actually creates a RunLoop first) 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)

   @autoreleasepool {
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
Copy the code

2, output the following code execution order

 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

The answer is 1423, and the test method does not execute. The reason is that if it is a delay function with afterDelay, an NSTimer is created internally and added to the RunLoop of the current thread. That is, if RunLoop is not enabled on the current thread, the method will be disabled. So let’s change it to:

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

However, the test method is still not executed. The reason is that if the mode of the 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, make sure we add the item

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

3. How to ensure that the child thread data comes back to update the UI without breaking the user’s sliding operation?

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