Objc Block实现分析 原 荐

栏目: Objective-C · 发布时间: 5年前

内容简介:Block在iOS开发中使用的频率是很高的,使用的场景包括接口异步数据的回调(AFN)、UI事件的回调(BlockKits)、链式调用(Masonry)、容器数据的遍历回调(NSArray、NSDictionary),具体的用法以及使用Block的一些坑这里就不一一赘述了,本文会从源代码的层面分析我们常用的Block的底层实现原理,做到知其然知其所以然。本文会从如下几个主题切入,层层递进分析Block的底层原理,还原Block本来的面目有如下的源代码,创建一个简单的block,在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)};

// 重写后的入口函数main
int 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};

// 重写后的入口函数main
int 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++代码的结构体关系图:

Objc Block实现分析 原 荐

该重写的代码大部分和上面分析过的例子代码是类似,发现增加了两个处理方法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 指向关系如下图所示

Objc Block实现分析 原 荐

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

/*
 * 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 *)©->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

// 使用YTObject
int 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++代码的结构体关系图:

Objc Block实现分析 原 荐

从类图上可以明显的看到 __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


// 使用YTObject
int 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++代码的结构体关系图:

Objc Block实现分析 原 荐

关于具体的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解决循环引用


以上所述就是小编给大家介绍的《Objc Block实现分析 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

细节决定交互设计的成败

细节决定交互设计的成败

张亮 / 2009-3 / 49.00元

《细节决定交互设计的成败》是一本非常实用的有关软件界面的交互设计和可用性设计方面知识的书籍,通过采用一问一答的形式,你将会有针对性地学习到一些能够很快应用在自己软件开发工作中的细节知识和诀窍。例如,如何减轻用户的等待感,如何预防和减少用户的使用错误等。另外,你会发现阅读《细节决定交互设计的成败》时会非常轻松和愉悦;这是由于《细节决定交互设计的成败》写作上的两个特点:第一,采用较多日常生活中的例子来......一起来看看 《细节决定交互设计的成败》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试