The previous article covered the use of various locks and the use of read-write locks, and you will learn about them by the end of this article

  1. Usage and principles of DisplayLink and Timer
  2. Memory allocation and memory management
  3. Principle of automatic release pool
  4. Weak pointer principle and release time
  5. Principle of reference counting

DisplayLink

CADisplayLink adds a task to a runloop, which calls target’s selector each time it loops. This is also used to detect stalling. Let’s start with the API

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel; - (void)addToRunLoop:(NSRunLoop *)runloopforMode:(NSRunLoopMode)mode; - (void)removeFromRunLoop:(NSRunLoop *)runloopforMode:(NSRunLoopMode)mode; // Cancel - (void)invalidate;Copy the code

We are running in a VC that needs to be pushed to observe the declaration cycle

@property (nonatomic,strong) CADisplayLink *link; / / initialize the self. The link = [FYDisplayLink displayLinkWithTarget: self selector: @ the selector (test)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
	@synchronized (self) {
		NSLog(@"FPS:%d",fps); fps = 0; }}); dispatch_resume(timer); // dispatch_source_t timer; static int fps; - (void)test{
	
	@synchronized (self) {
		fps += 1;
	}
}
- (void)dealloc{
	[self.link invalidate];
	NSLog(@"%s",__func__); } / /log2019-07-30 17:44:37.217781+0800 DAY17 timer [29637:6504821] FPS:60 2019-07-30 17:44:38.21277 +0800 DAY17 timer [29637:6504821] FPS:60 2019-07-30 17:44:38.21277 +0800 DAY17 timer [29637:6504821] FPS:60 2019-07-30 17:44:39.706000+0800 DAY17 - timer [29637:6504821] FPS:89 2019-07-30 17:44:40.706064+0800 Day17-timer [29637:6504821] FPS:60 2019-07-30 17:44:41.705589+0800 DAY17-timer [29637:6504821] FPS:60 2019-07-30 17:44:42.706268+0800 DAY17 - timer [29637.6504821] FPS:60 2019-07-30 17:44:43.705942+0800 DAY17 - timer [29637.6504821] FPS:60 705792+0800 DAY17 - Timer [29637:6504821] FPS:60Copy the code

After initialization, use the simple version of the read/write lock on the FPS, you can see that the FPS is basically stable at around 60, after clicking the button back, link and VC are not destroyed normally. VC (self) ->link->target(self); VC (self) ->target(self) ->target(self)

@interface FYTimerTarget : NSObject
@property (nonatomic,weak) id target;
@end

@implementation FYTimerTarget
-(id)forwardingTargetForSelector:(SEL)aSelector{
	return self.target;
}
- (void)dealloc{
	NSLog(@"%s",__func__);
}
@end


