美文网首页
内存管理(中)

内存管理(中)

作者: 浅墨入画 | 来源:发表于2021-10-24 16:16 被阅读0次

散列表结构分析

散列表本质就是一张哈希表,散列表的类型是SideTable,有如下定义

struct SideTable {
    spinlock_t slock;//开/解锁
    RefcountMap refcnts;//引用计数表
    weak_table_t weak_table;//弱引用表
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
}
  • 包含一把锁一张引用计数表一张弱引用表
  • SideTable的存取,会牵扯加锁解锁的耗时操作,为了线程安全。所以当多个对象同时操作SideTable时,为了保证效率,采用多张SideTable表分散压力
  • 当对象分散使用多张表时,当表中的对象全部释放后该表也可以释放,这样可以及时回收内存
  • 由于开表所消耗的内存过大,如果针对每个对象都开一张表,会造成很大程度的内存浪费。所以SideTable的核心作用,对引用计数表弱引用表进行处理。

上节课我们在探索retain流程时,有一步是判断是否是纯isa?这个过程中调用了sidetable_tryRetain函数,这里在objc源码中查看

bool
objc_object::sidetable_tryRetain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    // SideTables表示有很多张表
    SideTable& table = SideTables()[this];
    bool result = true;
    auto it = table.refcnts.try_emplace(this, SIDE_TABLE_RC_ONE);
    auto &refcnt = it.first->second;
    if (it.second) {
        // there was no entry
    } else if (refcnt & SIDE_TABLE_DEALLOCATING) {
        result = false;
    } else if (! (refcnt & SIDE_TABLE_RC_PINNED)) {
        refcnt += SIDE_TABLE_RC_ONE;
    }
    
    return result;
}

<!--  SideTables -->
static StripedMap<SideTable>& SideTables() {
    return SideTablesMap.get();
}

<!-- StripedMap -->
// 散列表底层使用的是StripedMap
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
    ......
}

SideTables内部是通过SideTablesMapget方法获取的,而SideTablesMap是通过StripedMap<SideTable>定义的。

散列表为什么在内存有多张?最多能够多少张?

  1. 如果散列表只有一张表,意味着全局所有的对象都会存储在一张表中,都会进行开锁解锁(锁是锁整个表的读写)。当开锁时,由于所有数据都在一张表,则意味着数据不安全
  2. 如果每个对象都开一个表,会耗费性能,所以也不能有无数个表
  3. 根据不同系统架构,可创建864SideTable,在iOS设置上只能创建8张
  • objc源码中查看sidetable_retain函数
id
objc_object::sidetable_retain(bool locked)
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    // 获取到当前对象所在的散列表
    SideTable& table = SideTables()[this];
    
    if (!locked) table.lock();
    // 获取当前散列表的存储空间
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    return (id)this;
}
  1. 通过当前对象,找到所属的SideTable
  2. 通过当前对象,找到引用计数表的所属空间
  3. 所属空间中并不是直接存储引用计数,而是使用位域存储很多信息
  4. 核心代码:refcntStorage += SIDE_TABLE_RC_ONE
  • 查看SIDE_TABLE_RC_ONE的定义
// 弱引用标记
#define SIDE_TABLE_WEAKLY_REFERENCED     (1UL<<0)   
// 是否正在析构
#define SIDE_TABLE_DEALLOCATING          (1UL<<1) // MSB-ward of weak bit 
// 引用计数
#define SIDE_TABLE_RC_ONE                (1UL<<2) // MSB-ward of deallocating bit 
// 标记引用计数是否越界
#define SIDE_TABLE_RC_PINNED             (1UL<<(WORD_BITS-1))

1左移2位结果为4,转为十六进制为100。因为真正的refcnt在位域中的第2位上存储+100的目的就是不影响前面两位,实则引用计数+1

关于引用计数的问题

打印对象的引用计数,代码如下

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *objc = [[NSObject alloc] init];
    NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
}

// 控制台打印
objc:1
  • 在老版objc源码中,对象alloc默认为0。为了避免它的释放,在rootRetainCount方法中,系统对引用计数默认+1
image.png

仅在读取时对引用计数+1,实际上extra_rc中的引用计数仍然为0

  • objc4-818.2版本的源码中,对rootRetainCount函数做了修改
inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED);
    if (bits.nonpointer) {
        uintptr_t rc = bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

没有+1的处理,直接获取extra_rc。如果使用散列表,再加上散列表中的引用计数

这里的变化源于alloc的底层修改,当对象alloc时调用initIsa函数。在isa初始化时,引用计数已经设置为1

image.png
验证weakObjc的引用计数

修改代码如下,进行调试对象的引用计数

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *objc = [[NSObject alloc] init];
    NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
    __weak typeof(id) weakObjc = objc;
    NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
    NSLog(@"objc:%ld",CFGetRetainCount((__bridge CFTypeRef)(weakObjc)));
}

// 控制台打印
objc:1
objc:1
objc:2

当前objc对象alloc初始化之后,引用计数为1。当对象被弱引用变量持有,相当于加入弱引用表并不影响引用计数,所以第二次打印为1。为什么打印weakObjc的引用计数为什么是2呢?下面进行探索......

弱引用表

下面我们来探索为什么打印weakObjc的引用计数为2呢?

  • weak修饰的变量处设置断点,运行项目,查看汇编代码
image.png
  • 调用objc_initWeak函数,来自libobjc框架
image.png
  • objc4-818.2源码中查看objc_initWeak函数
id 
objc_initWeak(id *location, id newObj) { 
    if (!newObj) { 
       *location = nil;
       return nil; 
    } 
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); 
}

storeWeak是一个模板类,内部逻辑具有高度封装性,通过传入不同参数执行不同的逻辑分支。

  • 查看
template <HaveOld haveOld, HaveNew haveNew, 
          enum CrashIfDeallocating crashIfDeallocating>        
static id 
storeWeak(id *location, objc_object *newObj) {

    ASSERT(haveOld || haveNew); 
    if (!haveNew) ASSERT(newObj == nil); 
    
    //location:弱引用对象指针 
    //newObj:原始对象 
    
    Class previouslyInitializedClass = nil; 
    id oldObj; 
    SideTable *oldTable; 
    SideTable *newTable; 
    
retry:
    //判断haveOld 
    if (haveOld) { 
        oldObj = *location; 
        oldTable = &SideTables()[oldObj]; 
    } else { 
        //在objc_initWeak中调用时,传入的DontHaveOld,不存在 
        //将oldTable设置为nil 
        oldTable = nil; 
    }
    
    //判断haveNew 
    if (haveNew) { 
        //在objc_initWeak中调用时,传入的DoHaveNew,存在 
        //找到原始对象所在的SideTable 
        newTable = &SideTables()[newObj]; 
    } else {
        newTable = nil; 
    }
    
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }
    
    //防止弱引用机制之间的死锁 
    //确保Class的初始化 
    //为弱引用对象未初始化isa
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        
        if (cls != previouslyInitializedClass  &&!((objc_class *)cls)->isInitialized()) {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);
            
            //赋值previouslyInitializedClass,防止重复初始化
            previouslyInitializedClass = cls;
            goto retry;
        }
    }
    
    if (haveOld) {
        //存在旧值,将其移除
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    
    if (haveNew) { 
        //调用weak_register_no_lock函数,传入当前对象所在散列表中的weak_table,并传入原始对象和弱引用对象指针 
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); 
                                  
           if (!newObj->isTaggedPointerOrNil()) {
               newObj->setWeaklyReferenced_nolock(); 
           } 
           
           //将原始对象赋值给弱引用对象 
           *location = (id)newObj; 
     } else { 
         // No new value. The storage is not changed. 
     }
     
     SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
     callSetWeaklyReferenced((id)newObj); 
     return (id)newObj;
}
  1. 参数为弱引用对象指针location原始对象newObj
  2. 判断haveOldobjc_initWeak中调用时,传入的DontHaveOld,不存在将oldTable设置为nil
  3. 判断haveNew,在objc_initWeak中调用时,传入的DoHaveNew,存在找到原始对象所在的SideTable
  4. 判断haveNew原始对象,防止弱引用机制之间的死锁,确保Class的初始化,为弱引用对象未初始化isa
  5. 判断haveOld存在旧值将其移除
  6. 判断haveNew存在
    6.1 调用weak_register_no_lock函数,传入当前对象所在散列表中的weak_table,并传入原始对象弱引用对象指针
    6.2 将原始对象赋值给弱引用对象
  7. 原始对象返回,流程结束。
  • 查看weak_register_no_lock函数
