What is self-release? An object automatically clears and reclaims its related resources at the end of its life cycle. This cleanup includes not only object memory reclamation, but also object decoupling and cleanup of ancillary events, such as timer stopping, notifications, and listening removal of KVO objects.

Object memory collection

In development, the basic principle of object management – who creates and who releases. But in MRC, we mark an object with an autoRelease, telling the editor, I’m not responsible for releasing that object. At this point, the object becomes a self-freed object, and when it is no longer needed, the system automatically reclaims its memory. By the time ARC came along, almost all objects were self-releasing objects for us, so we didn’t have to keep an eye out for memory leaks and could focus more on business logic.

Self-release of KVO

In iOS development, when we use KVO to listen to a keyPath of an object, we need to remove the corresponding keyPath listener before the object is released:

Person *person = [Person new];
self.person = person;
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];

- (void)dealloc {
	[self.person removeObserver:self forKeyPath:@"name"];
}
Copy the code

If we accidentally forget to remove the corresponding listener, we get an error like this:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x17000c2c0 of class Person was deallocated while key value observers were still registered with it. Current observation info: <NSKeyValueObservationInfo 0x17003c9e0>(
<NSKeyValueObservance 0x170243de0: Observer: 0x129d053b0, Key path: name, Options: <New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x170243db0>)'
Copy the code

FBKVOController

We can’t help but question: object dealloc function only did removeObserver: forKeyPath: one thing at a time, and can not write every time? FBKVOController might be a good choice:

Person *person = [Person new];
self.person = person;

[self.KVOController observe:person keyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSKeyValueChangeKey.id> * _Nonnull change) {
	NSString *new = change[NSKeyValueChangeNewKey];
	NSString *old = change[NSKeyValueChangeOldKey];
	NSLog(@ % @ % @ "",new,old);
}];
Copy the code

Despite the tiresome removeObserver: forKeyPath:, more concise and clear to meet the demand.

So how does FBKVOController self-release? Internally, the observer is bound to a third party, the FBKVOController, which is released when the observer is released. Finally, FBKVOController removes listeners in its dealloc method with the _FBKVOSharedController singleton.

ReactiveCocoa

In addition to FBKVOController, ReactiveCocoa also supports KVO self-release:

Person *person = [Person new];
self.person = person;

[[self.person rac_valuesAndChangesForKeyPath:@"name"  options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew  observer:self] subscribeNext:^(RACTwoTuple<id.NSDictionary *> * _Nullable x) {
	NSLog(@ % @ % @ "",x.second[@"old"],x.second[@"new"]);
}];
Copy the code

ReactiveCocoa is slightly different from FBKVOController in that ReactiveCocoa listens to the observer’s dealloc method and manages the addition/removal of object KVO listeners through the RACKVOTrampoline object.

⚠️ After testing, in iOS 11, the system has done KVO keyPath removal operation for us. Unfortunately, iOS 11 below, not removed is still a problem!

Auto-release of NSNotification

In general, we use notifications like this:

/ / add
[[NSNotificationCenter defaultCenter] addObserver:self  selector:@selector(respondsToNotification:) name:@"test0" object:nil];
/ / send
[[NSNotificationCenter defaultCenter] postNotificationName:@"test0" object:nil];
/ / remove
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"test0" object:nil];
Copy the code

Depending on the business scenario, either the dealloc method or viewWillDisappear: method is used to remove the object. However, in iOS 8 and above, we no longer need to remove notifications manually, so you can test this with the following code:

@implementation NSNotificationCenter (NS)

+ (void)load {
	Method origin = class_getInstanceMethod([self class].@selector(removeObserver:));
	Method current = class_getInstanceMethod([self class].@selector(_removeObserver:));
	method_exchangeImplementations(origin, current);
}
- (void)_removeObserver:(id)observer {
	NSLog(Call remove notification method: %@, observer);
}
@end
Copy the code

This is supposed to be one of apple’s optimizations for iOS 11.

Self release of NSTimer

Usually we use timers like this:

@property (strong.nonatomic) NSTimer *timer;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
Copy the code

The timer itself will be strong target, and self, which is target, will be strong, creating a circular reference that will not free self. To break this, we have to actively call the invalidate method. There are two ways to solve this problem:

  • useweak proxy, holding weak referencestarget, forward the message totarget.YYWeakProxyIt’s a good choice.
  • usedispatch_sourceImplement a timer yourself.YYTimerIt’s a good choice.

YYWeakProxy

YYWeakProxy is a subclass of NSProxy, which holds weak target. It uses message forwarding mechanism to forward messages to the incoming target:

@property (nullable.nonatomic.weak.readonly) id target;
Copy the code

This way, when the self reference count is zero, target will be nil, breaking the circular reference between self and NSTimer and freeing self.

However, although the circular reference between self and NSTimer was broken, it caused the circular reference between YYWeakProxy and NSTimer, resulting in the memory leak of YYWeakProxy. According to the authors, instead of leaking a potentially heavy Self, it would be better to leak a lightweight YYWeakProxy.

YYTimer

YYTimer can completely solve the problem of memory leakage, but the disadvantage is that the implementation is relatively complex. GCD dispatch_source: GCD dispatch_source

/ / the queue
dispatch_queue_t queue = dispatch_get_main_queue();
/ / create dispatch_source
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);
// Declare a member variable
self.timer = timer;
// Set the trigger to two seconds
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
// Set the next triggering event to DISPATCH_TIME_FOREVER
dispatch_time_t nextTime = DISPATCH_TIME_FOREVER;
// Set accuracy
dispatch_time_t leeway = 0.1 * NSEC_PER_SEC;
// Configure the time
dispatch_source_set_timer(timer, startTime, nextTime, leeway);
/ / callback
dispatch_source_set_event_handler(timer, ^{
	// ...
});

/ / activation
dispatch_resume(timer);
Copy the code

Need to cancel:

dispatch_source_cancel(self.timer);
Copy the code

reference

www.olinone.com/?p=232