iosblock游戏 (ios解析url参数)

block的本质

1. block 的基本用法

// 不带参数无返回值的block
void (^block)(void) = ^{
	NSLog(@"Hello, World!");
};

block();

// 带参数无返回值的block
void (^block)(int, int) =  ^(int a , int b) {
	NSLog(@"this is a block!");
};

 block(10, 20);

2.将 block 代码转换成 C++文件 后发现,生成了一个 __main_block_impl_0 类型的结构体, block 是指向这个结构体的指针

int age = 20;
   
void (^block)(int, int) =  ^(int a , int b){
  NSLog(@"this is a block! -- %d", age);
  NSLog(@"this is a block!");
  NSLog(@"this is a block!");
  NSLog(@"this is a block!");
};

block(10, 10);

// 转换后的C++代码
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 10;

        // 定义block变量
        // block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age)
        void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

        // 执行block内部代码
        // __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里
        // (block->FuncPtr)(block, 10, 10)
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

3. __main_block_impl_0 类型的结构体,里面包含了 __block_impl 类型的结构体变量 impl __main_block_desc_0 类型的结构体变量 Desc ,一个返回值为 __main_block_impl_0 类型的构造函数,还会生成一个age来存储外面引用的值

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
    
    // 构造函数(类似oc的init)
    // __main_block_func_0的地址传给fp
    //  : age(_age)语法会自动将_ag赋值给age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock; // isa指向的_NSConcreteStackBlock就是当前block类型
    impl.Flags = flags;
    impl.FuncPtr = fp; // 保存的就是__main_block_func_0的地址,也就是block执行逻辑的函数
    Desc = desc; // 保存的是&__main_block_desc_0_DATA的地址(主要存储的就是__main_block_impl_0的大小)
      
      // 默认返回的就是__main_block_impl_0结构体
  }
};

4. __block_impl 类型的结构体里包含 isa指针 ,说明block也是一个OC对象。在 __main_block_impl_0 的构造函数中isa指向的是 _NSConcreteStackBlock 类型的地址,侧面说明这个类型也是当前编译时的block的真实类型

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

5. __main_block_func_0 是一个封装了block执行逻辑代码的函数,在 __main_block_impl_0 的构造函数中通过参数 void *fp 赋值给 FuncPtr指针变量 ,来保存执行代码的地址

// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_3);
}

6. __main_block_desc_0 这个类型的结构体变量 __main_block_desc_0_DATA 里面的 reserved 赋值为0, Block_size 赋值为 sizeof(struct __main_block_impl_0) ,也就是当前 __main_block_impl_0 这个结构体的大小。在 __main_block_impl_0 的构造函数中通过参数 desc 赋值给 Desc

static struct __main_block_desc_0 {
  size_t reserved; // 0
  size_t Block_size; // 计算的就是__main_block_impl_0这个结构体的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

7. __main_block_impl_0 的构造函数中 : age(_age) 语法会自动将 _age 赋值给变量 int age 来保存。而且转换后的调用 __main_block_impl_0 可以直接转换为 __block_impl 类型,是因为两个类型的结构体地址是一样的,而且相当于直接把 __block_impl 里的值都放到 __main_block_impl_0

总结

  • block 本质上也是一个 OC对象 ,它内部也有个 isa指针
  • block 是封装了函数调用以及函数调用环境的 OC对象

iosblock是异步回调吗,ios中block用什么修饰符

block 的本质结构可以概括为下面这张图

iosblock是异步回调吗,ios中block用什么修饰符

block的变量捕获

为了保证 block 内部能够正常访问外部的变量, block 有个变量捕获机制

  • 局部变量默认是被 auto 修饰的,表示自动变量,离开作用域就销毁。 block 的捕获该变量是值传递
  • 局部变量被 static 修饰,会一直在内存中不被释放,block的捕获该变量是指针传递
  • 全局变量因为是一直都在内存中存在的,所以不用捕获

iosblock是异步回调吗,ios中block用什么修饰符

block的类型

