iOS Block本质笔记

OC中定义block

void(^myBlock)(NSString *) = ^(NSString *param){
};

block访问外部参数

int age = 10;
static NSString *name = @"Andy";
NSString *address = @"India";

void(^myBlock)(NSString *) = ^(NSString *param){
    
    name = @"Alandy";
    NSLog(@"name %@, age %@, address %@", name, @(age), address);
};

OC转C++分析

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

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSString **name;
  int age;
  NSString *address;
  __main_block_impl_0(
  void *fp, 
  struct __main_block_desc_0 *desc, 
  NSString **_name, int _age, NSString *_address, int flags=0) : name(_name), age(_age), address(_address) {
    // name(_name)的意思是把传入的_name赋值给外面的成员name
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

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

static void 
__main_block_func_0(struct __main_block_impl_0 *__cself, NSString *param) {

  NSString **name = __cself->name; // bound by copy
  int age = __cself->age; // bound by copy
  NSString *address = __cself->address; // bound by copy

  (*name) = (NSString *)&__NSConstantStringImpl__var_main_2;

  NSLog(
  &__NSConstantStringImpl__var_main_3, (*name), 
  objc_msgSend(objc_getClass("NSNumber"),sel_registerName("numberWithInt:"),age), 
  address);
}
static void 
__main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src) {
  _Block_object_assign((void*)&dst->name, (void*)src->name, 3);
  _Block_object_assign((void*)&dst->address, (void*)src->address, 3);
}
static void 
__main_block_dispose_0(struct __main_block_impl_0 *src) {
    _Block_object_dispose((void*)src->name, 3);
    _Block_object_dispose((void*)src->address, 3);
}

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 age = 10;
static NSString *name = (NSString *)&__NSConstantStringImpl__var_main_0;
NSString *address = (NSString *)&__NSConstantStringImpl__var_main_1;
void(*myBlock)(NSString *) = 
((void (*)(NSString *))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &name, age, address, 570425344));
-----------
简化一下 myBlock
void(*myBlock)(NSString *) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &name, age, address, 570425344);
  • block的变量捕获机制

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


int a = 1;等价于 auto int a = 1;
自动变量,离开作用域就销毁

static修饰的变量一直存在内存中不会被释放,在block内部可以直接访问,被捕获到block内部的static变量会变成*或**类型,例如:block中捕获了static修饰的name

struct __main_block_impl_0 {
  ...
  NSString **name;
  ...
};
  • block的类型

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


验证一下:

void(^myBlock)(NSString *) = ^(NSString *param){};
[myBlock class] 的返回值是 __NSGlobalBlock__
Class superCls = [myBlock superclass];
superCls 的值是 NSBlock
[superCls superclass] 的返回值是 NSObject
[[superCls superclass] superclass] 的返回值为 null

那么block的类型就是__NSGlobalBlock__了吗?不止如此
换一种block的用法:

int height = 173;
void(^myBlock)(int) = ^(int param){
    NSLog(@"height %d", height);
};

再通过NSLog打印出的结果是__NSMallocBlock__
再换一种block的用法:

NSLog(@"%@", [^{NSLog(@"%d", height);} class]);

这种直接方式打印出的结果是__NSStackBlock__
通过clang编译出来的block结果与打印出来的结果不同,以NSLog运行时打印的结果为准,原因与llvm的编译器有关系

通过这样的方式得到的block有3种类型分别是

注意:
(在ARC模式下,由于编译器增加了一些东西,导致在block内部访问了auto变量的时候,block变成了__NSMallocBlock__,在MRC下是__NSStackBlock__)

  • 在ARC模式下把block作为属性使用时用copy和strong修饰都可以
@property (nonatomic, copy) void (^block)(void);
or
@property (nonatomic, strong) void (^block)(void);
  • ARC模式下系统带有block的方法都是默认进行了copy操作的例如
-[NSArray enumerateObjectsUsingBlock:[block copy]];

有使用到__weak等修饰词的代码,OC文件转C++的时候需要额外添加一些内容

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

  1. 只要是在栈上的block(__NSStackBlock__)都不会对外部的对象进行强引用
  2. 如果block被拷贝到堆上
    • 会调用block中的__main_block_copy_0函数
    • __main_block_copy_0函数又会调用_Block_object_assign函数
    • _Block_object_assign中会根据外部的auto变量使用的修饰符(__weak__strongunsafe_unretained)做出相应的操作(强引用、弱引用)
  3. 如果block从堆上移除
    • 会调用block内部的__main_block_dispose_0函数
    • __main_block_dispose_0会调用_Block_object_dispose函数去释放block中引用的auto变量,类似release
  • 用__block修饰变量

被__block修饰过得变量结构会发生变化