id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) { 
    //referent_id:原始对象 
    //referrer_id:弱引用对象的指针 
    
    objc_object *referent = (objc_object *)referent_id; 
    objc_object **referrer = (objc_object **)referrer_id; 
    
    if (referent->isTaggedPointerOrNil()) return referent_id; 
    
    //确保引用的对象是可用的 
    if (deallocatingOptions == ReturnNilIfDeallocating || deallocatingOptions == CrashIfDeallocating) { 
        bool deallocating; 
        if (!referent->ISA()->hasCustomRR()) { 
            deallocating = referent->rootIsDeallocating(); 
        } else { 
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL)) lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference), referent->getIsa()); 
            if ((IMP)allowsWeakReference == _objc_msgForward) { 
                return nil; 
            } 
            
            deallocating = ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); 
        } 
        
        if (deallocating) { 
            if (deallocatingOptions == CrashIfDeallocating) { 
                _objc_fatal("Cannot form weak reference to instance (%p) of " 
                            "class %s. It is possible that this object was "
                            "over-released, or is in the process of deallocation.",
                            (void*)referent, object_getClassName((id)referent)); 
            } else { 
                return nil; 
            } 
        } 
    }
    
    weak_entry_t *entry; 
    //判断在表中是否能找到对象的存储
    if ((entry = weak_entry_for_referent(weak_table, referent))) { 
        //已存在,将弱引用对象追加到表中
        append_referrer(entry, referrer); 
    } else {
        //不存在,创建weak_entry_t,将原始对象和弱引用对象关联 
        weak_entry_t new_entry(referent, referrer); 
        //重新开辟一张表 
        weak_grow_maybe(weak_table); 
        //将new_entry插入到表中
        weak_entry_insert(weak_table, &new_entry); 
    } 
    return referent_id; 
}
  1. 传入原始对象referent_id和弱引用对象的指针referrer_id
  2. 一系列判断,确保引用的对象是可用的
  3. 判断在表中是否能找到对象的存储
    3.1. 已存在,将弱引用对象追加到表中
    3.2 不存在:创建weak_entry_t,将原始对象弱引用对象关联;重新开辟一张表;将new_entry插入到表中。
  4. 原始对象返回,流程结束。
  • 查看append_referrer函数
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer) {
    if (! entry->out_of_line()) { 
    
        // Try to insert inline. 
        //传入原始对象所属的weak_entry_t结构体指针和弱引用对象指针 
        //找到weak_entry_t下的inline_referrers,一个对象可能被赋值给多个弱引用对象 
        //循环找到空位,如果存在空位直接赋值 

        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { 
            if (entry->inline_referrers[i] == nil) { 
                entry->inline_referrers[i] = new_referrer; return;
            } 
        } 

        // Couldn't insert inline. Allocate out of line. 
        //没有空位,重新创建new_referrers 
        weak_referrer_t *new_referrers = (weak_referrer_t *) calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t)); 

        //将现有entry下的inline_referrers中的元素,循环导入到新创建的new_referrers中 
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[i]; 
        } 

        //相关成员变量的覆盖 
        entry->referrers = new_referrers; 
        entry->num_refs = WEAK_INLINE_COUNT; 
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE; 
        entry->mask = WEAK_INLINE_COUNT-1; 
        entry->max_hash_displacement = 0; 
    } 

    ASSERT(entry->out_of_line()); 
    
    //扩容相关操作 
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) { 
        return grow_refs_and_insert(entry, new_referrer); 
    }
    
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask); 
    size_t index = begin; 
    size_t hash_displacement = 0; 
    while (entry->referrers[index] != nil) { 
        hash_displacement++; 
        index = (index+1) & entry->mask; 
        if (index == begin) bad_weak_table(entry); 
    }
    
    if (hash_displacement > entry->max_hash_displacement) { 
        entry->max_hash_displacement = hash_displacement; 
    } 
    
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++; 
}
  1. 传入原始对象所属的weak_entry_t结构体指针弱引用对象指针
  2. 找到weak_entry_t下的inline_referrers,一个对象可能被赋值给多个弱引用对象
  3. 循环找到空位,如果存在空位直接赋值
  4. 没有空位,重新创建new_referrers
  5. 将现有entry下的inline_referrers中的元素,循环导入到新创建的new_referrers
  6. 相关成员变量的覆盖
  7. 扩容相关操作。

关于弱引用的引用计数

weakObjc引用计数为2的原因?

  • 查看汇编代码
image.png

弱引用对象在使用时,会调用objc_loadWeakRetained函数。汇编代码中的两次调用,第一次在执行CFGetRetainCount函数之前,第二次在执行NSLog方法之前。

  • objc4-818.2源码中查看objc_loadWeakRetained函数