  • block 有3种类型,可以通过调用 class 方法或者 isa指针 查看具体类型,最终都是继承自 NSBlock 类型 __NSGlobalBlock__ ( _NSConcreteGlobalBlock ) - __NSStackBlock__ ( _NSConcreteStackBlock ) - __NSMallocBlock__ ( _NSConcreteMallocBlock)
  • block 的真实类型都是以运行时为准的,通过 Clang 编译出的 C++ 类型不是最准确的,因为在运行时又会做了一些变动和处理。而且现在 LLVM 只会生成一种中间文件,和 Clang 生成的文件有差异

通过下面代码观察 block 的对应输出

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
       void (^block)(void) = ^{
        NSLog(@"Hello");
	    };
	    
	    NSLog(@"%@", [block class]);
	    NSLog(@"%@", [[block class] superclass]);
	    NSLog(@"%@", [[[block class] superclass] superclass]);
    }
    return 0;
}

// 对应的输出:__NSGlobalBlock__ NSBlock NSObject

不同内存区域对应的block类型不同

  • 数据段对应的是 __NSGlobalBlock__ 类型的block
  • 堆段对应的是 __NSMallocBlock__ 类型的block
  • 栈段对应的是 __NSStackBlock__ 类型的block

iosblock是异步回调吗,ios中block用什么修饰符

不同操作对应的block类型不同

  • 没有访问自动变量的block的类型是 __NSGlobalBlock__
  • 访问了自动变量的block的类型是 __NSStackBlock__
  • __NSStackBlock__ 的block调用了copy后类型会变为 __NSMallocBlock__

iosblock是异步回调吗,ios中block用什么修饰符

每一种类型的block调用 copy 后的结果如下所示

iosblock是异步回调吗,ios中block用什么修饰符

修改Xcode的 Build Setting->Objective-C Automatic Reference Counting No,使编译环境为MRC,然后输出下面代码可以查看block对应的类型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        
        // 堆:动态分配内存,需要程序员申请申请,也需要程序员自己管理内存
        void (^block1)(void) = ^{
            NSLog(@"Hello");
        };
        
        int age = 10;
        void (^block2)(void) = ^{
            NSLog(@"Hello - %d", age);
        };
        
        NSLog(@"%@ %@ %@", [block1 class], [[block2 copy] class], [^{
            NSLog(@"%d", age);
        } class]);
    }
    return 0;
}

// 对应的输出:__NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__

注意:block2在MRC环境下的类型为 __NSStackBlock__ ,是存储在栈段的。只有通过copy修饰才会变成 __NSMallocBlock__ ,存储在堆中。在ARC环境下即使不用copy修饰类型也是 __NSMallocBlock__ ,因为编译器会视情况自动进行copy操作

block的copy操作

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上

1. block 作为 函数返回值

如果不进行 copy操作 myblock 内部的 block返回值 作用域一结束就会被释放

typedef void (^Block)(void);

Block myblock()
{
    int age = 10;
    return ^{
        NSLog(@"---------%d", age);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       Block block = myblock();
        block();
        NSLog(@"%@", [block class]); // __NSMallocBlock__
    }
    return 0;
}

2.将 block 赋值给 __strong 指针时

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
       
        // 强指针Block block
        Block block = ^{
            NSLog(@"---------%d", age);
        };
        
        NSLog(@"%@", [block class]); // __NSMallocBlock__
    }
    return 0;
}

3. block 作为 Cocoa API 中方法名含有 usingBlock 的方法参数时

NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
          
}];

4. block 作为 GCD API 的方法参数时

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
  
});
   
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  
});

不同环境下block属性的写法

1. MRC block 属性的建议写法

@property (copy, nonatomic) void (^block)(void);

2. ARC block 属性的建议写法

// 因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性@property (strong, nonatomic) void (^block)(void);@property (copy, nonatomic) void (^block)(void);

对象类型的auto变量

当block内部访问了对象类型的auto变量时1.如果block是在栈上,将不会对auto变量产生强引用

@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person

