美文网首页iOS 底层探索之路
iOS 底层探索:对象的生命周期 & strong & weak

iOS 底层探索:对象的生命周期 & strong & weak

作者: 欧德尔丶胡 | 来源:发表于2020-12-14 14:08 被阅读0次

    iOS 底层探索: 学习大纲 OC篇

    前言

    • 之前我们分析过内存管理的一些操作, OC中的内存管理是通过引用计数器来实现的。一个对象的声明周期取决于它是否还没其他对象引用,即retainCount是否等于0。 但在有些情况下,在某个对象的生命周期中,我们并不希望对象的销毁时间由是否被其他对象应用来决定,而是这个对象本该是什么时候销毁就什么时候被销毁。因此引入对象的生命周期、、弱引用、强引用的概念。

    准备:

    内容:

      1. 对象的生命周期
      1. strong & weak
      1. 强弱引用

    一、对象的生命周期

    定义:

    在OC中一个对象的生命周期就是指,这个对象从创建到销毁的运行时(runtime)的生命过程。从内存管理引用计数的层面来讲,就是引用计数从1变成0的过程。

    • MRC中,手动管理内存,一个对象的生命周期经历了alloc、retain、release、dealloc等一些列的过程,直到对象的引用计数为0,被释放结束了。
    • ARC中,内存是系统(LLVM和Runtime的共同结果)自动管理的,其实 ARC 内部机制原理也是来源于mrc,一个对象的生命周期,大多是由系统自动管理的;
    对象的创建途径:
      1. 程序显示的创建并初始化;
      1. 对象作为另一个对象的副本;
      1. unArchiving:从已归档的二进制数据流中解码,如果一个对象是从一个nib文件中被unArchive的话,在所有的nib文件中的对象都被装载到内存之后,就会收到一个名叫 awakeFromNib 的消息
    图解对象生命周期

    总结:在对象的创建和初始化之后,只要对象的retainCount的值比0大,那么它就会一直存在在内存中。通过想一个对象发送retain消息,或者进行copy操作。其他的对象可以引用并持有该对象的所有权。同时,移除引用的时候要发送release消息。

    二、weak 和 strong

    weak的底层实现原理

    举例:

    int main(){
        NSObject *obj = [[NSObject alloc] init];
        id __weak obj1 = obj;
    }
    

    clang 编译报错提示:

    int main(){
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        id __attribute__((objc_ownership(weak))) obj1 = obj;
    }
    
    • objc_ownership字面意思是:获得对象的所有权,是对对象weak的初始化的一个操作
    那么开启汇编调试:
    • 调用objc_initWeak存入sidetable表;
    • 调用objc_loadWeakRetain返回自身,并引用计数+1(refcnts的value+固定增量值);
    • __weak修饰的 obj1是个作用域内的临时变量,所以出了作用域就被释放了。

    objc_initWeak为出发点去Objc-4源码中查看:

    
    /** 
     * Initialize a fresh weak pointer to some object location. 
     * It would be used for code like: 
     *
     * (The nil case) 
     * __weak id weakPtr;
     * (The non-nil case) 
     * NSObject *o = ...;
     * __weak id weakPtr = o;
     * 
     * This function IS NOT thread-safe with respect to concurrent 
     * modifications to the weak variable. (Concurrent weak clear is safe.)
     *
     * @param location Address of __weak ptr. 
     * @param newObj Object ptr. 
     */
    id
    objc_initWeak(id *location, id newObj)
    {
        if (!newObj) {
            *location = nil;
            return nil;
        }
    
        return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
            (location, (objc_object*)newObj);
    }
     //location : __weak指针 的地址 ,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
    // newObj :所引用的对象。即例子中的obj 。
    
    • 从上面的代码可以看出objc_initWeak方法只是一个深层次函数调用的入口,在该方法内部调用了storeWeak方法。下面我们来看下storeWeak方法的实现代码。
    static id 
    storeWeak(id *location, objc_object *newObj)
    {
        ASSERT(haveOld  ||  haveNew);
        if (!haveNew) ASSERT(newObj == nil);
    
        Class previouslyInitializedClass = nil;
        id oldObj;
        SideTable *oldTable;
        SideTable *newTable;
    
        // Acquire locks for old and new values.
        // Order by lock address to prevent lock ordering problems. 
        // Retry if the old value changes underneath us.
     retry:
        if (haveOld) {// 如果weak ptr之前弱引用过一个obj,则将这个obj所对应的SideTable取出,赋值给oldTable
            oldObj = *location;
            oldTable = &SideTables()[oldObj];
        } else {
            oldTable = nil;// 如果weak ptr之前没有弱引用过一个obj,则oldTable = nil
        }
        if (haveNew) { // 如果weak ptr要weak引用一个新的obj,则将该obj对应的SideTable取出,赋值给newTable
            newTable = &SideTables()[newObj];
        } else {
            newTable = nil; // 如果weak ptr不需要引用一个新obj,则newTable = nil
        }
    
    // 加锁操作,防止多线程中竞争冲突
        SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
    // location 应该与 oldObj 保持一致,如果不同,说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改
        if (haveOld  &&  *location != oldObj) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            goto retry;
        }
    
        // Prevent a deadlock between the weak reference machinery
        // and the +initialize machinery by ensuring that no 
        // weakly-referenced object has an un-+initialized isa.
        if (haveNew  &&  newObj) {
            Class cls = newObj->getIsa();
            if (cls != previouslyInitializedClass  &&  
                !((objc_class *)cls)->isInitialized())   // 如果cls还没有初始化,先初始化,再尝试设置weak
    
            {
                SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
                class_initialize(cls, (id)newObj);
    
                // If this class is finished with +initialize then we're good.
                // If this class is still running +initialize on this thread 
                // (i.e. +initialize called storeWeak on an instance of itself)
                // then we may proceed but it will appear initializing and 
                // not yet initialized to the check above.
                // Instead set previouslyInitializedClass to recognize it on retry.
                previouslyInitializedClass = cls; // 这里记录一下previouslyInitializedClass, 防止改if分支再次进入
    
                goto retry; // 重新获取一遍newObj,这时的newObj应该已经初始化过了
            }
        }
    
        // Clean up old value, if any.
        if (haveOld) {
            weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
        }
    
        // Assign new value, if any.
        if (haveNew) {  // 如果weak_ptr需要弱引用新的对象newObj
    
    // (1) 调用weak_register_no_lock方法,将weak ptr的地址记录到newObj对应的weak_entry_t中
            newObj = (objc_object *)
                weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                      crashIfDeallocating);
            // weak_register_no_lock returns nil if weak store should be rejected
    
    // (2) 更新newObj的isa的weakly_referenced bit标志位
            // Set is-weakly-referenced bit in refcount table.
            if (newObj  &&  !newObj->isTaggedPointer()) {
                newObj->setWeaklyReferenced_nolock();
            }
    
            // Do not set *location anywhere else. That would introduce a race.
    // (3)*location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
            *location = (id)newObj;
        }
        else {
            // No new value. The storage is not changed.
        }
        // 解锁,其他线程可以访问oldTable, newTable了
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    
    // 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
        return (id)newObj;
    }
    

    storeWeak具体流程如下:

    1. storeWeak 方法实际上是接收了5个参数,分别是 haveOld、haveNew和crashIfDeallocating ,这三个参数都是以模板的方式传入的,是三个bool类型的参数。分别表示weak指针之前是否指向了一个弱引用,weak指针是否需要指向一个新的引用,若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
    2. 该方法维护了 oldTable 和 newTable 分别表示旧的引用弱表和新的弱引用表,它们都是 SideTable 的hash表
    3. 如果weak指针之前指向了一个弱引用,则会调用 weak_unregister_no_lock 方法将旧的weak指针地址移除。
    4. 如果weak指针需要指向一个新的引用,则会调用 weak_register_no_lock 方法将新的weak指针地址添加到弱引用表中
    5. 调用 setWeaklyReferenced_nolock 方法修改weak新引用的对象的bit标志位

    有关SideTableiOS 底层探索:内存管理 (上) 中讲的有喔,这里主要讲下:weak_table_t

    /**
     * The global weak references table. Stores object ids as keys,
     * and weak_entry_t structs as their values.
     */
    struct weak_table_t {
        weak_entry_t *weak_entries; //hash数组,用来存储弱引用对象的相关信息weak_entry_t
        size_t    num_entries; //hash数组中的元素个数
        uintptr_t mask; //hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
        uintptr_t max_hash_displacement; //可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过改值)
    };
    
    • weak_table_t是一个典型的hash结构
    • weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息
    • weak_entry_t是存储在弱引用表中的一个内部结构体,它负责维护和存储指向一个对象的所有弱引用hash表。其定义如下:
    typedef objc_object ** weak_referrer_t; //objc_object是weak_entry_t表中weak弱引用对象的范型对象的结构体结构。
    struct weak_entry_t {
        DisguisedPtr<objc_object> referent;  //范型
        union {
            struct {
                weak_referrer_t *referrers;
                uintptr_t        out_of_line : 1; //最低有效位,也是标志位。当标志位 0 时,增加引用表指针纬度。
                uintptr_t        num_refs : PTR_MINUS_1; //引用数值。这里记录弱引用表中引用有效数字,因为弱引用表使用的是静态 hash 结构,所以需要使用变量来记录数目。
                uintptr_t        mask; //计数辅助量。
                uintptr_t        max_hash_displacement; //hash 元素上限阀值。
            };
            struct {
                // out_of_line=0 is LSB of one of these (don't care which)
                weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
            };
        }
    }
    

    weak的整体实现流程如图:

    【总结】:
    1:⾸先我们知道有⼀个⾮常⽜逼的家伙-sideTable
    2:得到sideTable的weakTable 弱引⽤表
    3:创建⼀个weak_entry_t
    4:把referent加⼊到weak_entry_t的数组inline_referrers
    5:把weak_table扩容⼀下
    6:把new_entry加⼊到weak_table中

    objc_loadWeakRetain的执行流程如下:

    /*
      Once upon a time we eagerly cleared *location if we saw the object 
      was deallocating. This confuses code like NSPointerFunctions which 
      tries to pre-flight the raw storage and assumes if the storage is 
      zero then the weak system is done interfering. That is false: the 
      weak system is still going to check and clear the storage later. 
      This can cause objc_weak_error complaints and crashes.
      So we now don't touch the storage until deallocation completes.
    */
    
    id
    objc_loadWeakRetained(id *location)
    {
        id obj;
        id result;
        Class cls;
    
        SideTable *table;
        
     retry:
        // fixme std::atomic this load
        obj = *location;
        if (!obj) return nil;
        if (obj->isTaggedPointer()) return obj;
        
        table = &SideTables()[obj];
        
        table->lock();
        if (*location != obj) {
            table->unlock();
            goto retry;
        }
        
        result = obj;
    
        cls = obj->ISA();
        if (! cls->hasCustomRR()) {
            // Fast case. We know +initialize is complete because
            // default-RR can never be set before then.
            ASSERT(cls->isInitialized());
            if (! obj->rootTryRetain()) {
                result = nil;
            }
        }
        else {
            // Slow case. We must check for +initialize and call it outside
            // the lock if necessary in order to avoid deadlocks.
            if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
                BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                    class_getMethodImplementation(cls, @selector(retainWeakReference));
                if ((IMP)tryRetain == _objc_msgForward) {
                    result = nil;
                }
                else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                    result = nil;
                }
            }
            else {
                table->unlock();
                class_initialize(cls, obj);
                goto retry;
            }
        }
            
        table->unlock();
        return result;
    }
    
    • 这段代码的核心就是:retainWeakReference->......rootRetain的过程。进行引用计数加1
    weak释放为nil过程

    1、调用objc_release
    2、因为对象的引用计数为0,所以执行dealloc
    3、在dealloc中,调用了_objc_rootDealloc函数
    4、在_objc_rootDealloc中,调用了object_dispose函数
    5、调用objc_destructInstance
    6、最后调用objc_clear_deallocating

    我们只看下objc_clear_deallocating

    void objc_clear_deallocating(id obj) 
    {
        assert(obj);
        assert(!UseGC);
        if (obj->isTaggedPointer()) return;
        obj->clearDeallocating();
    }
    
    //执行 clearDeallocating方法
    inline void objc_object::clearDeallocating()
    {
        sidetable_clearDeallocating();
    }
    // 执行sidetable_clearDeallocating,找到weak表中的value值
    void  objc_object::sidetable_clearDeallocating()
    {
        SideTable *table = SideTable::tableForPointer(this);
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
        spinlock_lock(&table->slock);
        RefcountMap::iterator it = table->refcnts.find(this); //这里可以再研究下
        if (it != table->refcnts.end()) {
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
    //清理对象
                weak_clear_no_lock(&table->weak_table, (id)this);
            }
            table->refcnts.erase(it);
        }
        spinlock_unlock(&table->slock);
    }
    
    • clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

    • objc_clear_deallocating该函数 具体流程如下:

    1、从weak表中获取废弃对象的地址为键值的记录
    2、将包含在记录中的所有附有 weak修饰符变量的地址,赋值为nil
    3、将weak表中该记录删除
    4、从引用计数表中删除废弃对象的地址为键值的记录

    总结

    weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

    strong的底层实现原理

    如果是用属性strong,同样可以用Clang去查看源码。这里只做汇编调试如下:


    我们进入objc_storeStrong源码:

    void
    objc_storeStrong(id *location, id obj)
    {
        id prev = *location;
        if (obj == prev) {
            return;
        }
        objc_retain(obj);//retain新值
        *location = obj;
        objc_release(prev);//release旧值
    }
    

    三、弱引用 & 强引用

    概念

    弱引用(Weak Reference):当前对象的声明周期不被是否由其他其他对象引用限制,它本该什么时候销毁就什么时候销毁。计时它的引用没断,但是当它的生存周期到了就会被销毁。

    强引用(Strong Reference ):当前对象被其他对象引用时,会执行retain,引用计数+1.当retainCount=0时,该对象才会被销毁。 默认情况下是强引用方式。

    简单的说:

    当用指针指向某个对象时,你可以通过retain/release管理它的内存,也可以不管理。
    如果你管理了,就拥有对这个对象的强引用;
    如果你没有管理,那么你拥有的就是弱引用。

    使用

    __weak

    • weak严格的说应当叫“ 归零弱引用 ”,weak相当于老版本的assign,即当对象被销毁后,会自动的把它的指针置为nil,这样可以防止野指针错误。
    • weak 作为属性的关键字的作用弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为 nil。
    __weak NSObject *obj;
    

    __strong

    • 变量声明默认都带有strong关键字,如果变量什么关键字都不写,那么就默认为强引用,strong相当于老版本的retain ;
    __strong NSObject *obj;
    
    验证

    将obj2声明改为__weak

    • 从上面可以看出使用__strong 和__weak的区别,因为__strong修饰的对象会使对象本身dretainCount+1,而weak的并不会。
      所以第一个例子的retainCount为2,obj1=nil之后retainCount为1,并不会对obj2造成影响,而第二个例子obj1=nil之后retainCount 为0了,内存也跟着释放了,所以obj2也为nil。
    weakSelf 与 self
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
    __weak typeof(self) weakSelf = self;
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
    

    打印weakSelf 和 self对象,以及指针地址:


    • 当前self取地址 和 weakSelf取指针的地址的值是不一样的。意味着有两个指针地址,指向的是同一片内存空间,即weakSelf 和 self 的内存地址是不一样,都指向同一片内存空间的

    强引用的举例分析:NSTimer(计时器)

    - (void)createTimer {
        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);
    }
    - (void)dealloc{
        [self.timer invalidate];
        self.timer = nil;
        NSLog(@"%s",__func__);
    }
    
    • 我们运行程序,进行push-pop跳转,发现定时器方法仍然在执行,并没有执行B的dealloc方法;
    • 我们知道:NSTimer创建后,需要手动加入到Runloop中才可以运行,但timer会使得当前控制器不走dealloc方法,导致timer控制器无法释放
    解决方式一: pop时在其他方法中销毁timer
    • 重写didMoveToParentViewController方法
    - (void)didMoveToParentViewController:(UIViewController *)parent{
        // 无论push 进来 还是 pop 出去 正常跑
        // 就算继续push 到下一层 pop 回去还是继续
        if (parent == nil) {
           [self.timer invalidate];
            self.timer = nil;
            NSLog(@"timer 走了");
        }
    }
    
    解决方式二:

    定义timer时,采用闭包的形式,因此不需要指定target:

    - (void)blockTimer{
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"timer fire - %@",timer);
        }];
    }
    
    • 这两个解决方法不是我们要研究的,为了对强引用进行拓展研究。所以回到最开始的地方;

    先看看官方文档NSTimertimerWithTimeInterval:target:selector:userInfo:repeats:方法

    • 从文档中可以看出,timer对传入的target具有强持有,即timer持有self。由于timer是定义在B界面中,所以self也持有timer,因此 self -> timer -> self构成了循环引用
    • 我们我们尝试通过__weak即弱引用来解决,代码修改如下:
    //typeof(self)是获取到self的类型,这样定义的weakSelf就是和self一个类型的,加上__weak是建立一个弱引用
    __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];
    

    我们再次运行程序,进行push-pop跳转。发现问题还是存在,即定时器方法仍然在执行,并没有执行B的dealloc方法。

    • 为什么呢?因为我们的分析并不全面,此时还有一个Runloop对timer的强持有,因为Runloop的生命周期比B界面更长,所以导致了timer无法释放。

    • 拓展循环引用的模型:

    timer模型:self -> timer -> weakSelf -> self,当前的timer捕获的是B界面的内存,即vc对象的内存,即weakSelf表示的是vc对象

    Block模型:self -> block -> weakSelf -> self,当前的block捕获的是指针地址,即weakSelf表示的是指向self的临时变量的指针地址

    解决方式三:中介者模式,即不使用self,依赖于其他对象

    将target换成NSObject对象,将fireHome交给target执行:

    //**********1、定义其他对象**********
    @property (nonatomic, strong) id            target;
    
    //**********2、修改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];
    
    //**********3、imp**********
    void fireHomeObjc(id obj){
        NSLog(@"%s -- %@",__func__,obj);
    }
    

    运行发现timer还是会继续执行。原因是解决了中介者的释放,但是没有解决中介者的回收,即self.target的回收。

    所以还需要释放timer

    解决方式四:自定义封装timer

    这种方式是根据思路三的原理,自定义封装timer,其实现如下

    //*********** .h文件 ***********
    @interface CJLTimerWapper : NSObject
    
    - (instancetype)cjl_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
    - (void)cjl_invalidate;
    
    @end
    
    //*********** .m文件 ***********
    #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]) {
            //传入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(CJLTimerWapper *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)cjl_invalidate{
        [self.timer invalidate];
        self.timer = nil;
    }
    
    - (void)dealloc
    {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    

    使用方式:

    @property (nonatomic, strong) LGTimerWapper *timerWapper;
    //定义
    self.timerWapper = [[CJLTimerWapper alloc] cjl_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
    
    //释放
    - (void)dealloc{
         [self.timerWapper cjl_invalidate];
    }
    

    运行结果如下:

    解决方式五:利用NSProxy虚基类的子类

    NSProxy子类也是处理timer强引用最常用的方式。

    • 首先定义一个继承自NSProxy的子类
    //************NSProxy子类************
    @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;
    }
    
    • 将timer中的target传入NSProxy子类对象,即timer持有NSProxy子类对象
    //************解决timer强持有问题************
    @property (nonatomic, strong) LGProxy       *proxy;
    
    
    self.proxy = [CJLProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
    
    - (void)fireHome{
        num++;
        NSLog(@"hello word - %d",num);
    }
    
    //在dealloc中将timer正常释放
    - (void)dealloc{
        [self.timer invalidate];
        self.timer = nil;
    }
    

    这样做的主要目的是将强引用转移成消息转发。虚基类只负责消息转发,即使用NSProxy作为中间代理、中间者;

    • vc释放,导致了proxy的释放

    • dealloc方法中,timer进行了释放,所以runloop强引用也释放了

    相关文章

      网友评论

        本文标题:iOS 底层探索:对象的生命周期 & strong & weak

        本文链接:https://www.haomeiwen.com/subject/gvgtwktx.html