FYProxy *proxy=[FYProxy proxyWithTarget:self];
self.link = [FYDisplayLink displayLinkWithTarget:self selector:@selector(test)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

- (void)test{
	NSLog(@"%s",__func__); } / /logThe 2019-07-30 17:59:04. 339934 - [ViewControllertest] the 17:59:04 2019-07-30. 356292 - [ViewControllertest[FYTimerTarget dealloc] 2019-07-30 [FYTimerTarget dealloc] 2019-07-30 [FYTimerTarget dealloc]Copy the code

FYTimerTarget weak references for target, the self to strong reference FYTimerTarget when destroyed, release the self first, then check that the self FYTimerTarget, FYTimerTarget only a weak property parameters, You can just release FYTimerTarget, and then release self(VC), and it works.

NSTimer

When I use NSTimer, timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo Repeats :(BOOL)yesOrNo strongly references the aTarget, so we do a simple wrapper for this aTarget

@interface FYProxy : NSProxy
@property (nonatomic,weak) id target;

+(instancetype)proxyWithTarget:(id)target;
@end
@implementation FYProxy
- (void)dealloc{
	NSLog(@"%s",__func__);
}
+ (instancetype)proxyWithTarget:(id)target{
	FYProxy *obj=[FYProxy alloc];
	obj.target = target;
	returnobj; } / / forwarding - (void) forwardInvocation: (NSInvocation *) invocation {[invocation invokeWithTarget: self. The target]; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{return [self.target methodSignatureForSelector:sel];
}
@end
Copy the code

FYProxy inherits from NSProxy, and NSProxy doesn’t inherit from NSObject, but is another base class that doesn’t follow the three steps of objc_msgSend(), – (void)forwardInvocation:(NSInvocation *)invocation, And – (NSMethodSignature *) methodSignatureForSelector (SEL) SEL directly into forward phase. Or change the inheritance to FYTimerTarget: NSObject, so that functions that target can’t find still go through the three steps of message forwarding. FYTimerTarget adds dynamic message parsing

-(id)forwardingTargetForSelector:(SEL)aSelector{
	return self.target;
}
Copy the code

In this way, Target’s aSelector is forwarded to the self.target handler, successfully weakly referencing self and the function’s forward handler.

FYTimerTarget *obj =[FYTimerTarget new]; obj.target = self; The self. The timer = [NSTimer timerWithTimeInterval: 1.0 f target: obj selector: @ the selector (test)
								   userInfo:nil
									repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
[self.timer setFireDate:[NSDate distantPast]];

//log2019-07-30 18:03:08.723433+0800 DAY17 - Timer [30877:6556631] -[ViewControllertest[2019-07-30 18:03:09.722611+0800 day17- timer [30877:6556631] -[ViewControllertest[2019-07-30 18:03:09.847540+0800 DAY17 timer [30877:6556631] -[FYTimerTarget dealloc] 2019-07-30 18:03:09.847677+0800 Day17 - Timer [30877:6556631] -[ViewController dealloc]Copy the code

Or use timerWithTimeInterval (NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block, The function is then called externally using __weak self, which also produces no circular reference. With blocks, the release is normal.

self.timer=[NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
	NSLog(@"123");
}];

//log[31126:6566530] 2019-07-30 18:08:25.659127+0800 DAY17 timer [31126:6566530] 120 2019-07-30 18:08:26.107643+0800 day17- Timer [31126:6566530] -[ViewController dealloc]Copy the code

Because link and timer are added to the runloop, each loop calls the timer or Link function and executes the corresponding function, with relatively little error in time. Each loop needs to refresh the UI(on the main thread), perform other functions, handle system port events, and do other calculations… In general, there are errors.

The timer in the GCD

The dispatch_source_t timer in GCD is kernel based with relatively little time error.

Timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC); // Set dispatch_source_set_event_handler(timer, ^{//code code for timer execution}); // Start timer dispatch_resume(timer);Copy the code

Or use the function dispatch_source_set_event_handler_f(timer, function_t);

dispatch_source_set_event_handler_f(timer, function_t);
void function_t(void * p){
    //code here    
}
Copy the code

Business often use timer, or encapsulate a simple function is better, encapsulation first from the requirements of the analysis, we use timer commonly used parameters are what? What features are needed?

First you need the time to start, then the frequency of execution, which task (function or block) is executed, and whether it is repeated, these are all required. Let’s define a function

+ (NSString *)exeTask:(dispatch_block_t)block start:(NSTimeInterval)time interval:(NSTimeInterval)interval repeat:(BOOL)repeat async:(BOOL)async; + (NSString *)exeTask:(id)target sel:(SEL)aciton start:(NSTimeInterval)time interval:(NSTimeInterval)interval repeat:(BOOL)repeat async:(BOOL)async; // cancel + (void)exeCancelTask:(NSString *)key;Copy the code

And then I take what I just wrote and I add some judgment. It only executes when there’s a task, otherwise it returns nil, and when it loops, it has to be more than zero, otherwise it returns synchronous or asynchronous, either the main queue or the asynchronous queue, and then it stores it in the global variable with the generated key,timer is value, and when it cancellations it just fetches the timer with the key and cancellations, Semaphores are used here to limit single-threaded operations. Limit store and fetch (cancel timer) time to improve the efficiency of other code execution.

+ (NSString *)exeTask:(dispatch_block_t)block start:(NSTimeInterval)time interval:(NSTimeInterval)interval repeat:(BOOL)repeat async:(BOOL)async{
	if (block == nil) {
		return nil;
	}
	if (repeat && interval <= 0) {
		return nil;
	}
	
	NSString *name =[NSString stringWithFormat:@"%d",i]; // dispatch_queue_t queue = dispatch_get_main_queue();if (async) {
		queue = dispatch_queue_create("async.com", DISPATCH_QUEUE_CONCURRENT); } dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); Dispatch_source_set_timer (_timer, dispatch_time(DISPATCH_TIME_NOW, time*NSEC_PER_SEC), interval*NSEC_PER_SEC, 0); // dispatch_source_set_event_handler(_timer, ^{block();if(repeat == NO) { dispatch_source_cancel(_timer); }}); // Start timer dispatch_resume(_timer); // Store it in the dictionaryif (name.length && _timer) {
		dispatch_semaphore_wait(samephore, DISPATCH_TIME_FOREVER);
		timers[name] = _timer;
		dispatch_semaphore_signal(samephore);
	}
	return name;
}



+ (NSString *)exeTask:(id)target
				  sel:(SEL)aciton
				start:(NSTimeInterval)time
			 interval:(NSTimeInterval)interval
			   repeat:(BOOL)repeat
				async:(BOOL)async{
	if (target == nil || aciton == NULL) {
		return nil;
	}
	if (repeat && interval <= 0) {
		return nil;
	}
	
	NSString *name =[NSString stringWithFormat:@"%d",i]; // dispatch_queue_t queue = dispatch_get_main_queue();if (async) {
		queue = dispatch_queue_create("async.com", DISPATCH_QUEUE_CONCURRENT); } dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); Dispatch_source_set_timer (_timer, dispatch_time(DISPATCH_TIME_NOW, time*NSEC_PER_SEC), interval*NSEC_PER_SEC, 0); // dispatch_source_set_event_handler(_timer, ^{#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Warc-performSelector-leaks"// Here is the warning codeif ([target respondsToSelector:aciton]) {
			[target performSelector:aciton];
		}
