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实现分析 原 荐》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

零售的哲学:7-Eleven便利店创始人自述

零售的哲学:7-Eleven便利店创始人自述

[日] 铃木敏文 / 顾晓琳 / 江苏文艺出版社 / 2014-12-1 / 36

全球最大的便利店连锁公司创始人——铃木敏文,结合40多年零售经验,为你讲述击中消费心理的零售哲学。铃木敏文的很多创新,现在已经成为商界常识,本书把那些不可思议的零售创新娓娓道来。关于零售的一切:选址、订货、销售、物流、管理……他一次又一次地在一片反对声中创造出零售界的新纪录。 翻开本书,看铃木敏文如何领导7-11冲破层层阻碍,成为世界第一的零售哲学。一起来看看 《零售的哲学:7-Eleven便利店创始人自述》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具