Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Cut target strong hold

In addition to the general method to solve the problem of circular reference, we can also solve the problem of circular reference through the strong holding of segment target.

1.1 The mediator model

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *objc = [[NSObject alloc] init]; 
    class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:"); 
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(fireHome) userInfo:nil repeats:YES]; 
}
void fireHomeObjc(id obj){ 
    num++; 
    NSLog(@"hello word - %d -%@",num, obj); 
}
- (void)dealloc{ 
    [self.timer invalidate]; 
    self.timer = nil;
}
Copy the code
  • Create NSObject objc, add fireHome method to NSObject by Runtime, IMP points to the function address of fireHomeObjc
  • Create NSTimer and pass objc to target to avoid NSTimer’s strong hold on self
  • When the page exits, the dealloc method is called normally because self is not held by NSTimer
    • In dealloc, the NSTimer is released. At this point NSTimer’s strong hold contact with objC, objC is also released

1.2 Encapsulating a custom Timer

Open LGTimerWapper. H and write the following code:

#import <Foundation/Foundation.h> 

@interface LGTimerWapper : NSObject

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

- (void)lg_invalidate;

@end
Copy the code

Open the lgTimerWapper. m file and write the following code:

#import "LGTimerWapper.h" 
#import <objc/message.h> 
@interface LGTimerWapper() 

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer; 

@end 

@implementation LGTimerWapper 

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self == [super init]) { 
        self.target = aTarget; 
        self.aSelector = aSelector; 
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method = class_getInstanceMethod([self.target class], aSelector); 
            const char *type = method_getTypeEncoding(method);
            class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type); 
            
            self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo]; 
        }
    }
    return self; 
}
void fireHomeWapper(LGTimerWapper *warpper){ 
    if (warpper.target) { 
        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
        lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer); 
    } else { 
        [warpper lg_invalidate]; 
    }
}
- (void)lg_invalidate{ 
    [self.timer invalidate]; 
    self.timer = nil;
}

@end
Copy the code

LGTimerWapper:

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
}
- (void)fireHome{ 
    num++; 
}
Copy the code
  • In LGTimerWapper, define the initialization lg_initWithtimInterval method and the lg_invalidate release method
  • Initialization method
    • The target inside the control uses the weak modifier to hold the ViewController weakly
    • Checks for the presence of the selector in the target. If so, use Runtime to add the same method number to the current class, pointing to its own internal fireHomeWapper function address
    • Create a real NSTimer timer and pass the control’s own instance object to the target to prevent NSTimer from holding the ViewController too hard.
  • When NSTimer calls back, it goes into the fireHomeWapper function
    • If target exists, use objc_msgSend to send the message to the selector under target itself.
  • When the page exits, the ViewController can be released normally. But LGTimerWapper and NSTimer hold each other, and neither can release it.
  • Since neither side can release, the NSTimer callback continues to be called
    • When you enter fireHomeWapper and find that target no longer exists, call LGTimerWapper’s lg_invalidate method to release NSTimer internally
    • When NSTimer is released, the strong hold on LGTimerWapper is released, and LGTimerWapper is also released.