- (void)dealloc
{
    [super dealloc];
    NSLog(@"Person - dealloc");
}
@end

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
            block = ^{
                NSLog(@"---------%d", person.age);
            };
            
            // MRC环境下对应的内存管理
            [person release];
            NSLog(@"------%@", [block class]);
        }
        
        // 在这里打断点,由于MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放
        NSLog(@"------");
    }
    return 0;
}

2.如果 block 被拷贝到堆上,会根据 auto变量 的修饰符 (__strong、__weak、__unsafe_unretained) 做出相应的操作

@interface Person : NSObject
@property (assign, nonatomic) int age;
@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person - dealloc");
}
@end

typedef void (^Block)(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Block block;
        
        {
            Person *person = [[Person alloc] init];
            person.age = 10;
            
           // __strong Person *weakPerson = person;
            __weak Person *weakPerson = person;
            
            block = ^{
                NSLog(@"---------%d", weakPerson);
            };
            
            NSLog(@"------%@", [block class]);
        }
        
        // 在这里打断点,在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放
        NSLog(@"------");
    }
    return 0;
}

将上面代码文件转换成 C++ 文件可以看出, block 内部的 __main_block_desc_0 结构体会调用 copy函数 copy函数 内部会调用 _Block_object_assign 函数,而 _Block_object_assign 函数会根据auto变量的修饰符 (__strong、__weak、__unsafe_unretained) 做出相应的操作,形成强引用(retain)或者弱引用

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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

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*/);}

3.如果 block 从堆上移除,会调用 block 内部的 dispose函数 dispose函数 内部会调用 _Block_object_dispose 函数, _Block_object_dispose 函数会自动释放引用的auto变量(release)

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

4. block 只有引用的是基本数据类型才不会生成 copy dispose 函数

5.如果用 static 修饰对象类型,那么生成的 C++ 代码如下

Block block;        
{
    static NSString *string = @"haha";
    block = ^{
        NSLog(@"---------%@", string);
    };
}

// 生成的C++代码        
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  // string变量的类型是NSString **
  NSString *__strong *string;
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString *__strong *_string, int flags=0) : string(_string) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

注意:代码里有 __weak ,转换C++文件可能会报错 cannot create __weak reference in file using manual reference ,可以指定支持ARC、指定运行时系统版本

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 源文件
```### __block修饰符

#### __block修饰基本数据类型
看下面代码,怎样可以在`block`内部修改`age`的值


typedef void (^Block)(void);

int main(int argc, const char * argv[]) {@autoreleasepool {

    int age = 10;
    
    Block block1 = ^{
        // age = 20;
        NSLog(@"age is %d", age);
    };
    
    block1();
    
    NSLog(@"age的内存地址 - %p", &age);
}
return 0;

}


1.用`static`来修饰`age属性`,`block`内部引用的是`age`的地址值,可以根据地址去修改`age`的值。但不好的是`age属性`会一直存放在内存中不销毁,造成多余的内存占用,而且会改变`age属性`的性质,不再是一个`auto变量`了


static int age = 10;


2.用`__block`来修饰属性,底层会生成`__Block_byref_age_0`类型的结构体对象,里面存储着`age`的真实值


__block int age = 10;


3.转换成`C++文件`来查看内部结构,会根据`__main_block_impl_0`里生成的`age`对象来修改内部的成员变量`age`而且在外面打印的`age`属性的地址值也是`__Block_byref_age_0`结构体里的成员变量`age`的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值


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

int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

    // __Block_byref_age_0 age = {0, &age, 0, sizeof(__Block_byref_age_0), 10};  
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};

    Block block1 = ((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 *)block1)->FuncPtr)((__block_impl *)block1);


}
return 0;

}


##### 总结:

- `__block`可以用于解决`block`内部无法修改`auto`变量值的问题
- 编译器会将`__block`变量包装成一个对象
- 其实修改的变量是`__block`生成的对象里面存储的变量的值,而不是外面的`auto变量`,但是内部生成的相同的变量的地址和外面的`auto变量`地址值是一样的,所以修改了内部的变量也会修改了外面的`auto变量`
- `__block`不能修饰全局变量、静态变量(static)

##### __block的内存管理
1.程序编译时,`block`和`__block`都是在栈中的,这时并不会对`__block`变量产生强引用

2.因为`__block`也会包装成`OC对象`,所以`block`底层也会生成`copy函数`和`dispose函数`  

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


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

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`修饰的变量因为被包装成了`OC对象`,所以也会被拷贝到堆上,如果再有`block`强引用`__block`,由于`__block`变量已经拷贝到堆上了,就不会再拷贝了,下图可以很好的表达出关系

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131050-2137722604.jpg)

