iOS weak原理源码探究

1,208 阅读3分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。

  • 弱引用时干了什么,如下代码来看看
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc]init];
        __weak NSObject *weakObj = obj;
    }
    return 0;
}
  • 通过clang来看看cpp代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_vc7szrdj0xj63p71bn1fz8n80000gn_T_main_32a486_mi_0);
        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"));
        __attribute__((objc_ownership(weak))) NSObject *weakObj = obj;
    }
    return 0;
}
  • 可以看出是通过objc_ownership来实现,但是这样也没法追踪下去了
    那么转换成.ll中间文件来看看
define i32 @main(i32 %0, i8** %1) #1 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca %0*, align 8
  %7 = alloca %0*, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %8 = call i8* @llvm.objc.autoreleasePoolPush() #2
  notail call void (i8*, ...) @NSLog(i8* bitcast (%struct.__NSConstantString_tag* @_unnamed_cfstring_ to i8*))
  %9 = load %struct._class_t*, %struct._class_t** @"OBJC_CLASSLIST_REFERENCES_$_", align 8
  %10 = bitcast %struct._class_t* %9 to i8*
  %11 = call i8* @objc_alloc_init(i8* %10)
  %12 = bitcast i8* %11 to %0*
  store %0* %12, %0** %6, align 8
  %13 = load %0*, %0** %6, align 8
  %14 = bitcast %0** %7 to i8**
  %15 = bitcast %0* %13 to i8*
  %16 = call i8* @llvm.objc.initWeak(i8** %14, i8* %15) #2
  %17 = bitcast %0** %7 to i8**
  call void @llvm.objc.destroyWeak(i8** %17) #2
  %18 = bitcast %0** %6 to i8**
  call void @llvm.objc.storeStrong(i8** %18, i8* null) #2
  call void @llvm.objc.autoreleasePoolPop(i8* %8)
  ret i32 0
}
  • 可以看到%16 = call i8* @llvm.objc.initWeak(i8** %14, i8* %15) #2,是调用了objcinitWeak方法,那么就去objc的源码里捋这个方法就行了,下面基本都是在源码里注释探索。

objc_initWeak

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}
  • id *location:注释上能看出,即__weak指针的地址,如文章开头例子中的weakObj的地址
  • id newObj:引用的对象,即文章开头例子中的obj
  • 而后returnstoreWeak方法,这个就是核心实现了

storeWeak

enum HaveOld { DontHaveOld = false, DoHaveOld = true };
enum HaveNew { DontHaveNew = false, DoHaveNew = true };
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
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;

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

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

            previouslyInitializedClass = cls;

            goto retry;/// 重新获取一遍newObj,这时的newObj应该已经初始化过了
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        // 如果weak_ptr之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该weak_ptr地址
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // 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);
        // 2. 更新newObj的isa的weakly_referenced bit标志位
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // 3. *location 赋值,也就是将weak ptr直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
        *location = (id)newObj;// 将weak ptr指向object
    }
    else {
        // No new value. The storage is not changed.
    }
    // 解锁
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    return (id)newObj;// 返回newObj
}
  • storeWeak方法接受5个参数,其中HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating 3个枚举分别传入的是false,true,true,分别表示:weak ptr之前是否已经指向了一个弱引用,weak ptr是否需要指向一个新引用, 如果被弱引用的对象正在析构,此时再弱引用该对象,是否应该crash
  • 结合上述代码注释应该大致能捋清楚storeWeak干了啥