二十八、iOS底层原理-内存管理之强引用分析

循环引用


static int num = 0;
@interface LGTimerViewController ()
@property (nonatomic, strong) NSTimer       *timer;


- (void)viewDidLoad {
    [super viewDidLoad];
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)fireHome{
    num++;
    NSLog(@"hello word - %d",num);
}

这是NSTimer的用法,会发生循环引用导致对象无法释放。

self对timer进行了强持有,timer又对self进行了强持有,如果timer的target交给weakSelf还是无法解决,因为NSRunLoop 对timer进行了强持有,导致weakSelf和self都无法释放。timer持有的是对象的内存,block持有的是指针地址。

(lldb) po self


(lldb) po weakSelf


(lldb) p &self
(LGTimerViewController **) $6 = 0x00000001336d0fc8
(lldb) p &weakSelf
(LGTimerViewController *const *) $7 = 0x00007ffee5f25b28
(lldb) 

所以无法通过weak来打破。

思路一:dealloc 不能来 那我们能不能看看有没有其他的方法在pop的时候 就销毁timer

需要调用释放方法。

[self.timer invalidate];
self.timer = nil;

这个方法无法放在dealloc中,因为对象没有释放就不会调用dealloc。
放在viewWillDisappear,push到下一层返回就不会走了。我们可以放在didMoveToParentViewController

- (void)didMoveToParentViewController:(UIViewController *)parent{
    // 无论push 进来 还是 pop 出去 正常跑
    // 就算继续push 到下一层 pop 回去还是继续
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}

也可以使用block形式的NSTimer scheduledTimerWithTimeInterval

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
       NSLog(@"timer fire - %@",timer);
    }];

- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"%s",__func__);
}
思路二:中介者模式 - 不方便使用 self
@property (nonatomic, strong) id            target;

     self.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];

我们把target交给其他对象。self就会释放,但是NSObject对象无法释放。最后在dealloc的时候

- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"%s",__func__);
}

思路三:自定义封装timer
这种方式是根据思路二的原理,自定义封装timer,其步骤如下

自定义timerWapper
在初始化方法中,定义一个timer,其target是自己。即timerWapper中的timer,一直监听自己,判断selector,此时的selector已交给了传入的target(即vc对象),此时有一个方法fireHomeWapper,在方法中,判断target是否存在
如果target存在,则需要让vc知道,即向传入的target发送selector消息,并将此时的timer参数也一并传入,所以vc就可以得知fireHome方法,就这事这种方式定时器方法能够执行的原因

如果target不存在,已经释放了,则释放当前的timerWrapper,即打破了RunLoop对timeWrapper的强持有 (timeWrapper <-×- RunLoop)

自定义J_invalidate方法中释放timer。这个方法在vc的dealloc方法中调用,即vc释放,从而导致timerWapper释放,打破了vc对timeWrapper的的强持有( vc -×-> timeWrapper)

//*********** .h文件 ***********
@interface JTimerWapper : NSObject

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

@end

//*********** .m文件 ***********
#import "JTimerWapper.h"
#import 

@interface JTimerWapper ()

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

@end

@implementation JTimerWapper

- (instancetype)J_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self == [super init]) {
        //传入vc
        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);
            //给timerWapper添加方法
            class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
            
            //启动一个timer,target是self,即监听自己
            self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

//一直跑runloop
void fireHomeWapper(JTimerWapper *wapper){
    //判断target是否存在
    if (wapper.target) {
        //如果存在则需要让vc知道,即向传入的target发送selector消息,并将此时的timer参数也一并传入,所以vc就可以得知`fireHome`方法,就这事这种方式定时器方法能够执行的原因
        //objc_msgSend发送消息,执行定时器方法
        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
         lg_msgSend((__bridge void *)(wapper.target), wapper.aSelector,wapper.timer);
    }else{
        //如果target不存在,已经释放了,则释放当前的timerWrapper
        [wapper.timer invalidate];
        wapper.timer = nil;
    }
}

//在vc的dealloc方法中调用,通过vc释放,从而让timer释放
- (void)J_invalidate{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
}

@end

timerWapper的使用

//定义
self.timerWapper = [[JTimerWapper alloc] J_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

//释放
- (void)dealloc{
     [self.timerWapper J_invalidate];
}

运行结果如下

思路三:虚基类NSProxy

NSProxy什么也没有做,我们需要自定义一个来继承,然后来进行消息转发。


@interface LGProxy()
@property (nonatomic, weak) id object;
@end

@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    LGProxy *proxy = [LGProxy alloc];
    proxy.object = object;
    return proxy;
}

// 仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。
// 转移
// 强引用 -> 消息转发

-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}

//// sel - imp -
//// 消息转发 self.object
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
//
//    if (self.object) {
//    }else{
//        NSLog(@"麻烦收集 stack111");
//    }
//    return [self.object methodSignatureForSelector:sel];
//
//}
//
//- (void)forwardInvocation:(NSInvocation *)invocation{
//
//    if (self.object) {
//        [invocation invokeWithTarget:self.object];
//    }else{
//        NSLog(@"麻烦收集 stack");
//    }
//
//}

你可能感兴趣的:(二十八、iOS底层原理-内存管理之强引用分析)