#pragma clang diagnostic pop

		if(repeat == NO) { dispatch_source_cancel(_timer); }}); // Start timer dispatch_resume(_timer); // Store it in the dictionaryif (name.length && _timer) {
		dispatch_semaphore_wait(samephore, DISPATCH_TIME_FOREVER);
		timers[name] = _timer;
		dispatch_semaphore_signal(samephore);
	}
	return name;
}
+ (void)exeCancelTask:(NSString *)key{
	if (key.length == 0) {
		return;
	}
	dispatch_semaphore_wait(samephore, DISPATCH_TIME_FOREVER);
	if ([timers.allKeys containsObject:key]) {
		dispatch_source_cancel(timers[key]);
		[timers removeObjectForKey:key];
	}
	dispatch_semaphore_signal(samephore);
}
Copy the code

It’s easy to use

key = [FYTimer exeTask:^{
        NSLog(@"123");
    } start:1
    interval:1 
    repeat:YES 
    async:NO];
Copy the code

or

key = [FYTimer exeTask:self sel:@selector(test) start:0 interval:1 repeat:YES async:YES];
Copy the code

Cancel execution

[FYTimer exeCancelTask:key];
Copy the code

Test the encapsulated timer

- (void)viewDidLoad {
	[super viewDidLoad];
	key = [FYTimer exeTask:self sel:@selector(test) start:0 interval:1 repeat:YES async:YES];
}
-(void)test{
	NSLog(@"% @",[NSThread currentThread]); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [FYTimer exeCancelTask:key]; } / /log2019-07-30 21:16:48.639486+0800 DAY17 - Timer 2[48817:1300897] <NSThread: 0x6000010EC000 >{number = 4, name = (null)} 2019-07-30 21:16:49.640177+0800 DAY17 - Timer 2[48817:1300897] <NSThread: 0x6000010EC000 >{number = 4, name = (NULL)} 2019-07-30 21:16:50.639668+0800 DAY17 - Timer 2[48817:1300897] <NSThread: 0x6000010EC000 >{number = 4, name = (null)} 2019-07-30 21:16:51.639590+0800 DAY17 - Timer 2[48817:1300897] <NSThread: 0x6000010ec000>{number = 4, Name = (NULL)} 2019-07-30 21:16:52.156004+0800 DAY17 - Timer 2[48817:1300845] -[ViewController touchesBegan:withEvent:]Copy the code

When clicking VC, cancel the operation and timer stops.

NSProxy of actual combat

NSProxy is actually a base class in addition to NSObject, with fewer methods, a message forwarding phase when no method is found (because there is no parent class), a shorter process for calling functions, and better performance.

Q: What are RET1 and RET2 respectively?

ViewController *vc1 =[[ViewController alloc]init];
FYProxy *pro1 =[FYProxy proxyWithTarget:vc1];

FYTimerTarget *tar =[FYTimerTarget proxyWithTarget:vc1];
BOOL ret1 = [pro1 isKindOfClass:ViewController.class];
BOOL ret2 = [tar isKindOfClass:ViewController.class];
NSLog(@"%d %d",ret1,ret2);
Copy the code

-(bool)isKindOfClass:(CLS) object function is used to determine whether the object is a subclass or instance of CLS, so ret1 should be 0 and ret2 should be 0

First see FYProxy implementation, forwardInvocation and methodSignatureForSelector, in the absence of the function for message forwarding, forward object is the self. The target, IsKindOfClass does not exist in the example and FYProxy, forward so tell the function to the VC, the BOOL ret1 = [pro1 isKindOfClass: ViewController. Class]; Equivalent to a BOOL ret1 = [ViewController. Class isKindOfClass: ViewController. Class]; So the answer is 1

And then ret2 is 0, tar inherits from NSObject, and it has -(bool)isKindOfClass:(CLS), so the answer is 0.

The answer is: REt1 is 1, ret2 is 0.

Memory allocation

Memory is divided into reserved segment, data segment, heap (↓), stack (↑), and kernel area.

The data segment includes

  • String constants: NSString * STR = @”11″
  • Initialized data: initialized global variables, static variables, etc
  • Uninitialized data: uninitialized global variables, static variables, etc

Stack: Function call overhead, such as local variables, allocates smaller and smaller memory space addresses.

Heap: Dynamically allocated space by alloc, malloc, calloc, etc. The allocated space addresses become larger and larger.

Validation:

int a = 10;
int b ;
int main(int argc, char * argv[]) {
    @autoreleasepool {
        static int c = 20;
        static int d;
        int e = 10;
        int f;
        NSString * str = @"123";
        NSObject *obj =[[NSObject alloc]init];
        NSLog(@"\na:%p \nb:%p \nc:%p \nd:%p \ne:%p \nf:%p \nobj:%p\n str:%p",&a,&b,&c,&d,&e,&f,obj,str);
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); / /}}log

