@(iOS development learning)[Review old, learn new, nuggets blog]
[TOC]
1. AutoreleasePool analysis and collation
To analyze AutoreleasePool, the following four scenarios are analyzed
The Person class is used to print the release timing of the object
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, strong) NSString* name;
@end
NS_ASSUME_NONNULL_END
@implementation Person
- (void)dealloc {
NSLog(@"func = %s, name = %@", __func__, self.name);
}
@end
Copy the code
Scenario 1: The object is not added to the AutoreleasePool
#import <UIKit/UIKit.h>
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolWithOutVC : UIViewController
@end
NS_ASSUME_NONNULL_END
@interface AutoreleasePoolWithOutVC ()
@property (nonatomic, strong) Person* zhangSanStrong;
@property (nonatomic, weak) Person* zhangSanWeak;
@end
@implementation AutoreleasePoolWithOutVC
- (void)viewDidLoad {
[super viewDidLoad];
Person *xiaoMing = [[Person alloc] init];
xiaoMing.name = @"xiaoMing";
_zhangSanStrong = [[Person alloc] init];
_zhangSanStrong.name = @"zhangSanStrong";
Person *zhangSanWeak = [[Person alloc] init];
zhangSanWeak.name = @"zhangSanWeak";
_zhangSanWeak = zhangSanWeak;
NSLog(@"func = %s, xiaoMing = %@", __func__, xiaoMing);
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
@end
Copy the code
_zhangSanWeak is released after viewDidLoad is finished.
Scenario 2: Objects are added to the manually created AutoreleasePool
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolManualWithVC : UIViewController
@end
NS_ASSUME_NONNULL_END
#import "AutoreleasePoolManualWithVC.h"
#import "Person.h"
@interface AutoreleasePoolManualWithVC ()
@property (nonatomic, strong) Person* zhangSanStrong;
@property (nonatomic, weak) Person* zhangSanWeak;
@end
@implementation AutoreleasePoolManualWithVC
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
Person *xiaoMing = [[Person alloc] init];
xiaoMing.name = @"xiaoMing";
_zhangSanStrong = [[Person alloc] init];
_zhangSanStrong.name = @"zhangSanStrong";
Person *zhangSanWeak = [[Person alloc] init];
zhangSanWeak.name = @"zhangSanWeak";
_zhangSanWeak = zhangSanWeak;
}
NSLog(@"func = %s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
NSLog(@"func = %s", __func__);
}
@end
Copy the code
The result: _zhangSanWeak, the temporary object xiaoMing and weak, is released before viewDidLoad ends.
Scenario 3: The object is added to the AutoreleasePool of the system
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolSystermWithVC : UIViewController
@end
NS_ASSUME_NONNULL_END
#import "AutoreleasePoolSystermWithVC.h"
@interface AutoreleasePoolSystermWithVC ()
@property (nonatomic, strong) NSString* zhangSanStrong;
@property (nonatomic, weak) NSString* zhangSanWeak;
@end
@implementation AutoreleasePoolSystermWithVC
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"func = %s start", __func__);
_zhangSanStrong = [NSString stringWithFormat:@"zhangSanStrong"];
NSString* zhangSanWeak = [NSString stringWithFormat:@"zhangSanStrong"];
_zhangSanWeak = zhangSanWeak;
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)printInfo {
NSLog(@"self.zhangSanStrong = %@", _zhangSanStrong);
NSLog(@"self.zhangSanWeak = %@", _zhangSanWeak);
}
@end
Copy the code
Running results: AutoreleasePool is added to each Runloop iteration. After the Runloop starts, the AutoreleasePool is created and an Autorelease object is added to the pool. The Autorelease object is released after the Runloop ends or during sleep.
Scene 4 🙁Tagged Pointer
The AutoreleasePool object is added to the system
After seeing other people’s blog, I decided to manually verify it, but I didn’t want to copy other people’s code completely. When I copied my own code, I was too lazy to write too much, so I simply wrote @ “1”, so the result was inconsistent with the blog, so I doubted life more. I think I am not the only one to this situation 😄.
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AutoreleasePoolSystermWithTaggedPointerVC : UIViewController
@end
NS_ASSUME_NONNULL_END
#import "AutoreleasePoolSystermWithTaggedPointerVC.h"@interface AutoreleasePoolSystermWithTaggedPointerVC () @property (nonatomic, weak) NSString* tagged_yes_1; // Tagged Pointer @property (nonatomic, weak) NSString* tagged_yes_2; // Tagged Pointer @property (nonatomic, weak) NSString* tagged_yes_3; // Tagged Pointer @property (nonatomic, weak) NSString* tagged_no_1; // non-tagged Pointer @property (nonatomic, weak) NSString* tagged_no_2; // non-tagged Pointer @property (nonatomic, weak) NSString* tagged_no_3; / / not Tagged Pointer @ end @ implementation AutoreleasePoolSystermWithTaggedPointerVC - (void) viewDidLoad {[super viewDidLoad]; NSLog(@"func = %s start", __func__);
NSString* tagged_yes_1_str = [NSString stringWithFormat:@"1"];
_tagged_yes_1 = tagged_yes_1_str;
NSString* tagged_yes_2_str = [NSString stringWithFormat:@"123456789"];
_tagged_yes_2 = tagged_yes_2_str;
NSString* tagged_yes_3_str = [NSString stringWithFormat:@"abcdefghi"];
_tagged_yes_3 = tagged_yes_3_str;
NSString* tagged_no_1_str = [NSString stringWithFormat:@"0123456789"];
_tagged_no_1 = tagged_no_1_str;
NSString* tagged_no_2_str = [NSString stringWithFormat:@"abcdefghij"];
_tagged_no_2 = tagged_no_2_str;
NSString* tagged_no_3_str = [NSString stringWithFormat:@"Chinese characters"];
_tagged_no_3 = tagged_no_3_str;
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"func = %s start", __func__);
[self printInfo];
NSLog(@"func = %s end", __func__);
}
- (void)printInfo {
NSLog(@"self.tagged_yes_1 = %@", _tagged_yes_1);
NSLog(@"self.tagged_yes_2 = %@", _tagged_yes_2);
NSLog(@"self.tagged_yes_3 = %@", _tagged_yes_3);
NSLog(@"self.tagged_no_1 = %@", _tagged_no_1);
NSLog(@"self.tagged_no_2 = %@", _tagged_no_2);
NSLog(@"self.tagged_no_3 = %@", _tagged_no_3);
}
@end
Copy the code
Running results:
Tagged Pointer
The type ofAutorelease objects
, the system will not release- non
Tagged Pointer
The type ofAutorelease objects
, the system will be in the currentRunloop
Release after completion
AutoreleasePool definition
- Autorelease pooling is implemented by Autorelease EpoolPage as a two-way linked list
- When an object calls the Autorelease method, it is added to the stack of AutoreleasePoolPage
- Calling the AutoreleasePoolPage:: POP method sends a release message to the object in the stack
In ARC environment, begin with the alloc/new/copy/mutableCopy method return value of the object is generated and holding, other condition is its object, the object of the holder is AutoreleasePool.
When we use @Autoreleasepool {}, the compiler converts it to the following form
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
Copy the code
__AtAutoreleasePool is defined as follows:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush(); }.. // Push the intermediate object onto the stack (atAutoReleasepoolobj is also an object, equivalent to the sentinel, representing an Autoreleasepool boundary, // corresponding to the current Autoreleasepool, pop is used to mark the end position, Is the first to be pushed on the stack by the current AutoreleasePool, and the last to be popped off the stack).. ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; };Copy the code
The objc_autoreleasePoolPush() method is called at creation time and the objc_autoreleasePoolPop() method is called at release time, which is a simple wrapper.
AutoreleasePool does not have a separate structure, but is essentially a bidirectional linked list, the nodes of which are AutoreleasePoolPage objects. The size of each node is 4KB (4*1024=4096 bytes), excluding the size of the instance variable, and the remaining space is used to store the address of the Autorelease object.
class AutoreleasePoolPage { magic_t const magic; AutoreleasePage id *next; Pthread_t const thread; pthread_t const thread; Autoreleasepool * const parent; AutoreleasePoolPage * const parent; // Parent = nil AutoreleasePoolPage *child; Uint32_t const depth; // Uint32_t const depth; // Uint32_t hiwat; // high water mark };Copy the code
The next pointer points to the free place where new objects (Autorelease objects, sentinel objects) will be added.
Objc_autoreleasePoolPush Execution process
When calling AutoreleasePoolPage: : push () method, first of all to the current page (hotPage) add a sentinel node next pointer pointing to the position of the object (POOL_SENTINEL, value is nil). Continue adding the sentinel if AutoreleasePool is nested behind it, otherwise press the Autorelease object on top of the sentinel. Move the next pointer to the high address until next == end() indicates that the current page is full. When next == begin(), AutoreleasePoolPage is empty. When next == end(), AutoreleasePoolPage is full.
- 1,
hotPage
And the currentpage
The dissatisfaction. callpage->add(obj)
Method to add an object toAutoreleasePoolPage
The stack - 2,
hotPage
And the currentpage
Is full. callautoreleaseFullPage
To initialize a new page, callpage->add(obj)
Method to add an object toAutoreleasePoolPage
The stack - 3,
hotPage
. callautoreleaseNoPage
To create ahotPage
, the callpage->add(obj)
Method to add an object toAutoreleasePoolPage
The stack.
objc_autoreleasePoolPop
Implementation process
When the AutoreleasePoolPage::pop() method is called, the pop input is the return value of the push function, which is the POOL_SENTINEL memory address, or pool token. When the POP operation is performed, the page of the sentinel object is found based on the address of the sentinel object passed in, and the Autorelease object pushed in later than the sentinel object is released. Any Autoreleased object whose memory address is after the pool token will be released. Until next on the page where the Pool token resides points to the pool token. And move the next pointer back to the correct position.
When is the AutoreleasePool object released?
Without manually adding AutoreleasePool, the Autorelease object is released at the end of the current Runloop iteration. Manually added Autorelease objects are also counted automatically and are released when the reference count reaches zero.
Before the actual validation, learn about a few private apis, check the status of the auto-release pool, and check the object reference count under ARC
// declare a private API extern void _objc_autoreleasePoolPrint(void); extern uintptr_t _objc_rootRetainCount(id obj); _objc_autoreleasePoolPrint(); // Call print autofree pool object _objc_rootRetainCount(obj); // Call to view the object's reference countCopy the code
NSThread, NSRunLoop, and NSAutoreleasePool
According to the description of NSRunLoop in the Official Apple documentation, each thread, including the main thread, will have its own NSRunLoop object, which will be created automatically when needed. Also, according to the official Apple documentation for NSAutoreleasePool, we know that the NSRunLoop object in the main thread (and presumably in other system-level threads as well), For example, before each event loop of dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) starts, an Autoreleasepool is automatically created. Drain at the end of the Event loop.
NSLog(@”[NSRunLoop currentRunLoop] = %@”, [NSRunLoop currentRunLoop]); The following table shows the screenshot of the log.
_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.
When to use @Autoreleasepool
According to Apple’s documentation, the usage scenarios are as follows:
- When you write a command line based program, you don’t have a UI framework like The Cocoa framework AppKit.
- Write loops that contain a large number of temporarily created objects. (Examples from this article)
- A new thread is created. (Not required for Cocoa programs to create threads)
- A task that runs in the background for a long time.
Tagged Pointer
Tagged Pointer is an interesting technique that can improve performance and save memory. In OS X 10.10, NSString uses this technique, so let’s take a look at how it is implemented.
- Tagged Pointer is used to store small objects such as NSNumber, NSDate, and NSString
- 2. The Tagged Pointer is no longer an address but a real value. No longer an object, no place in memory in the heap, no need for malloc and free. Avoid direct access to the object’s ISA variables in your code and use the methods isKindOfClass and objc_getClass instead.
- 3. Memory reads are 3 times more efficient, creating 106 times faster than before.
Questions you will be asked in an interview?
How does the reference count change when entering and exiting a pool? 2. Why use bidirectional rather than unidirectional lists? 3. You talked about sentinels. How can sentinels be used in linked lists to simplify programming?
Refer to the blog
Understand RunLoop in depth
Understand AutoreleasePool in depth
Autorelease behind the black screen
Autoreleasepool —- In-depth analysis of autoreleasepool
IOS development Autorelease pools and runloops
Objective-c Autorelease Pool
Understand Tagged Pointer
Take the Tagged Pointer string
Automatic reference counting in Objective-C
2, Block learning arrangement
What is a Block
A Block is ostensibly an anonymous function with automatic (local) variables. Essentially an object implementation of a closure, a Block is simply a structure object. In ARC, most of the time Block copying code from stack to heap is implemented by the compiler (Blcok as return value or parameter)
2.1. Block compiles and transforms the structure
2.1.1 Create a macOS project and write the simplest Block.
#import <Foundation/Foundation.h>
typedef void(^blockVoid)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
blockVoid block = ^() {
NSLog(@"block");
};
block();
}
return 0;
}
Copy the code
2.1.2 Use clang command to process into CPP file to get a preliminary understanding of Block.
Because the command is too long, we’ll alias it genCpp_mac. alias genCpp_mac=’clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk’
2.1.3 Go to the. M directory on the terminal and run genCpp_mac main.m. A. CPP file with the same name is generated in the current directory
2.1.4. Open the. CPP file to view the code related to Block
struct __block_impl { void *isa; // Int Flags; // int Flags; // use int Reserved when implementing block internal operations; Void *FuncPtr; // the function pointer called when the block executes}; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, struct __main_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }}; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_x0_1sgmhpyx6535p2pfkfsbfvww0000gn_T_main_94a22e_mi_0); Static struct __main_block_desc_0 {size_t reserved; // Keep the field size_t Block_size; // Block size (sizeof(struct __main_block_impl_0)); // Block size (sizeof(struct __main_block_impl_0)); } __main_block_desc_0_DATA = {0, sizeof(struct __main_block_impl_0)}; /* Block initializer: __main_block_func_0 (C function pointer to block syntax) __main_block_desc_0_DATA (as an instance pointer to the __main_block_DESc_0 structure initialized with a static global variable) struct __main_block_IMPL_0 TMP = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); struct __main_block_impl_0 *blk = &tmp; FuncPtr (* BLK -> im.funcptr)(BLK); This is simply calling a function using a function pointer. The pointer to the __main_block_func_0 function converted by Block syntax is assigned to the member variable FuncPtr, and the argument __cself to the __main_block_func_0 function points to the value of the Block, The source code shows that blocks are officially passed as parameters. */ int main(int argc, const char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; blockVoid block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); }return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
Copy the code
2.2 actual structure of blockBlock_private.h
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1struct Block_descriptor_3 { // requires BLOCK_HAS_SIGNATURE const char *signature; const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT }; struct Block_layout { void *isa; // All objects have this pointer, which is used to implement object-specific functions. volatile int32_t flags; // Contains ref count(block copy operates on this variable) int32_t reserved; Void (*invoke)(void *,...) ; // A function pointer to a specific block implementation's function call address. struct Block_descriptor_1 *descriptor; // Represents additional description of the block, mainly size and Pointers to copy and dispose functions. // Imported variables // capture local variables, block can access its external local variables, because these variables (or the address of the variables) in the structure. };Copy the code
2.3 Types of blocks
Block type | Storage area | Effect of copy | The life cycle |
---|---|---|---|
_NSConcreteStatckBlock | The stack | Copy from stack to heap, incorporate BLOCK_NEEDS_FREE into flags. This flag indicates that the block needs to be released, which is used for release and copying again. If there is a secondary copy function, it is called to copy the captured variable from the stack to the heap | Out of scope |
_NSConcreteMallocBlock | The heap | Pure reference count plus one | The reference count is 0 and is released when the runloop ends |
_NSConcreteGlobalBlock | Global data area | Do nothing and go straight back to Blcok | The entire app life cycle |
- 1. As long as you don’t access external variables
__NSGlobalBlock__
Block of type, regardless of whether__strong
or__weak
Embellishment (no default__strong
). - 2, if access to external variables, by
__strong
Modification is__NSMallocBlock__
Type,__weak
Modification is__NSStackBlock__
Type.
Note: Blocks that access external variables are of type __NSStackBlock__ when compiled, but ARC blocks that are modified by __strong are automatically copied at runtime and end up calling _Block_copy_internal. Redirect ISA from _NSConcreteStatckBlock to _NSConcreteMallocBlock.
static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; . aBlock = (struct Block_layout *)arg; . // Its a stack block. Make a copy.if(! Struct Block_layout *result = malloc(aBlock-> Descriptor ->size);if(! result)return(void *)0; // Copy block in stack to memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // reset refcount result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; // Change isa to _NSConcreteMallocBlock. Result ->isa = _NSConcreteMallocBlock;if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)... \n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else{... }}Copy the code
2.3.1 difference between __NSStackBlock__ and __NSMallocBlock__ : The modifier type is__strong
or__weak
/** __NSMallocBlock__:__strong: __NSStackBlock__:__weak */ -(void)blockStatckVsGloable {NSInteger I = 10; __strong void (^mallocBlock)(void) = ^{ NSLog(@"i = %ld", (long)i);
};
__weak void (^stackBlock)(void) = ^{
NSLog(@"self.number = %@", self.number);
};
}
Copy the code
2.3.2 the difference between __NSStackBlock__ and __NSGlobalBlock__ : access to external variables
/** __NSStackBlock__: access external variables __NSGlobalBlock__: access external variables */ -(void)blockStatckVsGloable {NSInteger I = 10; __weak void (^stackBlock)(void) = ^{ NSLog(@"i = %ld", (long)i);
};
__weak void (^globalBlock2)(void) = ^{
};
}
Copy the code
2.4 influence of access to modify variables on block structure
2.4.1 global variables
The structure of the Block does not change for accessing and modifying global variables.
2.4.2 Global static variables
For accessing and modifying global static variables, the structure of the Block does not change.
2.4.3 Local variables
If an ARC NSConcreteStackBlock introduces a local variable, it will be replaced by a block of type NSConcreteMallocBlock.
Accessing local variables
Modify local variables (local variables need to be decorated with __Block)
__main_block_copy_0
localVariable
__Block_byref_localVariable_0
__Block_byref_localVariable_0
__forwarding
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) { struct Block_byref **destp = (struct Block_byref **)dest; struct Block_byref *src = (struct Block_byref *)arg; . // Copy ->forwarding = copy; // Patch heap copy to point to itself (skip write-barrier) // Forwarding in stack refers to copy in heap SRC ->forwarding = copy; // patch stack to point to heap copy ... }Copy the code
2.4.4 Local static variables (modify without __Block modifier)
To access and modify local static variables, a Block intercepts the pointer to the static variable and changes the value directly through the pointer
2.4.5 Self implicitly loop reference
Self strongly holds the Block when the circular reference occurs, and as you can see from the following, the Block also strongly holds self.
2.6. Auxiliary functions of block
See the Block technique and low-level parsing for details
2.6 access or modify external variables
Objective-c Block implementation
Refer to the blog
Objective-c Advanced Programming Blocks
Block techniques and low-level parsing
Block source code analysis and in-depth understanding
Implementation of Objective-C Blocks
Blocks are not that difficult (B) : Memory management of blocks and variables
Source code parsing starts with Block
How do blocks in iOS hold objects
3, Runloop learning and sorting (continuous update and correction)
[TOC]
@(iOS development learning)
The problem
1. What is RunLoop
A RunLoop is essentially an object that manages the events and messages it needs to process and provides an entry function to execute the logic of the Event Loop above. Once the thread executes the function, it will remain in the “receive message -> wait -> process” loop within the function until the loop ends (such as the incoming quit message) and the function returns. Generally speaking, a thread can only execute one task at a time, and when it is finished, the thread exits. If we need a RunLoop, let the thread handle events at any time without exiting. The RunLoop core is a conditional loop.
Runloop composition
RunLoop structure involves the following four concepts: RunLoop Mode, Input Source, Timer Source, and RunLoop Observer.
Observers, InputSource, TimerSource, and other sources are included in a Runloop, and they can specify only one mode. To switch mode, you can only restart Runloop to specify another mode. The purpose of this is to handle sources, timers, and observers with different priorities.
Various modes of Runloop
NSUserDefaultRunloopMode // Default mode, usually the main thread runs under this mode UITrackingRunloopMode // interface trace mode, for ScrollView trace interface slide, Ensure the interface slip is not affected by other mode of UIInitializationRunloopMode / / just start the App into one of the first mode, Start not use GSEventReceiveRunloopMode / / acceptance after the completion of system internal mode of events, usually in less than. NSRunloopCommonModes // is not a true mode. NSUserDefaultRunloopMode and UITrackingRunloopMode are both marked as NSRunloopCommonModesCopy the code
The various modes of Runloop
Specifies the priority of the event in the run loop. Threads need different modes to run, to respond to different events, to handle different situational patterns. (For example, when you can optimize tableView, you can set UITrackingRunLoopMode to not perform some operations, such as setting pictures, etc.)
1, the InputSource:
Official documentation falls into three categories, port-based, custom, and Perform selector based. But sources can also be divided into two classes, source0 and source1, by function call stack. Source1 is port-based and contains a match_port and a callback (function pointer) that actively wakes up the Runloop thread; Source0 is non-port-based, contains only one callback (function pointer) and cannot actively fire events. That is, user-triggered events, including custom sources and Perfom Selector
Note: The button click event is the Source0 event from the function call stack. In fact, clicking on the screen generates an event that is passed to Source1, which then sends it to Source0.
2, TimerSource:
So that’s CFRunloopTimerRef, which is NSTimer. NSTimer must be added to the Runloop when it is created; otherwise, it cannot be executed. When adding NSTimer to the Runloop, you must specify mode to determine which mode NSTimer will run in.
3, the observer:
Listen for the status of Runloop
Runloop
The various states of
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // Approaching Runloop kCFRunLoopBeforeTimers = (1UL << 1), // Approaching Timer kCFRunLoopBeforeSources = (1UL << 2), // Source kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), // kCFRunLoopExit = (1UL << 7), // Runloop kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code
Runloop Life cycle
The life cycle | The main thread | The child thread |
---|---|---|
create | Default System Creation | Apple doesn’t allow direct creationRunLoop , it only provides two auto-fetching functions:CFRunLoopGetMain() 和 CFRunLoopGetCurrent() . Called in the current child thread[NSRunLoop currentRunLoop] If you have it, get it; if you don’t, create it |
Start the | The default startup | Manual start |
To obtain | [NSRunLoop mainRunLoop] orCFRunLoopGetMain() |
[NSRunLoop currentRunLoop] orCFRunLoopGetCurrent() |
The destruction | At the end of the app | The timeout period expires or end manuallyCFRunLoopStop() |
The CFRunLoopStop() method only ends the current runMode:beforeDate: call, not subsequent calls.
Startup note:
- – After starting with run, Runloop will always run processing input source data, repeatedly calling runMode:beforeDate in defaultMode mode :beforeDate:
- – Using runUntilDate: The difference between running and start is that Runloop is stopped after the set time expires
- – With runMode:beforeDate: start, Runloop runs only once, the set time is reached or the first input source is processed, Runloop stops
Run:
The Runloop is constantly detecting the event source, CFRunloopSourceRef, executing the handler. First, it generates a notification. CoreFundation adds the RunlooppaX to the thread to monitor the event and control the execution and sleep of the thread in the Runloop. Let the nsRunloop-controlled thread handle the task when it is busy, and let the NSRunloop-controlled thread sleep when it is idle. Process TimerSource first, then Source0, then Source1.
Stop:
- 1. The thread ends
- 2, Exit because there is no event Source (Runloop only checks Source and Timer, closes if not, does not check Observer)
- 3. Manually terminate the Runloop
Runloop Boot mode
Start the way | Call the number | describe |
---|---|---|
run | Cycle call | Unconditional entry is the easiest, but least recommended. This causes the thread to go into an infinite loop, which is not conducive to controlling the runloop, and the only way to kill the runloop is to kill it. It is essentially infinite calls to the runMode:beforeDate: method. |
runUntilDate | Cycle call | If we set the timeout, the runloop will end after the event has been processed or after the timeout, at which point we can choose to restart the runloop. RunMode :beforeDate: is also called repeatedly, the difference being that it is not called again after it times out. This approach is superior to the previous one. |
runMode:beforeDate: | A single call | This is relatively the best way to do it, and we can specify which mode runloop should run in compared to the second startup mode. |
Enabling runloop with run results in memory leaks, where Thread objects cannot be freed.
What Runloop does:
- 1, keep the program running continuously (such as the main run loop) to receive user input
- 2. Handle various events in App (such as touch events, timer events, Selector events)
- 3. Task scheduling (the caller produces a lot of events, and takes other operations without waiting for the caller to complete the execution of the event)
- 4, save CPU resources, improve program performance: the work when the work, the rest of the rest
RunLoop handles logic
from
2, Runloop and thread relationship
The main thread has its Runloop created by default, and the child thread needs to create its own Runloop. (The main thread can have no Runloop or at most one Runloop.)
3, Runloop and NSTimer
- 1. The Timer Source repeatedly sends messages to the Runloop at a preset point in time (the interval specified when the Timer was created) to execute the task callback function.
- 2. The main thread has Runloop enabled by default so the timer can run normally, but the child thread needs to manually start the Runloop for the timer to run properly.
- 3. The default mode specified by Timer added to Runloop is NSUserDefaultRunloopMode. When UIScrollView is rolled, Runloop will automatically switch to UITrackingRunloopMode. In this case, the Timer cannot run properly. To run properly, you need to change the mode added to the Runloop to NSRunloopCommonMode
NSTimer is essentially a CFRunLoopTimerRef. It’s toll-free bridged between them. Once an NSTimer is registered with a RunLoop, the RunLoop registers events for its repeated time points. Such as 10:00, 10:10, 10:20. To save resources, RunLoop does not call back this Timer at a very precise point in time. The Timer has a property called Tolerance that indicates the maximum error allowed when the time point is up.
If a point in time is missed, such as when a long task is executed, the callback at that point will also be skipped without delay. Like waiting for a bus, if I’m busy with my phone at 10:10 and I miss the bus at that time, I’ll have to wait for the 10:20 bus.
When a timer is created, it is automatically added to the current thread in mode NSUerDefaultRunloopMode. So the following two effects are equivalent.
GCD timers are not affected by Runloop mode. GCD itself has a horizontal relationship with RunLoop. They don’t include each other, but there is a cooperative relationship between them. When dispatch_async(dispatch_get_main_queue(), block) is called, libDispatch sends a message to the main thread’s RunLoop, the RunLoop wakes up and takes the block from the message, This block is executed in the callback CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(). But this logic is limited to dispatches to the main thread, dispatches to other threads are still handled by libDispatch.
4,RunLoop application
4.1. Realize smooth scrolling delay loading pictures in TableView
Using the feature of CFRunLoopMode, the loading of pictures can be put into the mode of NSDefaultRunLoopMode, so that the mode of UITrackingRunLoopMode will not be affected by the loading. Reference: RunLoopWorkDistribution
4.2 Resolve that NSTime is invalid when ScrollView is rolled
If you use the scrollView for automatic advertising scrollbar, add the timer to the current runloop NSRunLoopCommonModes
4.3. UI is stuck
The first method uses child threads to monitor the runLoop of the main thread to determine whether the elapsed time between the two state regions has reached a certain threshold. ANREye sets the flag to YES in the child thread and then sets the flag to NO in the main thread. Using the threshold time of child thread, judge whether the flag bit is successfully set to NO. NSRunLoop calls the method mainly between kCFRunLoopBeforeSources and kCFRunLoopBeforeWaiting, and after kCFRunLoopAfterWaiting, if we find that the time between the two is too long, then we can Determine that the main thread is stuck at this time
The second method is FPS monitoring. The refresh rate of the App should be kept at 60fps. CADisplayLink records the interval between refresh times to calculate the current FPS. CADisplayLink is a timer with the same refresh rate as the screen (but the implementation is more complicated, unlike NSTimer, which actually operates on a Source). If a long task is executed between screen refreshes, one frame will be skipped (similar to NSTimer) and the interface will feel stuck. Even a frame stalling is noticeable to the user when swiping a TableView quickly.
4.4 Use idle time to cache data or do other performance optimization related tasks
You can add an Observer to listen for the status of the RunLoop. For example, listen for click event processing (do something before all click events)
4.5 child thread resident
Some operations, need to repeatedly open up the child thread, repeated open memory is too high performance, you can set the child thread resident
If the NSRunLoop of the child thread has no source or timer set, the NSRunLoop of the child thread will be closed immediately
1. Add Source
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port]forMode:NSDefaultRunLoopMode];
Copy the code
2. Add Timer
NSTimer * timer = [NSTimer timerWithTimeInterval: 5.0 target: self selector: @ the selector (test) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer [NSRunLoop currentRunLoop] addTimer:timerforMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
Copy the code
4.6 UI interface updateListen for when the UICollectionView finishes refreshing reloadData
When in operation the UI, such as changing the Frame, update the UIView/CALayer level, or manually invoked the UIView/CALayer > setNeedsLayout/setNeedsDisplay method, The UIView/CALayer is marked as pending and submitted to a global > container.
Apple registers an Observer to listen for BeforeWaiting(about to go to sleep) and Exit (about to Exit Loop) events, calling back to execute a > long function: _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (). This function iterates through all the >UIView/CAlayer to be processed to perform the actual drawing and adjustment, and update the UI.
If the main thread is busy with a lot of business logic, updating the UI at this point can be slow. The asynchronous drawing framework ASDK(Texture) was created to solve this problem. The basic principle is to put the UI layout and drawing operations in the background as much as possible, and the final UI update operation in the main thread. It also provides a set of related properties like UIView and CAlayer to ensure the developer’s development habits as much as possible. The main thread Runloop Observer listens for the pending sleep and exit states, and receives a callback to traverse the queue for each pending task to be executed.
4.7. Event response
Apple registered a Source1 (based on the Mach port) to the receiving system, the callback function as __IOHIDEventSystemClientQueueCallback ().
When a hardware event (touch/lock/shake, etc.) occurs, an IOHIDEvent event is first generated by IOKit. Framework and received by SpringBoard. SpringBoard only receives events such as buttons (lock screen/mute etc.), touch, acceleration and proximity sensor, and then forwards them to the App process using Mach port. Then apple registered the Source1 will trigger __IOHIDEventSystemClientQueueCallback () callback, and call the _UIApplicationHandleEventQueue () distribution within the application.
_UIApplicationHandleEventQueue () will wrap IOHIDEvent process, and as a UIEvent processing or distribution, including identifying UIGesture/processing screen rotation/send UIWindow, etc. Usually click event such as a UIButton, touchesBegin/Move/End/Cancel events are completed in the callback.
SpringBoard
What is?SpringBoard is actually a standard application that manages the IOS home screen, In addition to launching WindowSever,bootstrapping, and some of the system initialization Settings at startup are the responsibility of this particular application. It’s the first recipient of events in our IOS application. It can only accept a few events, such as: button (lock screen/mute, etc.), touch, acceleration, proximity sensor and other events, and then use MacPort to forward to the required App process.
4.8 gesture recognition
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).
Runloop and PerformSelecter
PerformSelecter: The after function relies on Timer Source and Runloop startup; Runloop relies on Source (not limited to Timer Source) and exits without Sources
Reference: a little discussion of performSelector
import UIKit
class RunloopVc: UIViewController {
@objc func performSeletor() {
debugPrint("performSeletor \(Thread.current)")
}
func case0() {result: 1 → 2 → 1.1 → performSeletor → 1.2"1")
DispatchQueue.global().async {
debugPrint(1.1 \ "(Thread. Current)"// The current child thread executes asynchronously, 1.1 → 1.2 → performSeletor //self.performSelector(inBackground: #selector(self.performSeletor), with: nil)// Perform self.perform(#selector(self.performSeletor))
debugPrint(1.2 \ "(Thread. Current)")
}
debugPrint("2")
}
func case1() {// Result: 1 → 2 → performSeletor (child thread asynchronous execution)"1")
self.performSelector(inBackground: #selector(performSeletor), with: nil)
debugPrint("2")
}
func case2() {// Result: 1 → 2 → performSeletor (main thread asynchronous execution)"1")
self.perform(#selector(performSeletor), afterDelay: 1)
debugPrint("2")
}
func case3() {// Result: 1 → 2 → performSeletor (main thread asynchronous execution)"1")
self.perform(#selector(performSeletor), with: nil, afterDelay: 1, inModes: [.default])
debugPrint("2")
}
func case4() {// Result: 1 → 2 → performSeletor does not execute debugPrint()"1")
DispatchQueue.global(qos: .default).async {
debugPrint(1.1 \ "(Thread. Current)")
self.perform(#selector(self.performSeletor), afterDelay: 1)
debugPrint(1.2 \ "(Thread. Current)")
}
debugPrint("2")
}
func case5() {// Result: 1 → 2 → 1.1 → 1.2 → 1.3"1")
DispatchQueue.global(qos: .default).async {
debugPrint(1.1 \ "(Thread. Current)") // Not found in RunloopsourceRunloop.current-run () debugPrint()1.2 \ "(Thread. Current)")
self.perform(#selector(self.performSeletor), afterDelay: 10)
debugPrint(1.3 \ "(Thread. Current)")
}
debugPrint("2")
}
func case6() {// Result: 1 → 2 → 1.1 → 1.2 → 1.3"1")
DispatchQueue.global(qos: .default).async {
debugPrint(1.1 \ "(Thread. Current)")
self.perform(#selector(self.performSeletor), afterDelay: 1)
debugPrint(1.2 \ "(Thread. Current)") // Perform after runloop.current.run () // 1.3 can be executed because the timer is invalid after execution, so it is not in runloopsource, so the thread exits the debugPrint(1.3 \ "(Thread. Current)")
}
debugPrint("2")
}
func case7() {// Result: 1 → 2 → 1.1 → performSeletor → 1.2 → 1.3"1")
DispatchQueue.global(qos: .default).async {
debugPrint(1.1 \ "(Thread. Current)")
self.perform(#selector(self.performSeletor),
on: Thread.current,
with: nil,
waitUntilDone: true)
debugPrint(1.2 \ "(Thread. Current)") runloop.current.run () // 1.3 This command is not executed because of the timersourceInvalid, Runloop ends debugPrint(1.3 \ "(Thread. Current)")
}
debugPrint("2")
}
func case8() {result: 1 → 2 → 1.1 → 1.2 → performSeletor → 1.3"1")
DispatchQueue.global(qos: .default).async {
debugPrint(1.1 \ "(Thread. Current)")
self.perform(#selector(self.performSeletor),
on: Thread.current,
with: nil,
waitUntilDone: false)
debugPrint(1.2 \ "(Thread. Current)") runloop.current.run () // 1.3 This command is not executed because of the timersourceRunloop does not end debugPrint(1.3 \ "(Thread. Current)")
}
debugPrint("2")
}
func case9() {// Result: 1 → 2 → 1.1 → 1.2 → 1.3"1")
DispatchQueue.global(qos: .default).async {
debugPrint(1.1 \ "(Thread. Current)")
self.perform(#selector(self.performSeletor),
on: Thread.current,
with: nil,
waitUntilDone: false)
debugPrint(1.2 \ "(Thread. Current)"Runloop runloop.current.run (until: nsDate.init (timeIntervalSince1970: NSDate.init().timeIntervalSince1970 + 1) as Date) debugPrint(1.3 \ "(Thread. Current)")
}
debugPrint("2")
}
override func viewDidLoad() {
super.viewDidLoad()
case(5)}}Copy the code
- – when calling
NSObject
的performSelecter:afterDelay:
After that, one is actually created internallyTimer Source
And add to the current threadRunLoop
In the. So if the current thread doesn’tRunLoop
, this method will be invalid. - – when calling
performSelector:onThread:
, it actually creates oneTimer Source
Add to the correspondingthreadGo, again, if the corresponding thread does notRunLoop
This method will also fail.
The performSelector methods for the event source are:
/ / main thread performSelectorOnMainThread: withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone: modes: / / / the specified thread performSelector: onThread: withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone: modes: / / / for the current thread performSelector: withObject: afterDelay: performSelector: withObject: afterDelay:inModes: / / / cancelled, in the current thread, and cancelPreviousPerformRequestsWithTarget corresponding to the above two methods: cancelPreviousPerformRequestsWithTarget:selector:object:Copy the code
PerformSelector methods that are not event sources are:
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
Copy the code
6, Runloop andAutoreleasePool
A thread can contain more than one AutoReleasePool, but an AutoReleasePool can only be added to print Runloop code for a unique thread: NSLog(@”[NSRunLoop currentRunLoop] = %@”, [NSRunLoop currentRunLoop]); The screenshot of the log is as follows:
App can be found after launch, apple registered in the main thread RunLoop two Observer, the callback callout 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.
Question: Child threads do not enable Runloop by default, so how to handle Autorelease objects? Will memory leak without manual processing?
Explanation:
When you create a Pool in the child thread, the resulting Autorelease object will be managed by the Pool. The autoreleaseNoPage method is called if you did not create a Pool but generated an Autorelease object. In this method, you will automatically create a HotPage for you. Call Page ->add(obj) to add the object to the stack of AutoreleasePoolPage. This means no manual memory management and no memory leaks. StackOverFlow authors also say that this feature was only added to OS X 10.9+ and iOS 7+. There is no official documentation for this, but you can read the source code. Post part of the source code here:
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// No pool inPlace. // hotPage can be understood as the AutoreleasePoolPage currently in use. assert(! hotPage()); // POOL_SENTINEL is just an alias for nilif(obj ! = POOL_SENTINEL && DebugMissingPools) { // We are pushing an object with no poolin place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
(void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
returnnil; } // Install the first page. // Create a hotpage for you AutoreleasePoolPage(nil);setHotPage(page);
// Push an autorelease pool boundary if it wasn't already requested. // POOL_SENTINEL is just an alias for nil, the sentinel object if (obj! = POOL_SENTINEL) { page->add(POOL_SENTINEL); Return page->add(obj); return page->add(obj); }Copy the code
Runloop and GCD
Runloop is not directly related to GCD. When dispatchqueue.main. async is called from the child thread to the main thread to refresh the UI, libDispatch sends a message to the main thread Runloop to wake up the Runloop. Runloop takes the Block from the message and performs the Block operation in the __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ callback. Dispatch-to-off-thread operations are all driven by libDispatch.
Refer to the blog
Run Loops official documentation
RunLoop in iOS development
5: Run Loop as I understand it
Understand RunLoop in depth
Delve into Runloops and thread survival
Decryption – Mysterious RunLoop
Video learning
RunLoop study notes
IOS Learning in depth understanding RunLoop
RunLoop and Timer and common Mode NSTimer need to pay attention to