欢迎光临
我们一直在努力

Objc Block实现分析


Objc Block实现分析

Block在iOS开发中使用的频率是很高的,使用的场景包括接口异步数据的回调(AFN)、UI事件的回调(BlockKits)、链式调用(Masonry)、容器数据的遍历回调(NSArray、NSDictionary),具体的用法以及使用Block的一些坑这里就不一一赘述了,本文会从源代码的层面分析我们常用的Block的底层实现原理,做到知其然知其所以然。

本文会从如下几个主题切入,层层递进分析Block的底层原理,还原Block本来的面目

  • 无参数无返回值,不使用变量的Block分析
  • 使用自动变量的Block分析
  • 使用__block修饰的变量的Block分析
  • Block引用分析

无参数无返回值,不使用变量的Block分析

有如下的源代码,创建一个简单的block,在block做的处理是打印一个字符串,然后执行这个block。接下来会从源码入手对此进行分析:block是如何执行的

// 无参数无返回值的Block分析int main(int argc, const char * argv[]) {    void (^blk) (void) = ^{        printf("block invoke");    };    blk();    return 0;}// 输出:block invoke

使用clang -rewrite-objc main.m命令重写为C++语法的源文件。从重写后的代码中看到,一个简单block定义和调用的代码变成了大几十行,不过该源代码还算是相对简单的,我们从main函数入口逐步的进行分析:

main函数中创建__main_block_impl_0类型的实例

__main_block_impl_0结构体是什么鬼呢?我们看__main_block_impl_0结构体的实现,包含了__block_impl结构体和__main_block_desc_0结构体指针,为了方便,现在页先不用管__main_block_desc_0结构体,目前他还没有真正的使用到,后面讲到的__block修饰自动变量以及Block对对象的引用关系,设计到内存的拷贝和释放的时候会使用到该变量。__block_impl结构体包含了4个成员,为了简单分析,我们只关注其中的FuncPtr成员的FuncPtr成员,这个也是最重要的成员,其它的先忽略不计。

__main_block_impl_0实例的初始化参数

第一个是__main_block_func_0函数的地址,__main_block_func_0函数是什么呢,其实就是Block执行的方法,可以看到里面的实现就是一个简单的打印而已,和我们定义在block中的实现一样的,该参数用于初始化impl成员的。至于第二个参数先不管,在这个例子木有用到。

__main_block_impl_0实例的调用

创建了__main_block_impl_0结构体类型的实例之后,接下来就是获取到里面的FuncPtr指针指向的方法进行调用,参数是__main_block_impl_0结构体类型的实例本身,上一步创建步骤可知,FuncPtr指针是一个函数指针指向__main_block_func_0函数的地址,所以这里本质上就是__main_block_func_0函数的调用,结构是打印字符串"block invoke",一个简单的block执行孙然在代码量上增加了,其实也不算复杂。

注意点:(__block_impl *)blk这里做了一个强制转换,blk是__main_block_impl_0类型的实例的指针,根据结构体的内存布局规则,该结构体的第一个成员为__block_impl 类型,所以可以强制转换为__block_impl *类型。

由上面的分析,可以得出如下的结论:使用变量的Block调用本质上是使用函数指针调用函数

// 使用`clang -rewrite-objc main.m`命令重写为C++语法的源文件,对结果分析如下// __block_impl结构体struct __block_impl {  void *isa;  int Flags;  int Reserved;  void *FuncPtr;};// `__main_block_impl_0`是block的结构体,包含了两个成员__block_impl类型的impl成员以及__main_block_desc_0类型的Desc成员;一个构造方法__main_block_impl_0,构造方法中初始化impl成员和Desc成员struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};// Block执行的方法static void __main_block_func_0(struct __main_block_impl_0 *__cself) {        printf("block invoke");}static struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};// 重写后的入口函数mainint main(int argc, const char * argv[]) {    // 创建__main_block_impl_0类型的实例,名称为blk,这里把改实例强制转换为`void(funcPtr *)(void)`型的方法指针,不懂为何,因为在下一步会把该方法指针强制转换为对应的结构体,也就是说`void(funcPtr *)(void)`型的方法指针并没有真实的使用到。    void (*blk) (void) =    ((void (*)())&__main_block_impl_0(                                      (void *)__main_block_func_0,                                      &__main_block_desc_0_DATA));    // blk是`__main_block_impl_0`类型的实例的指针,根绝结构体的内存布局规则,该结构体的第一个成员为`__block_impl` 类型,可以强制转换为`__block_impl *`类型,获取FuncPtr函数指针,强制转换为`(void (*)(__block_impl *))`类型的函数指针,然后执行这个函数指针对应的函数,参数为blk    // 由上面的步骤可知,Block的调用本质上是使用函数指针调用函数    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);    return 0;}