a:0x1063e0d98 
b:0x1063e0e64 
c:0x1063e0d9c 
d:0x1063e0e60 
e:0x7ffee9820efc 
f:0x7ffee9820ef8 
obj:0x6000013541a0
str:0x1063e0068
Copy the code

Tagged Pointer

Starting from 64bit, iOS introduces Tagged Pointer technology to optimize storage of small objects such as NSNumber, NSDate, and NSString. Before they are not used, they need to dynamically allocate memory and maintain counts. After using Tagged Pointer, Objc_msgSend () is able to recognize Tagged Pointer. If the Pointer is not enough to store the Data, the Pointer will dynamically allocate memory to store the Data. The intValue method of NSNumber, for example, extracts the data directly from the pointer, saving the overhead of previous calls. On iOS, the highest bit is 1(64bit), and on Mac, the lowest significant bit is 1. Line objc-internal.h 370 in the Runtime source code determines whether optimization techniques are used

static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
Copy the code

We use this to determine whether the object is using optimization techniques.

NSNumbe Tagged Pointer

We use several NSNumber size numbers to verify

# if (TARGET_OS_OSX | | TARGET_OS_IOSMAC) && __x86_64__ / / MAC development
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1//iOS development
#endif

#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
bool objc_isTaggedPointer(const void * _Nullable ptr)
{
    return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSNumber *n1 = @2;
        NSNumber *n2 = @3;
        NSNumber *n3 = @(4);
        NSNumber *n4 = @(0x4fffffffff);
        NSLog(@"\n%p \n%p \n%p \n%p",n1,n2,n3,n4);
        BOOL n1_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n1));
        BOOL n2_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n2));
        BOOL n3_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n3));
        BOOL n4_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n4));

        NSLog(@"\nn1:%d \nn2:%d \nn3:%d \nn4:%d ",n1_tag,n2_tag,n3_tag,n4_tag);
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); / /}}log

0xbf4071e2657ccb95 
0xbf4071e2657ccb85 
0xbf4071e2657ccbf5 
0xbf40751d9a833444
2019-07-30 21:55:52.626317+0800 day17-TaggedPointer[49770:1328036] 
n1:1 
n2:1 
n3:1 
n4:0
Copy the code

It can be seen that N1, n2, and N3 are optimized, while N4 is a large number, which cannot be optimized because the pointer cannot accommodate it.

NSString Tagged Pointer

What happens when I run test1 and test2?

- (void)test1{
	dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
	for (NSInteger i = 0; i < 1000; i ++) {
		dispatch_async(queue, ^{
			self.name = [NSString stringWithFormat:@"abc"];
		});
	}
}
- (void)test2{
	dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
	for (NSInteger i = 0; i < 1000; i ++) {
		dispatch_async(queue, ^{
			self.name = [NSString stringWithFormat:@"abcsefafaefafafaefe"]; }); }}Copy the code

Let’s not run it. Let’s analyze it.

First global queue asynchronous multi-threaded concurrent problems will add tasks, for write operation will appear when concurrent resource competition, also can appear a little string pointer optimization problem, small strings and string switch _name structure change, writing and reading can lead to multiple threads access bad memory problems, we have to run it

Thread: EXC_BAD_ACCESS(code = 1)
Copy the code

It crashes directly on the child thread, and the crash function is objc_release. It fits our guess.

Verify NSString Tagged Pointer

- (void)test{
	dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
	for (NSInteger i = 0; i < 1; i ++) {
		dispatch_async(queue, ^{
			self.name = [NSString stringWithFormat:@"abc"];
			NSLog(@"test1 class:%@",self.name.class);
		});
	}
}
- (void)test2{
	dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
	for (NSInteger i = 0; i < 1; i ++) {
		dispatch_async(queue, ^{
			self.name = [NSString stringWithFormat:@"abcsefafaefafafaefe"];
			NSLog(@"test2 class:%@",self.name.class); }); / /}}log
test1 class:NSTaggedPointerString
test2 class:__NSCFString
Copy the code

NSString TaggedPointer class NSTaggedPointerString for small strings, optimized class, __NSCFString for large strings,

copy

In the shallow copy, the reference count is +1. In the deep copy, an object is copied and the reference count is not affected.

Copy object: generates a copy object, which does not affect the source object

IOS provides two copy methods

  1. Copy Copy immutable objects
  2. MutableCopy copies out mutable objects
void test1(){
	NSString *str = @"strstrstrstr";
	NSMutableString *mut1 =[str mutableCopy];
	[mut1 appendFormat:@"123"];
	NSString *str2 = [str copy];
	NSLog(@"%p %p %p",str,mut1,str2); } / /log
str:0x100001040 
mut1:0x1007385f0 
str2:0x100001040
Copy the code

As you can see, STR and STR2 address are not copied again. Mut1 address is not the same as STR, so it is a deep copy.

