preface
Weak references are analyzed in detail in the iOS Low-level Memory Management Weak References table, and the corresponding weak references are strong references. This article will analyze NSTimer’s strong references and propose several solutions to solve the problem.
Strong reference analysis of NSTimer
-
First take a look at the following NSTimer case:
// SecondViewController.m - (void)timerTest { __weak typeof(self)weakSelf = self; self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(addNumberAction) userInfo:nil repeats:true]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; } - (void)addNumberAction { num++; NSLog(@"++ %d ++", num); } - (void)dealloc { [self.timer invalidate]; self.timer = nil; NSLog(@" Second dealloc 🎉🎉🎉"); } Copy the code
So when the interface pops, does it go dealloc? It won’t go. As you can see in Developer Documentation, NSTimer will strongly reference target after startup:
- although
target
Is now aweak
Pointer, but it still pointsself
Memory, besidesRunloop
->timer
->weakSelf
->The self memory
That’s the same thing asRunloop
Indirect holdingself
, so do not release.- use
scheduledTimerWithTimeInterval
Method does not release, it isself
->timer
->weakSelf
->The self memory
Don’t release
- use
- The two cases of non-release are shown below:
- although
The solution
- Several solutions are examined below
NSTimer block mode
-
We know [NSTimer scheduledTimerWithTimeInterval: repeats: block:] won’t produce strong reference method, only need to pay attention to block circular reference for self, can solve the problem of no release. The code is as follows:
__weak typeof(self) weakSelf = self; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:true block:^(NSTimer * _Nonnull timer) { [weakSelf addNumberAction]; }]; Copy the code
At this point, weakSelf in the block block is a temporary variable, so self is not strongly held, so the interface will use the Dealloc method.
Destroy NSTimer when POP
- From the above analysis we can know
NSTimer
Will be a strong referencetarget
If destroyed at the right timetimer
It alsoDisconnect the
Strong references at this level, in turn, solve the non-release problem. - The destruction
timer
, we usually think of inviewWillDisappear
However, this will have a problem if the page needs topush
The interface is not destroyed at this point. So we destroyed the timer at this time, and when we returned to the interface, the timer task stopped, and we did not achieve the desired effect. - We can choose in the interface
pop
Destroy the timer when,pop
Judgment can be made in the following ways:-
- Get the current
navigationController.viewControllers
Array, if the current controller is not found in the arraypop
Operation:
- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; NSArray *navigations = self.navigationController.viewControllers; if ([navigations indexOfObject:self] = =NSNotFound) { // pop [self.timer invalidate]; self.timer = nil; }}Copy the code
- Get the current
-
- Rewrite system
didMoveToParentViewController
Method, and then according toparent
Judgment.didMoveToParentViewController
Methods inAfter the page loads
andAfter the page is removed
The call. The judgment method is as follows:
- (void)didMoveToParentViewController:(UIViewController *)parent { if (parent == nil) {[self.timer invalidate]; self.timer = nil; }}Copy the code
- Rewrite system
-
Either way, NSTimer can be destroyed to solve the strong reference problem
3. Intermediary mode
-
The mediation mode essentially tells the timer to hold other targets, and when the timer executes it finds the IMP of the target and executes the method so that self is not strongly held. The specific code is as follows:
self.target = [NSObject alloc]; class_addMethod([NSObject class].@selector(addNumberAction), (IMP)wushuang, "v@:"); self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(addNumberAction) userInfo:nil repeats:true]; void wushuang(id obj) { NSLog(@"__ %s __ %@", __func__, obj); } - (void)addNumberAction { num++; NSLog(@" printing -- %d", num); } Copy the code
The running results are as follows:
- This way it can be fixed
self
Strong reference problem, but the implementation iswushuang
This function is going to be executedaddNumberAction
This is not a very easy method to handle, so this is not a very good method
- This way it can be fixed
4. Customize NSTimer
-
The main idea of custom NSTimer is to put the timer method to execute in self(controller), then write the timer core method in the custom Timer class, and then call the core method in self(controller) with the mediation mode and objc_msgSend. This also does not create a strong reference to self(controller).
-
The concrete implementation is as follows:
// SecondViewController.m @interface SecondViewController(a) @property (nonatomic.strong) WSTimerWrapper *wrapperTimer; @end - (void)addNumberAction { num++; NSLog(@" printing -- %d", num); } - (void)timerTest { self.wrapperTimer = [[WSTimerWrapper alloc] ws_timerWithTimeInterval:1 target:self selector:@selector(addNumberAction) userInfo:nil repeats:true]; } // WSTimerWrapper.h @interface WSTimerWrapper : NSObject - (instancetype)ws_timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; @end // WSTimerWrapper.m @interface WSTimerWrapper(a) @property (nonatomic.weak) id aTarget; @property (nonatomic.strong) NSTimer *timer; @property (nonatomic.assign) SEL aSelector; @end @implementation WSTimerWrapper - (instancetype)ws_timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo { if (self= = [super init]) { self.aTarget = aTarget; self.aSelector = aSelector; if ([self.aTarget respondsToSelector:self.aSelector]) { Method method = class_getInstanceMethod([self.aTarget class], aSelector); const char *type = method_getTypeEncoding(method); class_addMethod([self class], aSelector, (IMP)wsAddNum, type); self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:selfselector:aSelector userInfo:userInfo repeats:yesOrNo]; }}return self; } void wsAddNum(WSTimerWrapper *wrapper) { if (wrapper.aTarget) { void (*ws_msgSend)(void *, SEL, id) = (void *)objc_msgSend; ws_msgSend((__bridge void *)(wrapper.aTarget), wrapper.aSelector, wrapper.timer); } else { [wrapper.timer invalidate]; wrapper.timer = nil; }} - (void)dealloc { NSLog(@ "WSTimerWrapper 🍉 🍉 🍉"); } @end Copy the code
The core flow diagram of the code is as follows:
- You can see that in the diagram
timer
It did notvc
Produces a strong hold whenvc
Destroyed due totarget
It doesn’t exist anymore, so it’s calledDestroy timer
code
- You can see that in the diagram
NSProxy virtual base class
-
NSProxy is an abstract superclass, the same class as NSObject, and its Api is relatively simple:
NSObject
There are also message forwarding, but after a series of conditions, the message is slowly forwarded, andNSProxy
Invoking the forward logic is much simpler. The concrete implementation is as follows:// SecondViewController.m @interface SecondViewController(a) @property (nonatomic.strong) NSTimer *timer; @property (nonatomic.strong) WSProxy *proxy; @end - (void)timerTest { self.proxy = [WSProxy proxyWithTransformTarget:self]; self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(addNumberAction) userInfo:nil repeats:true]; } - (void)addNumberAction { num++; NSLog(@" printing -- %d", num); } // WSProxy.h @interface WSProxy : NSProxy + (instancetype)proxyWithTransformTarget:(id)target; @end // WSProxy.m @interface WSProxy(a) @property (nonatomic.weak) id object; @end @implementation WSProxy + (instancetype)proxyWithTransformTarget:(id)target { WSProxy *proxy = [WSProxy alloc]; proxy.object = target; return proxy; } // Forward the message quickly //- (id)forwardingTargetForSelector:(SEL)aSelector { // return self.object; / /} // The message is slowly forwarded - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { if (self.object) { NSLog(@ "+ + + + + + % @".NSStringFromSelector(sel)); } else { NSLog(@"error : Signature has no object ~"); } return [self.object methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { if (self.object) { [invocation invokeWithTarget:self.object]; } else { NSLog(@"error : invocation has no Target ~"); }}@end Copy the code
- The timer strongly references
WSProxy
Object, andWSProxy
A weak referenceVC
And thenWSProxy
Cannot performsel
, so willsel
Method forward tovc
And that’s ittimer
, and does not generate a pairvc
The strong holds - Both messages in the code
Slowly forward
orFast forward
Can be eliminatedtimer
rightvc
Strong hold of.
- The timer strongly references