id 
objc_loadWeakRetained(id *location) { 
    id obj; 
    id result;
    Class cls;
    SideTable *table; 
retry: 
    //对弱引用对象指针取值 
    obj = *location; 
    
    if (obj->isTaggedPointerOrNil()) return obj; 
    table = &SideTables()[obj]; 
    table->lock(); 
    if (*location != obj) {
        table->unlock(); 
        goto retry; 
    }
    
    //赋值给result临时变量 
    result = obj; 
    cls = obj->ISA(); 
    if (! cls->hasCustomRR()) { 
        ASSERT(cls->isInitialized());
        
        //调用retain函数,对引用计数+1 
        if (! obj->rootTryRetain()) { 
            result = nil;
        } 
    } else { 
        
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) { 
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL)) lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls); 
            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;
}
  1. 传入弱引用对象指针
  2. 对弱引用对象指针取值,赋值给obj,相当于原始对象
  3. obj赋值给result,此时引用计数不变
  4. 核心代码:if (! obj->rootTryRetain())obj调用retain函数,引用计数+1

使用弱引用对象触发objc_loadWeakRetained函数,对象会进行一次retain操作。

这样做的目的:避免弱引用对象在使用过程中,由于原始对象被释放,导致所有正在使用的弱引用对象全部取值异常,造成大面积的连锁反应。当objc_loadWeakRetained函数执行完毕,临时变量会释放,自动恢复对象的引用计数。采用这种方式的好处,让原始对象弱引用对象更加独立,对强弱引用对象进行分开管理

弱引用流程图

timer的强持有

NSTimer定时器循环引用问题

- (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); 
}

- (void)dealloc {
    [self.timer invalidate]; 
    self.timer = nil;
}

创建NSTimer时传入self,导致NSTimer持有self,而self又持有timer。使用NSTimerinvalidate方法,可以解除NSTimerself的持有,但NSTimerinvalidate方法在dealloc方法内调用。而self又被timer持有,dealloc方法不会执行。故此双方相互等待,造成循环引用

NSTimertarget参数传入一个弱引用self,能否解除循环引用?

- (void)viewDidLoad { 
    [super viewDidLoad]; 
    
    __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]; 
}

运行工程发现依然存在循环引用,原因是[NSRunLoop currentRunLoop]->timer->weak->self链路中,在timerWithTimeInterval内部,使用强引用对象接收target参数,所以外部定义为弱引用对象没有任何意义。

官方文档中,对target参数的说明

image.png

target:定时器触发时指定的消息发送到的对象。计时器维护对该对象的强引用,直到它(计时器)失效。
Block的区别,Block将捕获到的弱引用对象,赋值给一个强引用的临时变量,当Block执行完毕,临时变量会自动销毁,解除对外部变量的持有。

解决方案
  • 方案一:更换API,使用携带Block的方法创建NSTimer,避免target的强持有
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval 
                                    repeats:(BOOL)repeats
                                      block:(void (^)(NSTimer *timer))block;
  • 方案二:修改invalidate调用时机