Let’s replace the string with some other common array

void test2(){
	NSArray *array = @[@"123"The @"123"The @"123"The @"123"The @"123"The @"123"The @"123"];
	NSMutableArray *mut =[array mutableCopy];
	NSString *array2 = [array copy];
	NSLog(@"\n%p \n%p\n%p",array,mut,array2); } / /log
0x102840800 
0x1028408a0
0x102840800

void test3(){
	NSArray *array = [@[@"123"The @"123"The @"123"The @"123"The @"123"The @"123"The @"123"] mutableCopy];
	NSMutableArray *mut =[array mutableCopy];
	NSString *array2 = [array copy];
	NSLog(@"\n%p \n%p\n%p",array,mut,array2); } / /log
0x102808720 
0x1028088a0
0x1028089a0
Copy the code

So if you copy an immutable array out of an immutable array, you don’t change the address, if you copy a mutable array out of an immutable array, you change the address.

Let’s switch to some other dictionary that we use

void test4(){
	NSDictionary *item = @{@"key": @"value"};
	NSMutableDictionary *mut =[item mutableCopy];
	NSDictionary *item2 = [item copy];
	NSLog(@"\n%p \n%p\n%p",item,mut,item2); } / /log
0x1007789c0 
0x100779190
0x1007789c0

void test5(){
	NSDictionary *item = [@{@"key": @"value"}mutableCopy];
	NSMutableDictionary *mut =[item mutableCopy];
	NSDictionary *item2 = [item copy];
	NSLog(@"\n%p \n%p\n%p",item,mut,item2); } / /log

0x1007041d0 
0x1007042b0
0x1007043a0
Copy the code

So if you copy an immutable dictionary and you copy an immutable dictionary and you copy a mutable dictionary, your address will change.

Seen from these, the following table is summarized

type copy mutableCopy
NSString Shallow copy Deep copy
NSMutableString Shallow copy Deep copy
NSArray Shallow copy Deep copy
NSMutableArray Deep copy Deep copy
NSDictionary Shallow copy Deep copy
NSMutableDictionary Deep copy Deep copy

Custom object implementation protocol NSCoping

How about using copy for custom objects? The system has been implemented, we need to customize their own implementation, custom class inheritance NSCopying

@protocol NSCopying

- (id)copyWithZone:(nullable NSZone *)zone;

@end

@protocol NSMutableCopying

- (id)mutableCopyWithZone:(nullable NSZone *)zone;

@end

Copy the code

NSCopying and NSMutableCopying are two protocols that are not useful for custom mutable objects. The properties of a defined object are basically mutable, so just implement NSCopying.

@interface FYPerson : NSObject @property (nonatomic,assign) int age; @property (nonatomic,assign) int level; @end @interface FYPerson()<NSCopying> @end @implementation FYPerson -(instancetype)copyWithZone:(NSZone *)zone{ FYPerson  *p=[[FYPerson alloc]init]; p.age = self.age; p.level = self.level;return p;
}

@end


FYPerson *p =[[FYPerson alloc]init];
p.age = 10;
p.level = 11;
FYPerson *p2 =[p copy];
NSLog(@"%d %d",p2.age,p2.level);
//log10 of 11Copy the code

I implemented NSCoping protocol to complete the deep copy of the object, and successfully copied the properties of the object. What should I do when there are too many properties? We can use Runtime to implement a once and for all solution.

And then copyWithZone uses runtime to go through all the member variables, assign all the variables, and when there are a lot of variables, you don’t have to change it here.

@implementation NSObject (add) -(instancetype)copyWithZone:(NSZone *)zone{ Class cls = [self class]; NSObject * p=[cls new]; // Number of member variables unsigned int count; Ivar *ivars = class_copyIvarList(self.class, &count); // go through the number groupfor(int i = 0; i < count; i ++) { Ivar var = ivars[i]; Const char * name = ivar_getName(var);if(name ! = nil) { NSString *v = [NSString stringWithUTF8String:name]; id value = [self valueForKey:v]; // Assign a value to the new objectif(value ! = NULL) { [psetValue:value forKey:v];
            }
        }
    }
    free(ivars);
    return p;
}
@end

FYPerson *p =[[FYPerson alloc]init];
p.age = 10;
p.level = 11;
p.name = @"xiaowang";
FYPerson *p2 =[p copy];
NSLog(@"%d %d %@",p2.age,p2.level,p2.name);
		
//log
10 
11 
xiaowang
Copy the code

Depending on the startup order, the methods of the class are loaded after the methods of the class, and the methods in the class override the methods of the class, so the base class NSObject is overwritten in the class -(instancetype)copyWithZone:(NSZone *)zone method, so subclasses don’t have to be overwritten. A permanent solution was reached.

Principle of reference counting

From Baidu Baike