使用自动变量的Block分析

有如下的源代码,创建一个简单的block,在block做的处理是打印一个字符串,使用到外部的自动变量,然后执行这个block。接下来会从源码入手对此进行分析:block是如何使用外部的自动变量的

// 使用变量的Block分析// 源代码int main(int argc, const char * argv[]) {    int age = 100;    const char *name = "zyt";    void (^blk) (void) = ^{        printf("block invoke age = %d age = %s", age, name);    };    age = 101;    blk();    return 0;}// 输出:block invoke age = 100 age = zyt

使用clang -rewrite-objc main.m命令重写为C++语法的源文件。对比上一个的数据结构方法和调用步骤大体上是一样的,只针对变化的地方进行分析

__main_block_impl_0结构体的变化

增加了两个成员:int类型的age成员、char*类型的name成员,用于保存外部变量的值,在初始化的时候就会使用自动变量的值初始化这两个成员的值

调用的变化

__main_block_func_0函数的参数struct __main_block_impl_0 *__cself在这个例子有使用到了,因为两个自动变量对应的值被保存在__main_block_impl_0结构体中了,方法中有使用到这两个变量,直接从__main_block_impl_0结构体中获取这两个值,但是这两个值是独立于自动变量的存在了

由上面的分析,可以得出如下的结论:使用变量的Block调用本质上是使用函数指针调用函数,参数是保存在block的结构体中的,并且保存的值而不是引用

// 使用`clang -rewrite-objc main.m`命令重写为C++语法的源文件,对结果分析如下// __block_impl结构体struct __block_impl {  void *isa;  int Flags;  int Reserved;  void *FuncPtr;};// __main_block_impl_0,包含了四个成员__block_impl类型的impl成员、__main_block_desc_0类型的Desc成员、int类型的age成员、char*类型的name成员;一个构造方法__main_block_impl_0,构造方法中初始化impl成员、Desc成员、age成员和name成员,比起上一个改结构体多了两个成员,用于保存外部变量的值struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int age;  const char *name;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, const char *_name, int flags=0) : age(_age), name(_name) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};// Block执行的方法static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  int age = __cself->age; // bound by copy  const char *name = __cself->name; // bound by copy        printf("block invoke age = %d age = %s", age, name);    }static struct __main_block_desc_0 {  size_t reserved;  size_t Block_size;} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};// 重写后的入口函数main// 由上面的步骤可知,使用变量的Block调用本质上是使用函数指针调用函数,参数是保存在block的结构体中的,并且保存的值而不是引用int main(int argc, const char * argv[]) {    int age = 100;    const char *name = "zyt";    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, name));    age = 101;    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);    return 0;}

使用__block修饰的变量的Block分析

有如下的源代码,有个__block修饰的int类型的自动变量age,在block和变量age的作用域中分别作了修改,从输入的结果看看是两次都生效了,接下来会从源码入手对此进行分析:block中是如何处理__block修饰的自动变量,该自动变量的内存是如何变化的

// 源代码int main(int argc, const char * argv[]) {    __block int age = 100;    const char *name = "zyt";    void (^blk) (void) = ^{        age += 2;        printf("block invoke age = %d age = %s", age, name);    };    age += 1;    blk();}// 输出 :// block invoke age = 103 age = zyt

使用clang -rewrite-objc main.m命令重写为C++语法的源文件,对结果分析如下的注释,该转换后的代码做了些许的调整,包括代码的缩进和添加了结构体声明,使得该代码可以直接运行