3.当`block`从堆中移除时,会调用`block`内部的`dispose函数`,`dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放引用的`__block`变量(release)


static void __main_block_dispose_0(struct __main_block_impl_0 src) {_Block_object_dispose((void )src->age, 8/ BLOCK_FIELD_IS_BYREF /);}


如果有多个`block`同时持有着`__block`变量,那么只有所有的`block`都从堆中移除了,`__block`变量才会被释放

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024131097-86614401.jpg)

##### __block和OC对象在block中的区别

看下面的代码,在`block`中的本质区别是什么


__block int age = 10;NSObject *obj = [[NSObject alloc] init];

Block block1 = ^{age = 20;

NSLog(@"age is %d", age);NSLog(@"obj is %p", obj);};


转成`C++文件`发现,`__block`生成的对象就是强引用,而`NSObject`对象会根据修饰符`__strong`或者`__weak`来区分是否要进行`retain操作`


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

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_object_assign((void )&dst->obj, (void )src->obj, 3/ BLOCK_FIELD_IS_OBJECT /);}

**注意:`__weak`不能修饰基本数据类型,编译器会报`__weak' only applies to Objective-C object or block pointer types; type here is 'int'`警告**

##### __forwarding指针
- 在栈中,`__block`中的`__forwarding指针`指向自己的内存地址
- 复制到堆中之后,`__forwarding指针`指向堆中的`__block`
- 堆中的`__forwarding`指向堆中的`__block`
- 这样的目的都是为了不论访问的`__block`是在栈上还是在堆上,都可以通过`__forwarding指针`找到存储在堆中的`auto变量`

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130733-2067416101.jpg)

#### __block修饰对象类型
1.看下面代码,用`__block`修饰的对象类型什么时候被释放


typedef void (^Block)(void);

int main(int argc, const char * argv[]) {@autoreleasepool {Block block;

    {
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        __block Person *weakPerson = person;

        block = ^{
            NSLog(@"---------%d", weakPerson.age);
        };
        
       
        NSLog(@"------%@", [block class]);
    }
    
    // 在这里打断点观察person是否会被释放
    NSLog(@"------");
}
return 0;

}


2.转换成`C++文件`可以发现,`__block`底层生成的结构体里面会引用着该对象类型,并且默认是用`__strong`来修饰,而且内部也会对应的生成`copy`和`dispose`函数


struct __Block_byref_weakPerson_0 {void __isa;__Block_byref_weakPerson_0 __forwarding;int __flags;int __size;void (__Block_byref_id_object_copy)(void , void );void ( __Block_byref_id_object_dispose)(void*);Person *__strong weakPerson;};


3.我们看`main函数`里会将`__Block_byref_id_object_copy_131`和`__Block_byref_id_object_dispose_131`赋值给`__Block_byref_weakPerson_0`这个结构体对象


int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;Block block;

    {
        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, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);

        // __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
        __attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};

        block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakPerson_0 *)&weakPerson, 570425344));


        NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
    }

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_wn_kcs2c07n3mqd02d77tvjgtjr0000gn_T_main_8be7f8_mi_2);
}
return 0;

}


4.找到这两个值能发现也是分别会调用`_Block_object_assign`和`_Block_object_dispose`这两个函数,而且传的对象就是`__Block_byref_weakPerson_0`内部的`weakPerson`这个对象,也就是说这个结构体内部也会对`weakPerson`这个对象进行着`retain`和`release`的操作


