Block底层详解

1.前言

本文旨在对block底层进行详细的探究,源码参考libclosure-79。

2.Block的类型

2.1.类型归纳

block类型环境
__ NSGlobalBlock__ (_NSConcreteGlobalBlock)没有访问auto变量
__ NSStackBlock__ (_NSConcreteStackBlock )访问了auto变量
__ NSMallocBlock__ ( _NSConcreteMallocBlock)__NSStackBlock__调用了copy

三种Block在内存的中位置如下:
在这里插入图片描述

2.2. __ NSGlobalBlock__ 类型

2.2.1.论证没有访问auto变量的Block是 __ NSGlobalBlock__类型

dispatch_block_t globalBlock = ^{
	NSLog(@"this is global block");
};
NSLog(@"%@", [globalBlock class]);
globalBlock();

Console输出如下:
2022-03-04 17:40:34.791202+0800 Test[36063:420956] __ NSGlobalBlock__
2022-03-04 17:40:34.792149+0800 Test[36063:420956] this is global block

2.2.2.底层探究

对globalBlock所在的文件执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m [-o xxx.cpp ],上面的代码会编译成下面的代码

//基本数据结构
struct __block_impl {
  void *isa; //说明block是oc对象
  int Flags;
  int Reserved;
  void *FuncPtr; //函数地址
};

//block的描述
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size; //block的size
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

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; //clang生成的是中间文件,以运行时为准
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//具体执行的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_e49a1b_mi_0);
}

/*执行代码 start*/
//block初始化
dispatch_block_t globalBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 

//获取globalBlock的class
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_e49a1b_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)globalBlock, sel_registerName("class")));

//调用__main_block_func_0函数, 并block作为参数传递
((void (*)(__block_impl *))((__block_impl *)globalBlock)->FuncPtr)((__block_impl *)globalBlock);
/*执行代码 end*/

2.3 __ NSStackBlock__ 类型

2.3.1.论证访问auto变量的Block是 __ NSStackBlock__类型

//开启MRC, ARC下被强引用的block, 会自动拷贝到堆上,成为__ NSMallocBlock__类型
int  age = 20;
dispatch_block_t stackBlock = ^{
	NSLog(@"this is stack block, age:%d", );
};
NSLog(@"%@", [stackBlock class]);
stackBlock();

Console输出如下:
2022-03-04 17:40:34.791202+0800 Test[36063:420956] __ NSStackBlock__
2022-03-04 17:40:34.792149+0800 Test[36063:420956] this is stack block, age:20

2.3.2.底层探究

对stackBlock所在的文件执行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m [-o xxx.cpp ],上面的代码会编译成下面的代码

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_9f69a9_mi_0, age);
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;  //capture 了age auto变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/*执行代码 start*/
 int age = 20;
 //block初始化, age以值的形式传递
 dispatch_block_t stackBlock = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

//获取stackBlock的class
NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_9f69a9_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)stackBlock, sel_registerName("class")));

//调用__main_block_func_0函数, 并block作为参数传递
((void (*)(__block_impl *))((__block_impl *)stackBlock)->FuncPtr)((__block_impl *)stackBlock);
/*执行代码 end*/

哪为啥访问了auto变量就成了__ NSStackBlock__ 类型?原因是auto变量是存在栈当中,函数执行完就会销毁,如果block还是__ NSGobalBlock__,auto变量销毁了再执行block,就会出现野指针。

2.4__ NSMallocBlock__ 类型

2.3.1.论证__NSStackBlock__调用了copy就变成__ NSMallocBlock__ 类型

//开启MRC, ARC下被强引用的__NSStackBlock__类型block, 会自动拷贝到堆上,成为__ NSMallocBlock__类型
int  age = 20;
dispatch_block_t mallocBlock = [^{
	NSLog(@"this is malloc block, age:%d", );
} copy];
NSLog(@"%@", [mallocBlock class]);
mallocBlock();

Console输出如下:
2022-03-07 16:45:51.057649+0800 Test[78667:918108] NSMallocBlock
2022-03-07 16:45:51.058730+0800 Test[78667:918108] this is malloc block, age:20

2.3.2 底层探究

基本和__ NSStackBlock__ 一致,就不做研究了。

3.Block底层数据结构

3.1 整体如下图

请添加图片描述

3.2 源码探究和分析

Block的定义在Block_private.h中

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
#endif

    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler, 是否有copy和dispose方法
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler,是否全局Block
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler,是否有签名
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
	void(*copy)(void *, const void *);
	void(*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature; 
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void * isa; //删除了__ptrauth_objc_isa_pointer, 表示Block的类型
    volatile int32_t flags; // 第一个枚举中定义的值
    int32_t reserved; //保留位
    void (*invoke)(void *, ...); //函数指针,指向block具体的执行函数
    /*
    descriptor 描述Block size、copy、dispose、signature、layout等信息,但copy、dispose只有引用了对象才会出现,所以Block_layout只出现了Block_descriptor_1
	*/
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

在ARC下 Block中访问了__block修饰的变量还有以下数据结构

// Values for Block_byref->flags to describe __block variables
enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime

    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler

    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime

    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
};

struct Block_byref {
    void * isa;
    struct Block_byref *forwarding; //保证栈和堆的变量访问的数据都是正确的,具体可以见下面的forwarding指针
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    void(*copy)(struct Block_byref*, struct Block_byref*);
    void(*dispose)(struct Block_byref *);
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

举例说明

	__block int age = 20;
    dispatch_block_t block = ^{
        NSLog(@"age:%d", age);
    };
    block();

clang之后代码如下:

//__Block_byref 类型,对age变量进行包装
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
 NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_350481_mi_0, (age->__forwarding->age));
    }
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*/);}

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

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/*执行代码 start*/
 __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};

dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
    
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/*执行代码 end*/

4.Block的变量捕获和访问方式

在这里插入图片描述

4.1 局部变量的捕获

值传递

4.1.1.auto对象局部变量

代码块

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Person
@end

int main(int argc, char * argv[]) {
    Person *person = [[Person alloc] init];
    person.name = @"abc";
    void (^block)() = ^{
        NSLog(@"name:%@", person.name);
    };
  
    block();
}

clang之后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person; //auto对象局部变量是强引用
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *person = __cself->person; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_f5ba61_mii_1, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("name")));
}

//通过_Block_object_assign做内存管理
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

//通过_Block_object_dispose做内存管理
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

int main(int argc, char * argv[]) {
    Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)person, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_f5ba61_mii_0);
   
    void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

捕获auto对象局部变量,是需要处理内存管理, 主要由_Block_object_assign和_Block_object_dispose来完成,下面对这两个函数的源码

  1. _Block_object_assign源码
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

enum {
    BLOCK_ALL_COPY_DISPOSE_FLAGS = 
        BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
        BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};

void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT: //捕获的是对象
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK: //捕获的是Block
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: 
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/

        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}
  1. _Block_object_dispose源码
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

4.1.3.static局部变量

指针传递

    static int age = 20;
    dispatch_block_t block = ^{
        NSLog(@"age:%d", age);
    };
    block();

clang 之后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *age;  //是个指针
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *age = __cself->age; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders__j_l1dmslx50997l6xfyb2pfh7m0000gp_T_main_938f88_mi_0, (*age));
    }

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)};
/*执行代码 start*/
 static int age = 20;
 
 dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &age)); //指针传递
     
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/*执行代码 end*/

看clang之后的代码,确实把static局部变量捕获到Block内部,且是指针访问。那是为啥呢?

  1. 把static局部变量捕获到Block内部,原因是Block 可能在其他的场景执行
  2. 指针访问,原因是首先static的作用域是定义它的函数或语句块结束时,其作用域随之结束,所以Block也是它的作用域,Block是可以访问和修改的,另外static变量是存在数据区,函数执行完不会销毁,不用担心野指针

4.2全局变量

直接访问

int age = 10;
int main(int argc, char * argv[]) {
    dispatch_block_t block = ^{
        age++;
    };
}

clang之后

int age = 10;

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
	age++;
}

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)};
int main(int argc, char * argv[]) {
    dispatch_block_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}

可以看出__main_block_impl_0 没有增加 age变量,中间没有age的传递,在__main_block_func_0中是直接访问的

5.Block的copy

对三种Block类型执行copy后的结果
在这里插入图片描述
注意事项:
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:

  1. block作为函数返回值时行
  2. 将block赋值给__strong指针时
  3. block作为Cocoa API中方法名含有usingBlock的方法参数时
  4. block作为GCD API的方法参数时

6.__block修饰符

  1. __block可以用于解决block内部无法修改auto变量值的问题
  2. __block不能修饰全局变量、静态变量(static)
  3. 编译器会将__block变量包装成一个对象,Block捕获包装后的对象,包装后的对象不能修改,但里面的属性是可以的修改, 参考代码如下:
__block int age = 20;
dispatch_block_t block = ^{
	NSLog(@"age:%d", age);
};
//age被包装成__Block_byref_age_0, 自己成为__Block_byref_age_0的属性
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  ...
};

6.2.内存管理

  1. 当block被copy到堆时
    a.会调用block内部的copy函数
    b.copy函数内部会调用_Block_object_assign函数
    c._Block_object_assign函数会对__block变量形成强引用(retain)

在这里插入图片描述

在这里插入图片描述

6.3.__forwarding指针

在这里插入图片描述

在这里插入图片描述在这里插入图片描述__forwarding存在的意义:保证当栈上的__block变量中的__forwarding copy到堆之后,栈上的__block变量通过__forwarding还能正确访问数据。

7.常见问题

  1. 循环引用
self.block = ^{
	NSLog(@"%@", self);
};

在这里插入图片描述
解决方式

//ARC
    __weak typeof(self) weakSelf = self; //__unsafe_unretained
    self.block = ^{
        NSLog(@"%@", weakSelf);
    };
//MRC
   __block id weakSelf = self; //__unsafe_unretained
   self.block = ^{
       NSLog(@"%@", weakSelf);
   };

在这里插入图片描述

  1. __weak clang 失败,换成 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-12.0.0 xxx.m [-o xxx.cpp ] 即可 //12.0.0 只是一个版本,可以换成其它的
  2. Thread 1: EXC_BAD_ACCESS (code=1, address=0x10) //64bits address=0x10, 32bits address=0x0c
//出现的场景如下
dispatch_block_t block = nil;
block();

//原因,看clang之后的数据结构,就一目了然
struct __block_impl {
  void *isa; //64bits 8, 32bits 4
  int Flags;//64bits 4, 32bits 4
  int Reserved;//64bits 4, 32bits 4
  void *FuncPtr; //执行block调用的是这个函数地址
};
  1. MRC下数组添加Block, 访问可能Crash
    请添加图片描述
    为什么第二个会Crash呢,原因是第一个Block是__ NSGlobalBlock__ 一直存在数据区域, 第二个是 __ NSStackBlock__ 在栈上,添加到数组不会拷贝到堆上,函数执行完就会销毁。

8.总结

  1. block本质上也是一个OC对象,它内部也有个isa指针。
  2. block是封装了函数调用以及函数调用环境的OC对象。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值