// clang改写后的代码如下,以下代码是经过调整可以直接运行的struct __main_block_desc_0;// __block_impl结构体struct __block_impl {    void *isa;    int Flags;    int Reserved;    void *FuncPtr;};// `__block`修饰的变量对应的结构体,里面包含了该变量的原始值也就是age成员,另外还有一个奇怪的`__forwarding`成员,稍后我们会分析它的用处  struct __Block_byref_age_0 {    void *__isa;    __Block_byref_age_0 *__forwarding;    int __flags;    int __size;    int age;};// `__main_block_impl_0`结构体包含了四个成员`__block_impl`类型的impl成员、`__main_block_desc_0`类型的Desc成员、`__Block_byref_age_0 *`类型的age成员、char*类型的name成员;一个构造方法`__main_block_impl_0`,构造方法中初始化impl成员、Desc成员、age成员和name成员,比起上一个结构体的变化是age的类型变为了包装自动变量的结构体了struct __main_block_impl_0 {    __block_impl impl;    __main_block_desc_0* Desc;    const char *name;    __Block_byref_age_0 *age; // by ref    __main_block_impl_0(void *fp,                        struct __main_block_desc_0 *desc,                        const char *_name,                        __Block_byref_age_0 *_age,                        int flags=0) :    name(_name),    age(_age->__forwarding) {        impl.isa = &_NSConcreteStackBlock;        impl.Flags = flags;        impl.FuncPtr = fp;        Desc = desc;    }};// Block执行的方法static void __main_block_func_0(struct __main_block_impl_0 *__cself) {    __Block_byref_age_0 *age = __cself->age; // bound by ref    const char *name = __cself->name; // bound by copy        (age->__forwarding->age) += 2;    printf("block invoke age = %d age = %s", (age->__forwarding->age), name);}// Block拷贝函数static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}// Block销毁函数static void __main_block_dispose_0(struct __main_block_impl_0*src) {    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}static struct __main_block_desc_0 {    size_t reserved;    size_t Block_size;    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);    void (*dispose)(struct __main_block_impl_0*);} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};// 重写后的入口函数mainint main(int argc, const char * argv[]) {    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {        (void*)0,        (__Block_byref_age_0 *)&age,        0,        sizeof(__Block_byref_age_0),        100};    const char *name = "zyt";    struct __main_block_impl_0 blockImpl =    __main_block_impl_0((void *)__main_block_func_0,                        &__main_block_desc_0_DATA,                        name,                        (__Block_byref_age_0 *)&age,                        570425344);    void (*blk) (void) = ((void (*)())&blockImpl);    (age.__forwarding->age) += 1;    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);}

__block修饰自动变量转换为C++代码的结构体关系图:
__block修饰自动变量转换为C++代码的结构体关系图

该重写的代码大部分和上面分析过的例子代码是类似,发现增加了两个处理方法Block拷贝函数__main_block_copy_0和Block销毁函数__main_block_dispose_0,这两个函数会保存在__main_block_desc_0结构体的copydispose成员中;另外添加了一个__Block_byref_age_0结构体类型用户处理__block修饰的自动变量。以下针对这两点从源代码的角度进行一个分析

__main_block_copy_0方法中调用到的_Block_object_assign可以在 runtime.c 这里找到 ,主要看下_Block_object_assign方法里面的处理逻辑

  • flags参数值为8,是BLOCK_FIELD_IS_BYREF枚举对应的值,会走到_Block_byref_assign_copy方法的调用步骤
  • _Block_byref_assign_copy方法会在在堆上创建Block_byref对象,也就是Block对象,并且把栈上和堆上的Block对象的forwarding属性值都修改为指向堆上的Block对象,这样使用两个对象的修改值都会修改为同一个地方

栈上的__block自动变量__forwarding指向关系以及拷贝到堆上之后__forwarding指向关系如下图所示
栈上的自动变量指向关系以及拷贝到堆上之后指向关系如下图所示

具体使用到的代码和对应的注释如下:

/* * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point * to do the assignment. */void _Block_object_assign(void *destAddr, const void *object, const int flags) {    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {            _Block_assign_weak(object, destAddr);        }        else {            // do *not* retain or *copy* __block variables whatever they are            _Block_assign((void *)object, destAddr);        }    }    // 代码会走到这个分支中,调用方法`_Block_byref_assign_copy`    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {        // copying a __block reference from the stack Block to the heap        // flags will indicate if it holds a __weak reference and needs a special isa        _Block_byref_assign_copy(destAddr, object, flags);    }    // (this test must be before next one)    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {        // copying a Block declared variable from the stack Block to the heap        _Block_assign(_Block_copy_internal(object, flags), destAddr);    }    // (this test must be after previous one)    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {        //printf("retaining object at %p\n", object);        _Block_retain_object(object);        //printf("done retaining object at %p\n", object);        _Block_assign((void *)object, destAddr);    }}/* * Runtime entry points for maintaining the sharing knowledge of byref data blocks. * * A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data * Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr. * We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment it. * Otherwise we need to copy it and update the stack forwarding pointer * XXX We need to account for weak/nonretained read-write barriers. */static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {    struct Block_byref **destp = (struct Block_byref **)dest;    struct Block_byref *src = (struct Block_byref *)arg;            //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);    //printf("src dump: %s\n", _Block_byref_dump(src));    if (src->forwarding->flags & BLOCK_IS_GC) {        ;   // don't need to do any more work    }    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {        //printf("making copy\n");        // src points to stack        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));        // if its weak ask for an object (only matters under GC)        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack        // copy是拷贝到堆上的Block_byref类型对象,scr是原来的Block_byref类型对象,两者的forwarding成员都指向到堆上的Block_byref类型对象也就是copy,这样不管是在栈上修改__block修饰的变量(age.age = 102调用)还是在堆上修改__block修饰的变量()        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)        src->forwarding = copy;  // patch stack to point to heap copy        copy->size = src->size;        if (isWeak) {            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning        }        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {            // Trust copy helper to copy everything of interest            // If more than one field shows up in a byref block this is wrong XXX            copy->byref_keep = src->byref_keep;            copy->byref_destroy = src->byref_destroy;            (*src->byref_keep)(copy, src);        }        else {            // just bits.  Blast 'em using _Block_memmove in case they're __strong            _Block_memmove(                (void *)&copy->byref_keep,                (void *)&src->byref_keep,                src->size - sizeof(struct Block_byref_header));        }    }    // already copied to heap    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {        latching_incr_int(&src->forwarding->flags);    }    // assign byref data block pointer into new Block    _Block_assign(src->forwarding, (void **)destp);}

Block引用分析

Block强引用分析

// 定义类YTObject@interface YTObject : NSObject@property (nonatomic, strong) NSString *name;@property (nonatomic, copy) void (^blk)(void);- (void)testReferenceSelf;@end@implementation YTObject- (void)testReferenceSelf {    self.blk = ^ {        // 这里不管是使用self.name还是_name,从clang重写的代码上看,处理方式是一样的        printf("self.name = %s", self.name.UTF8String);    };    self.blk();}- (void)dealloc {    NSLog(@"==dealloc==");}@end// 使用YTObjectint main(int argc, const char * argv[]) {    YTObject *obj = [YTObject new];    obj.name = @"hello";    [obj testReferenceSelf];        return 0;}// 输出 :// self.name = hello

使用clang -rewrite-objc main.m命令重写为C++语法的源文件如下

static struct /*_method_list_t*/ { unsigned int entsize;  // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[6];} _OBJC_$_INSTANCE_METHODS_YTObject __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 6, {{(struct objc_selector *)"testReferenceSelf", "v16@0:8", (void *)_I_YTObject_testReferenceSelf}, {(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_YTObject_dealloc}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_YTObject_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YTObject_setName_}, {(struct objc_selector *)"blk", "@?16@0:8", (void *)_I_YTObject_blk}, {(struct objc_selector *)"setBlk:", "v24@0:8@?16", (void *)_I_YTObject_setBlk_}}};struct __YTObject__testReferenceSelf_block_impl_0 {  struct __block_impl impl;  struct __YTObject__testReferenceSelf_block_desc_0* Desc;  YTObject *self;  __YTObject__testReferenceSelf_block_impl_0(void *fp, struct __YTObject__testReferenceSelf_block_desc_0 *desc, YTObject *_self, int flags=0) : self(_self) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};static void __YTObject__testReferenceSelf_block_func_0(struct __YTObject__testReferenceSelf_block_impl_0 *__cself) {    YTObject *self = __cself->self; // bound by copy    printf("self.name = %s", ((const char * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("name")), sel_registerName("UTF8String")));}static void __YTObject__testReferenceSelf_block_copy_0(struct __YTObject__testReferenceSelf_block_impl_0*dst, struct __YTObject__testReferenceSelf_block_impl_0*src) {    _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}static void __YTObject__testReferenceSelf_block_dispose_0(struct __YTObject__testReferenceSelf_block_impl_0*src) {    _Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}static struct __YTObject__testReferenceSelf_block_desc_0 {  size_t reserved;  size_t Block_size;  void (*copy)(struct __YTObject__testReferenceSelf_block_impl_0*, struct __YTObject__testReferenceSelf_block_impl_0*);  void (*dispose)(struct __YTObject__testReferenceSelf_block_impl_0*);} __YTObject__testReferenceSelf_block_desc_0_DATA = { 0, sizeof(struct __YTObject__testReferenceSelf_block_impl_0), __YTObject__testReferenceSelf_block_copy_0, __YTObject__testReferenceSelf_block_dispose_0};static void _I_YTObject_testReferenceSelf(YTObject * self, SEL _cmd) {    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlk:"), ((void (*)())&__YTObject__testReferenceSelf_block_impl_0((void *)__YTObject__testReferenceSelf_block_func_0, &__YTObject__testReferenceSelf_block_desc_0_DATA, self, 570425344)));    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("blk"))();}int main(int argc, const char * argv[]) {    YTObject *obj = ((YTObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YTObject"), sel_registerName("new"));    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_fk_19cr58zj0f7f19001k_mxzlm0000gp_T_main_21b52d_mii_1);    ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("testReferenceSelf"));    return 0;}

Block引用对象转换为C++代码的结构体关系图:
Block引用对象转换为C++代码的结构体关系图

从类图上可以明显的看到__YTObject__testReferenceSelf_block_impl_0YTObject之间有循环依赖的关系,这样NSLog(@"==dealloc==");这段代码最终是没有调用到,也就是这里会出现Block循环引用导致内存泄漏问题

Block弱引用分析

Block的循环引用问题其中一种解决方案是可以使用weakself来解除这种强引用关系,防止内存的泄漏,代码的改造如下

@interface YTObject : NSObject@property (nonatomic, strong) NSString *name;@property (nonatomic, copy) void (^blk)(void);- (void)testReferenceSelf;@end@implementation YTObject- (void)testReferenceSelf {    __weak typeof(self) weakself = self;    self.blk = ^ {        __strong typeof(self) strongself = weakself;        // 这里不管是使用self.name还是_name,从clang重写的代码上看,处理方式是一样的        printf("self.name = %s\n", strongself.name.UTF8String);    };    self.blk();}- (void)dealloc {    printf("==dealloc==");}@end// 使用YTObjectint main(int argc, const char * argv[]) {    YTObject *obj = [YTObject new];    obj.name = @"hello";    [obj testReferenceSelf];        return 0;}

添加了weak之后需要使用clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.14 main.mm这个命令才能够重写为C++语言对应的代码,重写后的代码如下

static struct /*_method_list_t*/ {    unsigned int entsize;  // sizeof(struct _objc_method)    unsigned int method_count;    struct _objc_method method_list[6];} _OBJC_$_INSTANCE_METHODS_YTObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {    sizeof(_objc_method),    6,    {{(struct objc_selector *)"testReferenceSelf", "v16@0:8", (void *)_I_YTObject_testReferenceSelf},        {(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_YTObject_dealloc},        {(struct objc_selector *)"name", "@16@0:8", (void *)_I_YTObject_name},        {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_YTObject_setName_},        {(struct objc_selector *)"blk", "@?16@0:8", (void *)_I_YTObject_blk},        {(struct objc_selector *)"setBlk:", "v24@0:8@?16", (void *)_I_YTObject_setBlk_}}};struct __YTObject__testReferenceSelf_block_impl_0 {    struct __block_impl impl;    struct __YTObject__testReferenceSelf_block_desc_0* Desc;    YTObject *const __weak weakself;    __YTObject__testReferenceSelf_block_impl_0(void *fp, struct __YTObject__testReferenceSelf_block_desc_0 *desc, YTObject *const __weak _weakself, int flags=0) : weakself(_weakself) {        impl.isa = &_NSConcreteStackBlock;        impl.Flags = flags;        impl.FuncPtr = fp;        Desc = desc;    }};static void __YTObject__testReferenceSelf_block_func_0(struct __YTObject__testReferenceSelf_block_impl_0 *__cself) {    YTObject *const __weak weakself = __cself->weakself; // bound by copy    __attribute__((objc_ownership(strong))) typeof(self) strongself = weakself;    printf("self.name = %s\n", ((const char * _Nullable (*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)strongself, sel_registerName("name")), sel_registerName("UTF8String")));}static void __YTObject__testReferenceSelf_block_copy_0(struct __YTObject__testReferenceSelf_block_impl_0*dst, struct __YTObject__testReferenceSelf_block_impl_0*src) {    _Block_object_assign((void*)&dst->weakself, (void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);}static void __YTObject__testReferenceSelf_block_dispose_0(struct __YTObject__testReferenceSelf_block_impl_0*src) {    _Block_object_dispose((void*)src->weakself, 3/*BLOCK_FIELD_IS_OBJECT*/);}static struct __YTObject__testReferenceSelf_block_desc_0 {    size_t reserved;    size_t Block_size;    void (*copy)(struct __YTObject__testReferenceSelf_block_impl_0*, struct __YTObject__testReferenceSelf_block_impl_0*);    void (*dispose)(struct __YTObject__testReferenceSelf_block_impl_0*);} __YTObject__testReferenceSelf_block_desc_0_DATA = { 0, sizeof(struct __YTObject__testReferenceSelf_block_impl_0), __YTObject__testReferenceSelf_block_copy_0, __YTObject__testReferenceSelf_block_dispose_0};static void _I_YTObject_testReferenceSelf(YTObject * self, SEL _cmd) {    __attribute__((objc_ownership(weak))) typeof(self) weakself = self;    __YTObject__testReferenceSelf_block_impl_0 blockImpl =    __YTObject__testReferenceSelf_block_impl_0((void *)__YTObject__testReferenceSelf_block_func_0,                                               &__YTObject__testReferenceSelf_block_desc_0_DATA,                                               weakself,                                               570425344)    ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, s                                                          el_registerName("setBlk:"),                                                          ((void (*)())&blockImpl));    ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("blk"))();}int main(int argc, const char * argv[]) {    YTObject *obj = ((YTObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YTObject"), sel_registerName("new"));    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)obj, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_fk_19cr58zj0f7f19001k_mxzlm0000gp_T_main_6f39dd_mii_0);    ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("testReferenceSelf"));        return 0;}

从上面的代码中可以看到__YTObject__testReferenceSelf_block_impl_0结构体中weakself成员是一个__weak修饰的YTObject类型对象,也就是说__YTObject__testReferenceSelf_block_impl_0YTObject的依赖是弱依赖。weak修饰变量是在runtime中进行处理的,在YTObject对象的Dealloc方法中会调用weak引用的处理方法,从weak_table中寻找弱引用的依赖对象,进行清除处理,可以查看Runtime源码中objc_object::clearDeallocating该方法的处理逻辑,另外关于__weak修饰的变量的详细处理可以查看Runtime相关的知识

Block弱引用对象转换为C++代码的结构体关系图:
Block弱引用对象转换为C++代码的结构体关系图

关于具体的weakSelf和strongSelf可以参考这篇文章深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用中的描述

weakSelf 是为了block不持有self,避免Retain Circle循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个strongSelf 会自动释放,没有不会存在循环引用问题。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。

结束

以上就是关于Block底层实现的一些分析,不妥之处敬请指教

参考

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用

未经允许不得转载:ITyet » Objc Block实现分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
打开看板娘