// 将NSTimer的invalidate方法写在viewWillDisappear方法中
- (void)viewWillAppear:(BOOL)animated { 
    [super viewWillAppear:animated]; 
    self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; 
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

- (void)viewWillDisappear:(BOOL)animated { 
    [super viewWillDisappear:animated]; 
    [self.timer invalidate]; 
    self.timer = nil; 
}

// 或者写在didMoveToParentViewController方法中
- (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)didMoveToParentViewController:(UIViewController *)parent {
    if (parent == nil) {
        [self.timer invalidate]; 
        self.timer = nil; 
    }
}
  • 方案三:中介者模式
    通过切断target的强持有解决循环引用
- (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; 
}
  1. 创建NSObject实例对象objc,通过RuntimeNSObject增加fireHome方法,IMP指向fireHomeObjc的函数地址;
  2. 创建NSTimer,将objc传入target参数,这样避免NSTimerself的强持有;
  3. 当页面退出时,由于self没有被NSTimer持有,正常调用dealloc方法:
  4. dealloc中,对NSTimer进行释放。此时NSTimerobjc的强持有解除,objc也跟着释放。
  • 方案四:封装自定义Timer
    创建LGTimerWapper,实现自定义Timer的封装
<!-- LGTimerWapper.h -->
#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

<!-- LGTimerWapper.m -->
#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
  1. LGTimerWapper中,定义初始化lg_initWithTimeInterval方法和lg_invalidate释放方法;
  2. 初始化方法:
    2.1 控件内部的target使用weak修饰,对ViewController进行弱持有;
    2.2 检测target中是否存在该selector。如果存在,对当前类使用Runtime添加同名方法编号,指向自身内部fireHomeWapper的函数地址;
    2.3 创建真正的NSTimer定时器,将控件自身的实例对象传入target,避免NSTimerViewController强持有。
  3. NSTimer回调时,会进入fireHomeWapper函数:
    3.1 函数内部不负责业务处理,如果target存在,使用objc_msgSend,将消息发送给target自身下的selector方法。
  4. 当页面退出时,ViewController可以正常释放。但LGTimerWapperNSTimer相互持有,双方都无法释放;
  5. 由于双方都无法释放,NSTimer的回调会继续调用:
    5.1 当进入fireHomeWapper函数,发现target已经不存在了,调用LGTimerWapperlg_invalidate方法,内部对NSTimer进行释放;
    5.2 当NSTimer释放后,对LGTimerWapper的强持有解除,LGTimerWapper也跟着释放。
  • 方案五:NSProxy虚基类
    NSProxy的作用:
  1. OC不支持多继承,但是它基于运行时机制,可以通过NSProxy来实现伪多继承
  2. NSProxyNSObject属于同一级别的类,也可以说是一个虚拟类,只实现了NSObject的协议部分;
  3. NSProxy本质是一个消息转发封装的抽象类,类似一个代理人。

可以通过继承NSProxy,并重写以下两个方法实现消息转发:

- (void)forwardInvocation:(NSInvocation *)invocation; 

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;

NSProxy除了可以用于多继承,也可以作为切断强持有的中间人

<!-- LGProxy.h -->
#import <Foundation/Foundation.h>

@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object; 
@end

<!-- LGProxy.m -->
#import "LGProxy.h" 

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

@implementation LGProxy 

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

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { 
    if (!self.object) { 
        NSLog(@"异常收集-stack"); 
        return nil; 
    } 
    return [self.object methodSignatureForSelector:sel]; 
} 

- (void)forwardInvocation:(NSInvocation *)invocation {
    if (!self.object) { 
        NSLog(@"异常收集-stack"); 
        return; 
    }
    [invocation invokeWithTarget:self.object]; 
} 

@end

// LGProxy的使用代码
- (void)viewDidLoad { 
    [super viewDidLoad];
    self.proxy = [LGProxy 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); 
} 

- (void)dealloc {
    [self.timer invalidate]; 
    self.timer = nil;
}
  1. LGProxy初始化方法,将传入的object赋值给弱引用对象
  2. UIViewController中,创建LGProxy对象proxy。创建NSTimer对象,将proxy传入target,避免NSTimerViewController强持有;
  3. NSTimer回调时,触发LGProxy的消息转发方法:
    3.1 methodSignatureForSelector:设置方法签名;
    3.2 forwardInvocation:自身不做业务处理,将消息转发给object。
  4. 当页面退出时,ViewController可以正常释放:
    4.1 在dealloc中,对NSTimer进行释放。此时NSTimerproxy的强持有解除,proxy也跟着释放。

相关文章

  • JavaScript —— 内存管理及垃圾回收

    目录 JavaScript内存管理内存为什么需要管理?内存管理概念JavaScript中的内存管理JavaScri...

  • 内存管理

    内容包括: C++内存管理 Java内存管理 C++内存管理 内存分配方式 在C++中,内存分成5个区,分别是栈、...

  • 基本知识摘录

    一:内存管理的理解首先iOS中数据是存贮在堆和栈中的。内存管理需要管理堆上的内存,栈上的内存并不需要我们管理。非O...

  • 伙伴算法和slab算法

    0. 内存管理问题 内存碎片太小和管理内存碎片的效率问题 内存碎片:回收内存时,将内存块放入free链表中。因内存...

  • 内存管理(中)

    散列表结构分析 散列表本质就是一张哈希表,散列表的类型是SideTable,有如下定义 包含一把锁、一张引用计数表...

  • Java内存泄漏

    Java中的内存管理 要了解Java中的内存泄漏,首先就得知道Java中的内存是如何管理的。 在Java程序中,我...

  • 说说Java内存泄漏

    Java中的内存管理 要了解Java中的内存泄漏,首先就得知道Java中的内存是如何管理的。 在Java程序中,我...

  • Redis源码:内存管理与事件处理

    Redis内存管理 Redis内存管理相关文件为zmalloc.c/zmalloc.h,其只是对C中内存管理函数做...

  • OC中内存管理

    在OC中内存管理MRC手动内存管理和ARC自动内存管理,ARC是从iOS 4.0开始,在iOS 4.0之前...

  • iOS之从MRC到ARC内存管理详解

    概述 在iOS中开发中,我们或多或少都听说过内存管理。iOS的内存管理一般指的是OC对象的内存管理,因为OC对象分...

网友评论

      本文标题:内存管理(中)

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