Block笔记(四)

通过前几章的学习我们可以知道,Block转换为Block的结构体类型自动变量__block变量转换为__block变量结构体类型的自动变量,所谓结构体类型自动变量,即上生成的该结构体的实例

Block存储域

之前我们在讲Block本质的时候知道,Block也是Objective-C对象,其isa指向了NSConcreteStackBlock类,还有两个与此类似的类分别是NSConcreteGlobalBlockNSConcreteMallocBlock

NSConcreteStackBlock类的名称中含有“栈”(stack)一词,即该类的对象设置在上,同样的NSConcreteGlobalBlock类对象与全局变量一样设置在数据区域中,NSConcreteMallocBlock类对象则设置在由malloc函数分配的内存块(堆)中。

前几篇文章中我们介绍的例子都是NSConcreteStackBlock类,且都设置在栈上,在记叙全局变量的地方使用Block语法时,生成的Block为NSConcreteGlobalBlock类,例如:

void (^blk)(void) = ^{printf("Global Block\n");};

int main(){

}

代码转换如下:

struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
printf("Global Block\n");}

static struct __blk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blk_block_desc_0_DATA = { 0, sizeof(struct __blk_block_impl_0)};
static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void (*blk)(void) = ((void (*)())&__global_blk_block_impl_0);

int main(){


}

该Block的类为_NSConcreteGlobalBlock类,设置在程序的数据区域中,在使用全局变量的地方不能使用自动变量,所以也就不存在对自动变量进行截获,由此Block结构体实例的内容不依赖于执行时的状态,所以整个程序中只需要一个实例。因此Block即被设置在于全局变量相同的数据区域中。

在记叙全局变量的地方有Block语法时以及Block语法表达式不使用应截获的自动变量时Block为_NSConcreteGlobalBlock类对象,除此之外Block语法生成的Block为_NSConcreteStackBlock类对象,且设置在上。

那么NSConcreteMallocBlock类在何时使用呢?

从栈复制到堆

之前一篇我们提到过,由Block语法生成的值Block上可以存有超过变量作用域被截获自动变量,当变量作用域结束时,原来的自动变量被废弃,该Block就被废弃,将不能通过指针来访问原来的自动变量。由于__block变量也配置在上,如果其所属的变量作用域结束,该__block变量也会被废弃。

Block会提供将Block__block变量上复制到上的方法来解决这个问题。将配置在上的Block复制到上,这样即使Block语法记述的变量作用域结束上的Block还可以继续存在。

复制到上的Block将_ NSConcreteMallocBlock类对象写入Block的结构体实例成员变量isa

impl.isa = &_ NSConcreteMallocBlock;

__block变量结构体成员变量__forwarding可以实现无论__block变量配置在上还是上时都能够正确的访问__block变量

那么Blocks提供的方法是什么呢?实际上当ARC有效时大多数情形下编译器会恰当的进行判断,自动生成将Block从栈上复制到堆上的代码。我们就来举例说明。

typedef void (^block_t)(void);

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    block_t block = [self func];
    block();
}


-(block_t)func{
    __block int add=10;
    return ^{NSLog(@"add=%d\n",++add);};
};

@end

该源代码为返回配置在上的Block函数。即程序执行中从函数返回函数调用方时变量作用域结束,因此上的Block也被废弃。但是该源代码可以正常运行成功输出如下。

2018-10-26 13:43:34.257219+0800 testblock[43867:4966419] add=11

这是因为在ARC有效时编译器会如下转换代码:

block_t block = &__ViewController__func_block_impl_0(__ViewController__func_block_func_0, &__ViewController__func_block_desc_0_DATA, (__Block_byref_add_0 *)&add, 570425344)));

block = objc_retainBlock(block);

return objc_autoreleaseReturnValue(block);

objc_retainBlock函数实际上就是Block_copy函数。即:

//通过Block语法生成的Block,配置在栈上的Block结构体实例,赋值给相当于Block类型的变量block中
block = _Block_copy(block);
//_Block_copy函数将栈上的Block复制到堆上,复制后将堆上的地址作为指针赋值给变量block
return objc_autoreleaseReturnValue(block);
//将堆上的Block作为OC对象注册到autoreleasepool中,然后返回对象

将Block作为函数返回值时,编译器会自动生成复制到堆上的代码。

之前说大多数情况下编译器会适当的判断,在此之外的情况下需要手动生成代码,将Block从栈复制到堆上。此时我们使用“copy实例方法”

那么编译器不能进行判断的情况又是什么样呢。

1.向方法或函数的参数中传递Block时
2.Cocoa框架的方法且方法名字中含有usingBlock等时
3.GCD的API

我们再来举例说明:

typedef void (^block_t)(void);

@interface ViewController ()

@end



@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    id obj = [self getBlockArray];
    block_t block = (block_t)[obj objectAtIndex:0];
    block();
}

- (id)getBlockArray{
    int val = 10;
    return [[NSArray alloc] initWithObjects:^{NSLog(@"block0:%d",val);},^{NSLog(@"block1:%d",val);}, nil];
}

@end

getBlockArray方法在上生成两个Block,并传递给NSArray类的initWithObjects实例方法。但是在执行时会出现异常。

2018-10-26 14:12:16.621722+0800 testblock[44061:5009777] block0:10
objc[44061]: Attempt to use unknown class 0x600001e6b680.

异常

可以看到上的block为null,这是因为上的Block已经被废弃。可惜此时编译器不能判断是否需要复制,我们就需要手动复制,但是将Block从复制到是相当消耗CPU的,因此只有在必须复制的时候我们再进行手动复制。

- (id)getBlockArray{
    int val = 10;
    return [[NSArray alloc] initWithObjects:[^{NSLog(@"block0:%d",val);} copy],[^{NSLog(@"block1:%d",val);} copy], nil];
}

像这样我们的程序就可以正常的运行了。

__block变量存储域

上面我们把重点放在Block进行说明,那么对__block变量又是如何处理的呢?使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响。

若在一个Block中使用__block变量,则当该Block从栈复制到堆时,使用的所有__block变量也必定配置在上,这些__block变量也会跟随Block一起复制到,此时Block持有__block变量

现在我们来看看之前说过的__forwarding成员变量,“不管__block变量配置在上还是在上,都能够正确地访问该变量”。通过Block的复制,__block变量也从复制到,此时可同时访问上的__block变量上的__block变量

    __block int val = 0;
    void (^blk)(void) = [^{++val;} copy];
    ++val;
    blk();
    NSLog(@"%d",val);

利用copy方法复制了__block变量的Block语法。Block和__block变量两者均是从栈复制到堆。

//该val为复制到堆上的__block变量
void (^blk)(void) = [^{++val;} copy];
//该val为复制前栈上的__block变量
++val;
//以上两者都可转化为如下形式
++(val.__forwarding->val);

但是上的__block变量在从复制到时,会将成员变量forwarding的值替换为复制目标上的__block变量的地址。通过这个功能,无论是在Block中,Block外使用__block变量上或者上的__block变量,都可以顺利的访问同一个__block变量

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容