Block原理

通过这篇文章,可以让小伙伴能理解Block的使用及注意细节,也让Block代码块在开发中运用得更加恰到好处,本人通过自己的理解来讲解这篇文章,若有错误,欢迎指正!

一、原理

typedef void(^myBlock)(void);
myBlock block = ^{
    printf("helloWorld");
};
block();
  • 我们将代码转换为C++源码来进行详细地分析
// 进行适当缩减,易阅读
myBlock block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  1. 通过调用__main_block_impl_0函数传入参数来初始化生成一个block对象。
  • __block_impl
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

__block_impl结构体存放着类对象以及block函数实现。

  1. isa:指向类对象地址;
  2. Flags:标记,默认为0;
  3. Reserved:今后版本升级所需的区域;
  4. FuncPtr:指向实现函数地址;
  • __main_block_desc_0
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_desc_0结构体用于计算存放内存空间大小。

  1. reserved:今后版本升级所需的区域;
  2. Block_size:block所需占用内存大小;
  • __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;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

   printf("helloWorld");
}
  1. 将__main_block_func_0实现函数地址传入指向FuncPtr;
  2. 将block块内存大小初始化Desc;

二、本质

结论:Block本质是OC对象

  • ?

  1. block中的isa指针指向类__NSMallocBlock__;
  2. 根据runtime源码,我们知道Class就是结构体objc_class,而objc_class又继承自objc_object结构体,所以可得block本质就是一个对象。
  • ?
Block block = ^{
   NSLog(@"helloWorld");
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);

//运行结果
// __NSGlobalBlock__
// __NSGlobalBlock
// NSBlock
// NSObject
  1. 根据运行结果可清楚得看到block的父类;
  2. block最终继承自NSObject,所以这里我们可以更加确信我们的结论!

三、捕获变量及对象

  • 对象及变量存放区域

  1. 未使用到局部变量的block则是__NSGlobalBlock__类型;
  2. 使用到局部变量的block则是__NSMallocBlock类型,需要进行内存管理;
  3. __NSStackBlock__是存放在栈上的,受作用域所影响,超过作用域后内存将会被回收。
  • 捕获局部变量
int var = 100;
void(^block)(void) = ^{
     //var = 200; 不能修改var的值
     NSLog(@"var:%d", var);
};
var = 200;
block();

// 转换部分源码
struct __main_block_impl_0 {
  //..省略部分源码..
  int var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _var, int   flags=0) : var(_var) {
  //..省略部分源码..
   }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int var = __cself->var; // bound by copy
  NSLog(var);
}
int main(int argc, const char * argv[]) {
{
   int var = 100;
   void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, var));
}}
  1. block捕获局部变量后,不能在block中修改局部变量var的值;
  2. 从源码知道,block在内部重新生成一个相同的变量引用着局部变量var的值,在这里变量var只是将值传递给block,所以在block内部不能修改重新给变量var赋值;因为是变量var值传递,局部变量在外部修改不会对block内部产生影响。
  • 捕获static变量
static int var = 100;
void(^block)(void) = ^{
    NSLog(@"var:%d", var);
    var = 200;
    NSLog(@"var:%d", var);
};
block();

// 转换源码
struct __main_block_impl_0 {
  ...
  int *var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_var, int flags=0) : var(_var) {
  ...
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *var = __cself->var; // bound by copy
  NSLog((*var));
  (*var) = 200;
  NSLog((*var));
}
int main(int argc, const char * argv[]) {
{
    static int var = 100;
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &var));
    ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
}
  1. 变量加上static后,传递过程为变量地址;
  2. block内部生成一个指针var指向静态变量var的地址,所以在block内部可直接修改变量var的值,直接对指向变量地址的指针变量进行赋值;
  • 捕获全局变量
static int var = 100;
int globalVar = 100;
void(^block)(void) = ^{
    NSLog(@"var:%d, globalVar: %d", var, globalVar);
    var = 200;
    globalVar = 200;
    NSLog(@"var:%d, globalVar: %d", var, globalVar);
};
block();

//转换源码
static int var = 100;
int globalVar = 100;
    ..