Reference counting is a memory management technique in computer programming languages. It is the process of storing the number of references to a resource (object, memory, disk space, etc.) and releasing it when the number of references reaches zero. Automatic resource management can be achieved using reference counting techniques. Reference counting can also refer to garbage collection algorithms that use reference counting techniques to reclaim unused resources

In iOS, reference counting is used to manage OC object memory. The default reference count for a newly created OC object is 1. When the reference count is reduced to 0, the OC object is destroyed, freeing up other memory space. When you call alloc, new, copy, or mutableCopy to return an object, you call release or AutoRelease torelease it when it’s no longer needed. If you want to own an object, you give it a reference count of +1. If you don’t have an object anymore, you give it a reference count of -1.

This is how we use it all the time in MRC

FYPerson *p=[[FYPerson alloc]init];
FYPerson *p2 =[p retain];
//code here
[p release];
[p2 release];
Copy the code

In ARC, however, the system does the automatic reference counting for us, so instead of having to do a lot of tedious things for developers, let’s explore how reference counting is implemented.

The reference count is stored in the EXTRA_rc in the ISA pointer, bits. Has_sidetable_rc =1 and the remaining RetainCount is stored in the global table with the key of self.

Objc_object :: Retain()->objc_object::rootRetain()->objc_object::rootRetain(bool, bool)

// high probability x==1#define fastpath(x) (__builtin_expect(bool(x), 1))// high probability x==0#define slowpath(x) (__builtin_expect(bool(x), 0))// Reference count +1 //tryRetain attempt +1 //handleOverflow whether to override ALWAYS_INLINE ID objC_Object ::rootRetain(bool tryRetain, Bool handleOverflow) {// The optimized pointer returns thisif (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false; //old bits oldisa = LoadExclusive(&isa.bits); newisa = oldisa; // Use the consortium techniqueif(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits); //nothingif(! tryRetain && sideTableLocked) sidetable_unlock(); / / unlockif (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
			else returnsidetable_retain(); ////sidetable reference count +1} // don't check newisa.fast_rr; We already called any RR overrides // Do not attempt retain and are destroying nothing return nil if (slowpath(tryRetain && newisa.deallocating) { ClearExclusive(&isa.bits); if (! tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; // reference count +1 (bits.extra_rc++;) newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); Extra_rc ++ if (slowPath (carry)) {newisa.extra_rc++ if (! handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Prepare for copying to side table if (! tryRetain && ! sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); If (slowpath(transcribeToSideTable)) {// copy half of the reference count to sidetable sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(! tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } // sideTable reference count +1 ID objC_object ::sidetable_retain() {#if SUPPORT_NONPOINTER_ISA assert(! isa.nonpointer); Table key=this SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; }Copy the code

Add the value of extra_rc to the sidetable and set the parameter ISA -> has_sideTABLE_rc =true.

Reference count -1, Objc_object ::release()->objc_object::rootRelease()->objc_object::rootRelease(bool performDealloc, Bool handleUnderflow), we go inside the function

ALWAYS_INLINE bool  objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false; // Pointer optimised non-existence counter bool sideTableLocked =false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {//isa
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if(slowpath(! newisa.nonpointer)) { ClearExclusive(&isa.bits);if (sideTableLocked) sidetable_unlock();
			//side table -1
            return sidetable_release(performDealloc);
        }
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        if (slowpath(carry)) {
            // don't ClearExclusive() goto underflow; } } while (slowpath(! StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: newisa = oldisa; if (slowpath(newisa.has_sidetable_rc)) { if (! handleUnderflow) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } if (! sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; goto retry; } // Side table reference count -1 size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); if (borrowed > 0) { newisa.extra_rc = borrowed - 1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (! stored) { isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; if (newisa2.nonpointer) { uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); if (! overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); } } } if (! stored) { // Inline update failed. // Put the retains back in the side table. sidetable_addExtraRC_nolock(borrowed); goto retry; } sidetable_unlock(); return false; } else {// Side table is empty after all. fall-through to the dealloc path.} (slowpath(newisa.deallocating)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); // Does not actually return} // Setting newisa.dealLocating = true; if (! StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __sync_synchronize(); If (performDealloc) {// Destroy ((void(*)(objc_object, SEL))objc_msgSend)(this, SEL_dealloc); } return true; }Copy the code

Seeing that reference counting has two parts, extra_RC and side table, explore the implementation of rootRetainCount()

inline uintptr_t  objc_object::rootRetainCount() {// The optimization pointer returns directlyif (isTaggedPointer()) return(uintptr_t)this; Sidetable_lock (); //isa pointer isa_t bits = LoadExclusive(& ISa.bits); ClearExclusive(&isa.bits); // Do nothingif(bits.nonpointer) {// Use union to store more data uintptr_t rc = 1 + bits.extra_rc; // Count the quantityifRc += sidetable_getExtraRC_nolock(); rc += sidetable_getExtraRC_nolock(); } sidetable_unlock();returnrc; } sidetable_unlock(); // The count stored in sideTablereturn sidetable_retainCount();
}
Copy the code

For large data, the table will be locked. After class optimization, more data will be stored using union. If class is not optimized, the data will be read directly by sizedable. Optimised to read data in sidetable_getExtraRC_nolock()

// Use the commonwealth size_t objc_object::sidetable_getExtraRC_nolockAssert (ISa.nonpointer) {// Assert (ISa.nonpointer); Table = SideTables()[this]; RefcountMap::iterator it = table.refcnt.find (this);if (it == table.refcnts.end()) return 0;
    else return it->second >> SIDE_TABLE_RC_SHIFT;
}
Copy the code

