This article learns reference iOS- Basic Principle 33: Memory management (2) strong reference analysis, thanks in this
This article is mainly through timers to comb through several solutions to strong references
Strong reference (strong hold)
Problem is introduced into
Assume that there are two interfaces, A and B, pushing from A to B. There are the following timer codes in INTERFACE B. When I return to A interface from B pop, I find that the timer does not stop, and its method is still executing. Why?
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code
The main reason is that the B interface is not released, that is, the dealloc method is not executed, so the Timer cannot stop and release
Solution 1
- rewrite
didMoveToParentViewController
methods
- (void) didMoveToParentViewController: (the parent UIViewController *) {/ / push in or pop out normal run / / even if continue to pop back to continue to push to the next layer if (parent == nil) { [self.timer invalidate]; self.timer = nil; NSLog (@ "timer go"); }}Copy the code
Solution 2
- When defining timer, this parameter is used
closure
, so there is no need to specify target
- (void)blockTimer{ self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"timer fire - %@",timer); }]; }Copy the code
whyB the interface is not released
Let’s delve into the bottom layer, why B interface cannot be released after it has timer, that is, it does not go to dealloc method. We can through the official document view timerWithTimeInterval: target: the selector: the userInfo: repeats: method for the description of the target
As you can see from the documentation, the timer has a strong hold on the passed target, that is, the timer holds self. Since the timer is defined in the B interface, self also holds the timer, so self -> timer -> self forms a circular reference
In the iOS- Underlying Principles: Block Underlying Principles article, there are several solutions for looping applications. We tried to solve this problem with __weak or weak references, and the code changed as follows
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code
We run the program again for a push-pop jump. The timer method is still executing, but B’s dealloc method is not executed.
-
We use __weak to break the loop reference before self -> timer -> self, i.e. the reference chain becomes self -> timer -> weakSelf -> self. However, our analysis is not comprehensive here. At this time, there is also a strong holding of timer by Runloop. Because the life cycle of Runloop is longer than that of INTERFACE B, timer cannot be released, and self of interface B cannot be released. So, the original chain of references should look like this
addweakSelf
And then, it looks like this
WeakSelf with self
For weakSelf and Self, there are mainly the following two questions
- 1.
weakSelf
Reference counting is performed+ 1
Operation? - 2,
weakSelf
和self
Is the pointer to the same address, to the same piece of memory? - With questions, we are
weakSelf
Before and after printingself
Reference count of
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) weakSelf = self;
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
Copy the code
The results are as follows: before and after discoveryself
The reference count of the8
One conclusion can therefore be drawn:WeakSelf does not perform +1 operation on memory
- To continue printing
weakSelf
和self
Object, and the address of the pointerpo weakSelf po self po &weakSelf po &self Copy the code
The results are as follows:As can be seen from the print result, the currentself
Take the address andweakSelf
The value of the address is different. Means that there areTwo pointer addresses, pointing to the same memory space
, i.e.,WeakSelf and self have different memory addresses, pointing to the same memory space
the
-
As can be seen from the print above, at this time, the timer captures
, which is an object, so strong holding cannot be solved by weakSelf. That is, the reference chain relationship is: NSRunLoop -> Timer -> weakSelf (
). So RunLoop has a strong hold on the entire object space, RunLoop does not stop, timer and weakSelf cannot be released
-
And we are in
Block
In principleA circular reference to a block
, andtimer
There is a difference. A method that uses the underlying principles of block__Block_object_assign
To be seen,block
To capture theObject pointer address
, i.e.,Weakself is the pointer address of a temporary variable
, withself
It doesn’t matter becauseWeakSelf is the new address space
. At this timeWeakSelf equals an intermediate value
. Its reference relation chain isSelf -> block -> weakSelf (pointer address of temporary variable)
, can be accessed throughaddress
getPointer to the
. Block is printed as follows:
So here we need to distinguish between the model referenced by the block and timer cycles
- The timer model:
self -> timer -> weakSelf -> self
The currenttimer
To capture theB Memory of the interface, that is, the memory of the VC object
, i.e.,weakSelf
Represents theVc object
- Block model:
self -> block -> weakSelf -> self
, the current block capturesPointer to the address
, i.e.,weakSelf
Represents theThe address of the pointer to self's temporary variable
Resolve strong references (strong holds)
Train of thought 2, train of thought 3, train of thought 4 all are: rely on intermediary mode, break strong hold, recommend train of thought 4 among them
Idea 1: Destroy the timer in another method when pop
From the previous explanation, we know that the Runloop indirectly holds self because of its strong holding of the timer (because the timer captures the VC object). Therefore, the dealloc method cannot be executed. You need to see if there is another way to destroy the timer during pop. This method is didMoveToParentViewController
-
DidMoveToParentViewController method, is used when a view controller to add or remove viewController, must call the method. To tell iOS that adding or deleting child controllers is complete.
-
Rewrite didMoveToParentViewController method in the interface of B
- (void) didMoveToParentViewController: (the parent UIViewController *) {/ / push in or pop out normal run / / even if continue to pop back to continue to push to the next layer if (parent == nil) { [self.timer invalidate]; self.timer = nil; NSLog (@ "timer go"); }}Copy the code
Idea 2: The mediator pattern, which does not use Self, relies on other objects
In timer mode, what we care about is that fireHome executes, and we don’t care who the target is, because it’s not convenient to use self (because of the strong hold problem), so we can change target to another object, like target to NSObject, Hand fireHome to Target for execution
- Change the timer target from self to objc
/ / * * * * * * * * * * 1, define other object * * * * * * * * * * @ property (nonatomic, strong) id target; / / * * * * * * * * * * 1, modifying target * * * * * * * * * * the self. The target = [[NSObject alloc] init]; class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:"); self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES]; / / * * * * * * * * * * 3, imp * * * * * * * * * * void fireHomeObjc (id obj) {NSLog (@ - % @ "% s", __func__, obj); }Copy the code
The result is as follows
After dealloc is executed, timer continues. The reason is that the release of the mediator is addressed, but the collection of the mediator, i.e., self.target, is not. So this approach is flawed
This can be resolved by undoing the timer in the dealloc method, as shown below
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
Copy the code
The result of the run is as follows: when a POP is found, the timer is released, and the broker is also released
Idea 3: Customize the encapsulation Timer
In this way, according to the principle of idea 2, the encapsulation timer is customized. The steps are as follows
-
Custom timerWapper
-
In the initialization method, define a timer whose target is itself. So the timer in the timerWapper, it’s always listening to itself, judging the selector, and it’s already given the selector to the target that’s passed in, and there’s a method called fireHomeWapper, and in that method, it’s telling you if the target exists
- if
The target is
, the vc needs to know, that is, send the selector message to the target passed in, and pass in the timer parameter as well, so the VC can knowfireHome
Method, which is why the timer method can be executed in this way - if
Target does not exist
To release the current timerWrapper, breaking RunLoop’s strong hold on timeWrapper (TimeWrapper < - x - RunLoop
)
- if
-
Release timer from custom CJL_invalidate method. This method is called in the VC dealloc method, which causes the timerWapper to be released, breaking the VC’s strong hold on timeWrapper (vc-× -> timeWrapper).
-
/ / * * * * * * * * * * *. H file * * * * * * * * * * * @ interface CJLTimerWapper: NSObject - (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo; - (void)cjl_invalidate; @ the end / / * * * * * * * * * * * m file * * * * * * * * * * * # import "CJLTimerWapper. H # import" < objc/message. H > @ interface CJLTimerWapper () @property(nonatomic, weak) id target; @property(nonatomic, assign) SEL aSelector; @property(nonatomic, strong) NSTimer *timer; @end @implementation CJLTimerWapper - (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget Selector :(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{if (self == [super init]) {// pass vc self.target = aTarget; // The timer method self.aSelector = aSelector; if ([self.target respondsToSelector:self.aSelector]) { Method method = class_getInstanceMethod([self.target class], aSelector); const char *type = method_getTypeEncoding(method); // Add the timerWapper method class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type); // Start a timer, target is self, The listening your self. The timer = [NSTimer scheduledTimerWithTimeInterval: ti target: self selector: aSelector the userInfo: the userInfo repeats:yesOrNo]; } } return self; } runloop void fireHomeWapper(CJLTimerWapper *wapper){if (wapper. Target){// If (wapper. Target){ //objc_msgSend sends a selector message to the target that is passed in, and the timer parameter is passed in as well, so that the VC can see why the 'fireHome' method works. Void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend; lg_msgSend((__bridge void *)(wapper.target), wapper.aSelector,wapper.timer); }else{// Release the current timerWrapper [wapper.timer invalidate] if the target does not exist; wapper.timer = nil; (void)cjl_invalidate{[self.timer invalidate]; self.timer = nil; } - (void)dealloc { NSLog(@"%s",__func__); } @endCopy the code
- The use of timerWapper
// define self.timerWapper = [[CJLTimerWapper alloc] cjl_initWithTimeInterval:1 Target :self selector:@selector(fireHome) userInfo:nil repeats:YES]; // Release - (void)dealloc{[self.timerWapper cjl_invalidate]; }Copy the code
The result is as follows
This method seems to be cumbersome with many steps. Moreover, for timerWapper, method needs to be continuously added and a series of processing needs to be carried out.
Idea 4: Use a subclass of NSProxy virtual base class
Here’s one of the most common ways to handle timer strong references: the NSProxy subclass
You can use NSProxy virtual base class, which can be implemented by subclasses. NSProxy is introduced in iOS- Basic principles: Block basic principles have been introduced, and will not be repeated here
- First, define an inheritance from
NSProxy
A subclass of
/ / * * * * * * * * * * * * NSProxy subclass * * * * * * * * * * * * @ interface CJLProxy: NSProxy + (instancetype) proxyWithTransformObject (id) object; @end @interface CJLProxy() @property (nonatomic, weak) id object; @end @implementation CJLProxy + (instancetype)proxyWithTransformObject:(id)object{ CJLProxy *proxy = [CJLProxy alloc]; proxy.object = object; return proxy; } -(id)forwardingTargetForSelector:(SEL)aSelector { return self.object; }Copy the code
- will
timer
In thetarget
The incomingNSProxy subclass object
, i.e.,The Timer holds the NSProxy subclass object
/ / * * * * * * * * * * * * to solve the problem of the timer is strong hold * * * * * * * * * * * * the self in the proxy = [CJLProxy proxyWithTransformObject: self]. self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES]; - (void)dealloc{[self.timer invalidate]; self.timer = nil; }Copy the code
The main purpose of this is to shift the focus from strong references to message forwarding. The virtual base class is only responsible for message forwarding, that is, using NSProxy as an intermediate proxy, intermediary
Does the defined proxy object still exist when dealloc is released?
-
The proxy object will be released normally, because vc is released normally, so it can release its holders, namely timer and proxy. The release of timer also breaks runLoop’s strong hold on proxy. Vc-× -> proxy <-× -runloop
- Vc is released, causing
proxy
The release of - In the dealloc method, the timer is freed, so the runloop strong reference is freed
- Vc is released, causing