static void __Block_byref_id_object_copy_131(void *dst, void src) {_Block_object_assign((char )dst + 40, *(void * ) ((char )src + 40), 131);}static void __Block_byref_id_object_dispose_131(void src) {_Block_object_dispose( (void * ) ((char )src + 40), 131);


5.我们把第一段代码中的`weakPerson`加上`__weak`修饰符,再运行程序会发现,当作用域结束后,`person`对象也会被释放了


typedef void (^Block)(void);

int main(int argc, const char * argv[]) {@autoreleasepool {Block block;

    {
        Person *person = [[Person alloc] init];
        person.age = 10;
        
        __block __weak Person *weakPerson = person;

        block = ^{
            NSLog(@"---------%d", weakPerson.age);
        };
        
       
        NSLog(@"------%@", [block class]);
    }
    
    // 在这里打断点观察person是否会被释放
    NSLog(@"------");
}
return 0;

}


6.我们转换成`C++文件`能发现,`__Block_byref_weakPerson_0`里面的`person`对象修饰符变成了`__weak`


struct __Block_byref_weakPerson_0 {void __isa;__Block_byref_weakPerson_0 __forwarding;int __flags;int __size;void (__Block_byref_id_object_copy)(void , void );void ( __Block_byref_id_object_dispose)(void*);Person *__weak weakPerson;};


##### 总结:
- `__block`修饰的对象类型也会生成一个新的结构体对象,并且只会被`block`进行强引用,同`__block`修饰基本数据类型是一样的
- `__block`内部也会生成该对象类型的成员变量,而且会根据不同的修饰符`__strong`和`__weak`来对应着该对象类型是否被强引用
- `__block`内部也会生成`copy`和`dispose`函数
	- 当`__block`变量被`copy`到堆时,会调用`__block`变量内部的`copy函数`,`copy函数`内部会调用`_Block_object_assign`函数,`_Block_object_assign`函数会根据所指向对象的修饰符`(__strong、__weak、__unsafe_unretained)`做出相应的操作,形成强引用(retain)或者弱引用
	- 如果`__block`变量从堆上移除,会调用`__block`变量内部的`dispose函数`,`dispose函数`内部会调用`_Block_object_dispose`函数,`_Block_object_dispose`函数会自动释放指向的对象(release)

**注意:在MRC环境下即使用\_\_block修饰,\_\_block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,\_\_block修饰的变量就已经释放了,这点和在ARC环境下不同**

### 循环引用
`block`在使用中很容易就会造成循环引用问题,例如下面的代码


typedef void (^Block) (void);

@interface Person : NSObject@property (copy, nonatomic) Block block;@property (assign, nonatomic) int age;

  • (void)test;@end

@implementation Person

  • (void)test {// 内部循环引用self.block = ^{NSLog(@"age is %d", self.age);};}@end

int main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init];person.age = 10;

    // 循环引用
    person.block = ^{
        NSLog(@"age is %d", person.age);
    };
}

NSLog(@"111111111111");
return 0;

}


`person`对象里面的`block`属性强引用着`block`对象,而`block`对象内部也会有一个`person`的成员变量指向这个`Person对象`,这样就会造成循环引用,谁也无法释放

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130088-1235768827.jpg)


#### 解决方法
##### 在ARC环境下
让其中一个指针变成弱引用

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024129668-1990190805.jpg)

1.用`__weak`解决,不会产生强引用,当指向的对象销毁时,会自动让指针置为`nil`


@implementation Person

  • (void)test {__weak typeof(self) weakSelf = self;
  • self.block = ^{NSLog(@"age is %d", weakSelf.age);};}@end

int main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init];person.age = 10;

    // __weak Person *weakPerson = person;
    __weak typeof(person) weakPerson = person;
    person.block = ^{
        NSLog(@"age is %d", weakPerson.age);
    };
}

NSLog(@"111111111111");
return 0;

}


2.用`__unsafe_unretained`解决,不会产生强引用,但是是不安全的,当指向的对象销毁时,指针存储的地址值不变,仍然是指向着那块已经被回收的内存空间,那么再访问这个这个变量就会造成野指针错误


