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

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强引用也释放了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容