__block int height = 173;
本质是:
__attribute__((__blocks__(byref))) __Block_byref_height_0 height = {(void*)0,(__Block_byref_height_0 *)&height, 0, sizeof(__Block_byref_height_0), 173};
简化一下:
__Block_byref_height_0 height =
{
  0,
  &height,
  0,
  sizeof(__Block_byref_height_0), 
  173
};

struct __Block_byref_height_0 {
  void *__isa;
  __Block_byref_height_0 *__forwarding;
  int __flags;
  int __size;
  int height;
};
---------------- 分隔线 ----------------
__block NSObject *obj = [NSObject new];
本质是:
__attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
简化一下:
__Block_byref_obj_1 obj  = 
{
  0, 
  &obj, 
  33554432, 
  sizeof(__Block_byref_obj_1), 
  __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, 
  objc_msgSend(objc_getClass("NSObject"), sel_registerName("new"))
};

struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};
^{   
    obj = nil;
    height = 175;
}
这段block中的代码本质是:
在__main_block_func_0中的
(obj->__forwarding->obj) = __null;
(height->__forwarding->height) = 175;

有个一直没解决的问题:代码中使用__block修饰NSString *之后,转换C++文件会报错,不知道怎么处理!!!求指教

  • 创建__block修饰的NSArray对象
    __block NSArray *arr = @[@"1111"];
block中修改arr的值
^{        
   arr = @[@"22222"];
};

转换成C++

__Block_byref_arr_2 arr = 
{
 0,
 &arr, 
 33554432, 
 sizeof(__Block_byref_arr_2), 
 __Block_byref_id_object_copy_131, 
 __Block_byref_id_object_dispose_131, 
 objc_msgSend(
   objc_getClass("NSArray"), 
   sel_registerName("arrayWithObjects:count:"), 
   __NSContainer_literal(1U, &__NSConstantStringImpl__var_main_0).arr, 1U)
};
  • __main_block_func_0
(arr->__forwarding->arr) = objc_msgSend(
 objc_getClass("NSArray"), 
 sel_registerName("arrayWithObjects:count:"), 
 __NSContainer_literal(1U, &__NSConstantStringImpl__var_main_1).arr, 1U
);

综上看来被__block修饰的变量会把变量包装成一个结构体来使用

__block int height = 173;

struct __Block_byref_height_0 {
  void *__isa;
  __Block_byref_height_0 *__forwarding;
  int __flags;
  int __size;
  int height;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_height_0 *height; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_height_0 *_height, int flags=0) : height(_height->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 基本数据类型的变量如果没有被__block修饰过,不会产生copy和dispose函数
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};

// block中执行的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_height_0 *height = __cself->height;
  (height->__forwarding->height) = 175;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
  _Block_object_assign(&dst->height, src->height, 8);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
  _Block_object_dispose(src->height, 8);
}
被__block修饰的height变量的实际地址和__Block_byref_height_0中的height相同。
  • __block的内存管理

    1. 当block在栈上(__NSStackBlock__),并不会对__block变量产生强引用
    2. 当block被copy到堆时(__NSMallocBlock__)
      • 会调用block内部的__main_block_copy_0函数
      • copy函数内部会调用_Block_object_assign函数
      • _Block_object_assign函数会对__block修饰的变量形成强引用(retain)或弱引用(仅限于ARC模式下会retain,MRC模式不会retain)
    3. 当block从堆上移除
      • 会调用block内部的__main_block_dispose_0函数
      • dispose函数内部会调用_Block_object_dispose函数
      • _Block_object_dispose函数会自动释放引用的__block变量(release)
  • block的循环引用问题

    ARC模式下

    • __weak__unsafe_unretained解决
    __weak typeof(self) weakSelf = self;    
    __unsafe_unretained typeof(self) unSelf = self;
    
    self.block = ^{
          
        weakSelf.name = @"__weak";
        unSelf.name = @"__unsafe_unretained";
    };
    
    • __block解决(必须要执行block才能释放)
    __block YSObject *bSelf = self;
    self.block = ^{
    
        bSelf.name = @"__block";
        bSelf = nil;
    };
    self.block();
    

    MRC模式下

    • __unsafe_unretained解决(MRC下没有弱引用)
    __unsafe_unretained typeof(self) unSelf = self;
    self.block = ^{
          
        unSelf.name = @"__unsafe_unretained";
    };
    
    • __block解决
    __block YSObject *bSelf = self;
    self.block = ^{
    
        bSelf.name = @"__block";
    };
    
    • 使用__strong确保对象没有被释放
    __weak typeof(self) weakSelf = self;
    self.block = ^{
      // 确保weakSelf有效,使用weakSelf无法直接访问_name
      __strong strongSelf = weakSelf;
      strongSelf->_name = @"__weak";
    };
    

  • block的常见面试题

    1. block的本质
    2. __block的原理
    3. 如何处理循环引用问题,ARC和MRC下的处理方式有哪些不同,为什么?

你可能感兴趣的:(iOS Block本质笔记)