What is not optimized is direct reading

// Uintptr_t objc_object::sidetable_retainCountSideTable& table = SideTables()[this]; size_t refcnt_result = 1; table.lock(); RefcountMap::iterator it = table.refcnts.find(this);if(it ! = table.refcnts.end()) { refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT; } table.unlock();return refcnt_result;
}
Copy the code

Weak pointer principle

Dealloc is called when an object is to be destroyed. The call path is dealloc->_objc_rootDealloc-> object_Dispose ->objc_destructInstance->free We go inside objc_destructInstance

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for//c++ destructor bool CXX = obj->hasCxxDtor(); Bool assoc = obj->hasAssociatedObjects(); // This order is important.if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }
    return obj;
}
Copy the code

After destroying the c++ destructor and correlation function and finally going to clearDeallocating, we’re inside the function

// Removing Side table and Weakly referenced Inline void objc_object::clearDeallocating()
{
    if(slowpath(! isa.nonpointer)) { // Slow pathforRaw pointer isa. // Release weak sidetable_clearDeallocating(); }else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path forClearDeallocating_slow (); non-pointer isa with weak refs and/or side table data. } assert(! sidetable_present()); }Copy the code

Finally, sidetable_clearDeallocating and clearDeallocating_slow are called to destroy weak and reference-counting sidetable.

NEVER_INLINE void
objc_object::clearDeallocating_slow() { assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc)); SideTable& table = SideTables()[this]; table.lock(); / / remove the weakif(ISa.weakly_referenced) {//table. Weak_table weak_no_lock (&table. Weak_table, (id)this); } // Reference countif(isa.has_sidetable_rc) {// Erase this table.refcnts. Erase (this); } table.unlock(); }Copy the code

The weak object is stored in the global SideTable, and is searched for in the SideTable when the object is destroyed. If the weak object exists, it is destroyed.

Autoreleasepool principle

Autoreleasepool – a pool containing variables that release objects (reference count -1) when the pool is no longer needed. Let’s convert the following code to c++

@autoreleasepool {
		FYPerson *p = [[FYPerson alloc]init];
	}
Copy the code

Convert to c++ using xcrun-sdk iphoneos clang-arch arm64-rewrite-objc-f main.m

 /* @autoreleasepool */ {
  __AtAutoreleasePool __autoreleasepool;
  FYPerson *p = ((FYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((FYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FYPerson"), sel_registerName("alloc")), sel_registerName("init"));

 }
Copy the code

__AtAutoreleasePool is a structure

struct __AtAutoreleasePool {
	__AtAutoreleasePool() {// The constructor calls atAutoReleasepoolobj = objc_autoreleasePoolPush(); } ~__AtAutoreleasePool() {// Call objc_autoreleasePoolPop(atAutoReleasepoolobj) when destructor is destroyed; } void * atautoreleasepoolobj; };Copy the code

And then you integrate the above code with c++ and that’s what it looks like

{
    __AtAutoreleasePool pool = objc_autoreleasePoolPush();
    FYPerson *p = [[FYPerson alloc]init];
    objc_autoreleasePoolPop(pool)
}
Copy the code

After entering curly braces to generate a release pool and leaving curly braces to release the release pool, let’s take a look at how the release function works

void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}
Copy the code

Pop implements the release of objects in AutoreleasePoolPage. See runtime nsobject.mm on line 1063.

AutoreleasePool is managed by AutoreleasePoolPage. The AutoreleasePoolPage structure is as follows

class AutoreleasePoolPage { magic_t const magic; id *next; Pthread_t const thread; // thread AutoreleasePoolPage * const parent; // AutoreleasePoolPage *child; // Uint32_t const depth; // Deep uint32_t hiwat; }Copy the code

AutoreleasePoolPage applies 4096 bytes in addition to its own variable space in the autoreleaseNewPage after initialization. AutoreleasePoolPage is a C++ implementation of the class

  • Internal useid *nextIt points to the top of the stackaddCome inautoreleaseObject at the next location
  • aAutoreleasePoolPageWhen the space is full, a new one is createdAutoreleasePoolPageObject, linked list, laterautoreleaseObject in the newpagejoin
  • AutoreleasePoolPageEach object allocates 4096 bytes of memory (the size of a page of virtual memory), except for the space occupied by the instance variables above, and the rest is used for storageautoreleaseObject address
  • AutoreleasePoolIs thread-by-thread correspondence (in the structurethreadPointer to current thread)
  • AutoreleasePoolThere is no single structure, there are severalAutoreleasePoolPageCombined in the form of a bidirectional linked list (respectively corresponding to the structureparentPointers andchildPointer)

All other Pointers are Pointers to other objects in the auto-release pool. We can use _objc_autoreleasePoolPrint() to view the contents of the auto-release pool

extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
	@autoreleasepool {//r1 = push()

		FYPerson *p = [[FYPerson alloc]init];
		_objc_autoreleasePoolPrint();
		printf("\n--------------\n");
	}//pop(r1)
	return0; } / /log

objc[23958]: # # # # # # # # # # # # # #
objc[23958]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[23958]: 3 releases pending.
objc[23958]: [0x101000000]  ................  PAGE  (hot) (cold)
objc[23958]: [0x101000038]  ################ POOL 0x101000038
objc[23958]: [0x101000040]       0x10050cfa0  FYPerson
objc[23958]: [0x101000048]       0x10050cdb0  FYPerson
objc[23958]: # # # # # # # # # # # # # #

--------------
Copy the code

You can see that it stores 3 Releases Pending, and it’s all 8 bytes in size. Let’s look at another complex, auto-release pool nested auto-release pool

int main(int argc, const char * argv[]) {
	@autoreleasepool {//r1 = push()

		FYPerson *p = [[[FYPerson alloc]init] autorelease];
		FYPerson *p2 = [[[FYPerson alloc]init] autorelease];
		@autoreleasepool {//r1 = push()
			
			FYPerson *p3 = [[[FYPerson alloc]init] autorelease];
			FYPerson *p4 = [[[FYPerson alloc]init] autorelease];
			
			_objc_autoreleasePoolPrint();
			printf("\n--------------\n");
		}//pop(r1)
	}//pop(r1)
	return0; } / /log
objc[24025]: # # # # # # # # # # # # # #
objc[24025]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[24025]: 6 releases pending.
objc[24025]: [0x100803000]  ................  PAGE  (hot) (cold)
objc[24025]: [0x100803038]  ################ POOL 0x100803038
objc[24025]: [0x100803040]       0x100721580  FYPerson
objc[24025]: [0x100803048]       0x100721b10  FYPerson
objc[24025]: [0x100803050]  ################ POOL 0x100803050
objc[24025]: [0x100803058]       0x100721390  FYPerson
objc[24025]: [0x100803060]       0x100717620  FYPerson
objc[24025]: # # # # # # # # # # # # # #
Copy the code

We see two pools and four FYPerson objects for a total of six objects that will be released when we get out of the release POOL.

When there is no optimization of pointer call autorelease is invoked the AutoreleasePoolPage: : autorelease (this) (id) – > autoreleaseFast (obj)

static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); // Add pages when there are pages and pages are not fullif(page && ! page->full()) {return page->add(obj);
        } else if(page) {// Create a new page to add obj and set hotPagereturn autoreleaseFullPage(obj, page);
        } else{// Create a new page to add if there is no pagereturnautoreleaseNoPage(obj); }}Copy the code

In MRC autorealEase modifiers that objects are destroyed during runloop loops when no external addition is made to the automatic release pool

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 }; // The system listens for the kCFRunLoopBeforeWaiting and kCFRunLoopExit of the mainRunloop to update the autoRelease / / callback function is _wrapRunLoopWithAutoreleasePoolHandler"
      
       {valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10f94087d), context = 
       
        {type = mutable-small, count = 1, values = (\n\t0 : <0x7fb6dc004058>\n)}}"
       
      
Copy the code

The system listens for the kCFRunLoopBeforeWaiting and kCFRunLoopExit states of mainRunloop to update the data callback function of autoRelease _wrapRunLoopWithAutoreleasePoolHandler.

void test(){
    FYPerson *p =[[FYPerson alloc]init];
}
Copy the code

The p object is pushed in a loop and pops into kCFRunLoopBeforeWaiting once, so the autolease object of the last loop has no other retained object to release. It is not released immediately after test().

In ARC, test() is released immediately after execution.

conclusion

  • Automatic release pooling is a good choice when objects are repeatedly created or when the code snippet lifecycle is not easily managed.
  • Exists globallySideTableThe weak object in thedeallocThe object is detected or destroyed during execution of the function.
  • Copy of immutable objects always generates new objects. Copy of immutable objects to immutable objects is reference count +1.
  • Optimized pointer to object, no need to walkobjc_msgSend()To improve performance.
  • CADisplayLinkandTimerThe essence is to add PI to PIloopIn the loop, attached to the loop, norunloop, cannot be executed correctlyrunloopNote circular references andrunloopA release problem for the thread in which the.

The resources

  • Autorelease behind the black screen
  • Little brother video
  • IOS and OS multithreading with memory management
  • IOS and macOS performance optimization

Data download

  • Download Git
  • demo code git
  • Runtime runtime runtime source git

The most afraid of a mediocre life, but also comfort their ordinary valuable.

AD time