@implementation Person

  • (void)test {__unsafe_unretained typeof(self) weakSelf = self;
  • self.block = ^{NSLog(@"age is %d", weakSelf.age);};}@end

int main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init];person.age = 10;

    __unsafe_unretained Person *weakPerson = person;
    person.block = ^{
        NSLog(@"age is %d", weakPerson.age);
    };
}

NSLog(@"111111111111");
return 0;

}


3.用``__block``解决,用`__block`修饰对象会造成三者相互引用造成循环引用,需要手动调用block


int main(int argc, const char * argv[]) {@autoreleasepool {__block Person *person = [[Person alloc] init];person.age = 10;

    person.block = ^{
        NSLog(@"age is %d", person.age);
        person = nil;
    };
}

person.block();

NSLog(@"111111111111");
return 0;

}


`block`内部也需要手动将`person`置空,这个`person`是`__block`内部生成的指向`Person对象`的变量

![](https://img2020.cnblogs.com/blog/2320802/202104/2320802-20210407024130614-1682005821.jpg)

##### 在MRC环境下
1.用`__unsafe_unretained`解决,同`ARC环境下`一样,只是`MRC`不支持`__weak`


Person *person = [[Person alloc] init];__unsafe_unretained typeof(person) weakPerson = person;

person.block = [^{NSLog(@"age is %d", weakPerson.age);} copy];

[person release];

2.用`__block`解决,在`MRC`中,`__block`对象里是不会对`person对象`进行强引用的,所以不会造成循环引用


__block Person *person = [[Person alloc] init];person.age = 10;

person.block = [^{NSLog(@"age is %d", person.age);} copy];

[person release];

### 面试题
#### 1.看下面代码,分别输入的值是什么


int a = 10;static int b = 10;

int main(int argc, const char * argv[]) {@autoreleasepool {

	auto int age = 10;
	static int height = 10;
	
	void (^block)(void) = ^{
		NSLog(@"age is %d, height is %d", age, height);
		NSLog(@"a is %d, b is %d", a, b);
	};
	
	age = 20;
	height = 20;
	a = 20;
	b = 20;
	
	block();
	    
	// 输出结果为:age=10,height=20,a=20,b=20 
}
return 0;

}


`age`是自动变量,是值传递

`height`表示的是指针传递,`block`捕获的是该变量的地址

而`a、b`都为全局变量,所以`block`根本不用捕获,需要时直接拿取当前最新的值就可以了


int a = 10;static int b = 10;

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;int *height;

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};

int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;auto int age = 10;static int height = 10;

	void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));

	age = 20;
	height = 20;
	a = 20;
	b = 20;

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

}


#### 2.看下面代码,block内部会不会捕获self


@interface Person : NSObject

@property (copy, nonatomic) NSString *name;

  • (instancetype)initWithName:(NSString *)name;@end

@implementation Person

  • (void)test{void (^block)(void) = ^{NSLog(@"-------%d", [self name]);};block();}
  • (instancetype)initWithName:(NSString *)name{if (self = [super init]) {self.name = name;}return self;}

@end


会捕获。因为`self`本质也是一个局部变量,`block`内部会生成一个变量来保存`Person对象`的地址


struct __Person__test_block_impl_0 {struct __block_impl impl;struct __Person__test_block_desc_0* Desc;Person *self;__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}};

// 函数都会生成隐式参数self和_cmdstatic void _I_Person_test(Person * self, SEL _cmd) {void ( block)(void) = ((void ( )())&__Person__test_block_impl_0((void )__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));((void ( )(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}


#### 3.\_\_block的作用是什么?有什么使用注意点

可以将修饰的变量包装成一个对象,解决在`block`内部无法修改外部变量的问题。

`__block`内部会进行内存管理,还有在`MRC环境下`是不会对对象进行强引用

#### 4.block的属性修饰词为什么是copy?使用block有哪些使用注意?

`block`一旦没有进行`copy操作`,就不会在堆上。放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理。

注意循环引用