iOS 底层探索之路

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

2020-12-14  本文已影响0人  欧德尔丶胡

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

前言

准备:

内容:

一、对象的生命周期

定义:

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

对象的创建途径:
图解对象生命周期

总结:在对象的创建和初始化之后,只要对象的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_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 。
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表中的冲突次数绝不会超过改值)
};
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;
}
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);
}

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 NSObject *obj;

__strong

__strong NSObject *obj;
验证

将obj2声明改为__weak

weakSelf 与 self
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));
__weak typeof(self) weakSelf = self;
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)self));

打印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__);
}
解决方式一: pop时在其他方法中销毁timer
- (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:方法

//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方法。

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子类************
@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强持有问题************
@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作为中间代理、中间者;

上一篇 下一篇

猜你喜欢

热点阅读