struct __main_block_impl_0 {
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    ..
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSLog(var, globalVar);
  var = 200;
  globalVar = 200;
  NSLog(var, globalVar);
}
int main(int argc, const char * argv[]) {
{ 
    void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
    ((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  }
    return 0;
}
  1. 对于全局变量,block中不会有任何变量的初始化传递;
  2. 全局变量在block直接使用,block中不会生成任何变量;
  • 捕获对象
JZPerson* person = [JZPerson new];
person.name = @"hello"; 
void(^block)(void) = ^{
     NSLog(@"name: %@", person.name);
};
block();

// 转换源码
struct __main_block_impl_0 {
  JZPerson *person;
  ...
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JZPerson *_person, int flags=0) : person(_person) {
  ...
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  JZPerson *person = __cself->person; // bound by copy

  NSLog(person, sel_registerName("name")));
}
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*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
int main(int argc, const char * argv[]) {
{
    JZPerson* person = (objc_msgSend(objc_getClass("JZPerson"), sel_registerName("new"));
    void(*block)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
    (((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  1. block中生成person的强引用指向person对象;
  2. person对象类似于局部变量,只是将person对象传入block并用__main_block_impl_0结构体中的person进行初始化,我们只可以访问对象的属性值,而不能在block中将外部的person对象重新赋值。
  • __block使用

当我们需要在block内部对变量或者对象进行重新赋值修改时,对于变量,我们可以使用static进行修饰进行修改;而对于对象,OC提供了__block修饰符给我们使用,当然了__block也可以修饰变量!

?

__block int var = 100;
void(^block)(void) = ^{
    NSLog(@"name: %d", var);
};
block();

//转换源码
struct __Block_byref_var_0 {
 __Block_byref_var_0 *__forwarding;
 int var;
 ...
};

struct __main_block_impl_0 {
  ...
  __Block_byref_var_0 *var; // by ref
  __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_var_0 *var = __cself->var; // bound by ref
  NSLog(var->__forwarding->var);
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

static struct __main_block_desc_0 {
  ...
  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, const char * argv[]) {
        __attribute__((__blocks__(byref))) __Block_byref_var_0 var = {0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 100};

        void(*block)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  1. 使用__block修饰后block内部重新生成__Block_byref_var_0结构体,并将变量初始化为一个结构体复制到__main_block_impl_0结构体中var;
  2. __Block_byref_var_0中定义了一个相同类型的属性__forwarding,__forwarding指向了对象本身,代码中初始化为&var;
  • __forwarding作用

我们在MRC下测试__forwarding的作用,因为MRC下block是在栈上,而ARC下生成的block是在堆上!

?一:

__block int var = 100;
NSLog(@"%p", &var);
void(^block)(void) = ^{
    NSLog(@"%p", &var);
};
block();
NSLog(@"%p", &var);

/**  结果  **/
//0x7ffeefbff468
//0x7ffeefbff468
//0x7ffeefbff468

 ?二:

__block int var = 100;
NSLog(@"%p", &var);
void(^block)(void) = ^{
    NSLog(@"%p", &var);
};
[block copy];
block();
NSLog(@"%p", &var);

/**  结果  **/
//0x7ffeefbff468
//0x103108058
//0x103108058
  1. 例一通过打印地址在MRC下变量var是在栈上的,block内部使用的是外部的var;
  2. 例二将block执行copy,会将block复制到堆上!同时,block也将变量var也一并复制到了堆上;

作用:

  1. 方便内存管理!
  2. 通过__forwarding指针指向了自身,可通过__forwarding将var变量进行修改;
  3. 当block在栈上时,__forwarding指向的是自身;
  4. 当block在堆上时,__Block_byref_var_0也会复制一份到堆上,此时栈上的__forwarding将不再指向自己,而是指向了堆上的__forwarding,堆上的__forwarding则是指向自己!!

copy到堆上时的__forwarding:

  • __block内存管理
  1. 当block在栈上时,则block使用的变量也是在栈上,无需对变量或对象进行内存管理;
  2. 当block复制到堆上,则block中使用__main_block_copy_0将对象或变量进行内存管理,当对象为strong时,则将对象引用计数加1;当对象为weak时,则不增加对象引用计数。当对象释放时,相应地会调用__main_block_dispose_0进行减1操作,对象的释放还是要看其是否被其它对象所引用!

block复制到堆上:

block释放:

 

 

参考文章:

iOS底层原理总结 - 探寻block的本质(一)

iOS底层原理总结 - 探寻block的本质(二)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值