什么是Blocks
blocks是带有自动变量(局部变量)的匿名函数
(1)匿名函数:不带名称的函数。
(2)带有自动变量(局部变量)
int func(int count); int result = func(10);
int (*funcptr)(int) = &func;int result = (*funcptr)(10);
ps:C语言函数中可能使用的变量:
1自动变量(局部变量)2函数参数 //1、2都属于局部变量,存储于栈分配空间;只有在函数执行期间可见。
3静态变量(静态局部变量) //3局部变量,静态存储区域;静态变量只被初始化一次,且只对定义自己的函数体可见。
4静态全局变量 5全局变量 //4、5 都是全局作用域,分配内存空间于静态存储区域。 静态全局变量只作用于定义它的文件里,也称文件作用域(static修饰)。全局变量只在一个源文件中定义,就可以作用于所有的源文件,其它源文件需使用extern引入。
static修饰符,全局->静态全局,限制了使用范围。变量->静态变量,改变了存储方式和生存期。
使用Blocks可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量时的问题,仅用编写C语言函数的源代码量即可使用带有自动变量的匿名函数。
Blocks模式
Block语法
完整形式的Block语法同c语言函数定义相比,仅有两点不同。
(1)没有函数名
(2)带有^
以下为block语法的BN范式:
^ 返回值类型 参数列表 表达式
^(int)(int count){return count+1;}
其中返回值类型可省略变成:^(int count){return count+1;}
如果不使用参数,参数列表也可省略^{printf("Blocks ");};
Block类型变量
在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量中。 如下:
int func (int count)
{
return count+1;
}
int (*funcptr)(int) = &func;
同样可将Block语法赋值给声明为Block类型的变量中。如下:
int (^blk)(int) =
^ (int)(int count)
{
return count+1;
};
该Block类型变量与一般的c语言变量相同。
int (^blk1)(int) = blk;
void func(int(^blk)(int));//作为函数参数使用
typedef int(^blk_t) (int);//作为函数返回值使用时候记述方式极为复杂。所以可以像函数指针那样使用typedef来解决问题。
所以作为函数参数使用可变为void func(blk_t blk);
作为返回值可以变为blk_t func();
blk_t blk=
^ (int)(int count)
{
return count+1;
}
blk_t *blkptr = &blk;
(*blkptr)(10);//可以这样调用,同c相同
截获自动变量的值
带有自动变量值 在Blocks就表现为 截获自动变量的值。
(块就是一个值,且自有其相关类型。块的强大之处是,在声明它的范围里,所有变量都可以为其所捕获,如果捕获的变量是对象类型,就会自动保留。且默认情况下被块所捕获的变量,是不可以在块里修改的,若想修改此变量。声明变量的时候可以加上__block。)
例外:对于
id array =[[NSMutableArray alloc]init];
void (^blk)(void) = ^{
id obj =[[NSObject alloc]init];
[array addObject:obj];
};
虽然赋值给截获的自动变量array的操作会产生编译错误。但是使用截获的值却不会有任何问题。
ps:截获自动变量的方法并没有实现对C语言数组的截获(查找:为什么Block不能截获c语言数组)
const char text[] = "hello";
void (^blk) (void) = ^{printf("%c ",text[2]);};
所以以上代码会造成编译器错误,这时使用指针便可以解决该问题,改为const char *text = "hello";
Block实现
Block实现
int main() {
void (^blk)(void) = ^{};
blk();
return 0;
}
通过
clang -rewrite-objc main.m -o main.cpp编译成如下形式
#ifndef BLOCK_IMPL
#define BLOCK_IMPL
//表示今后版本升级所需的区域以及函数指针
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//表示今后版本升级所需的区域和Block大小
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_DATA
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;
}
};
//这里__cself表示指向Block值的变量(其实Block就是__main_block_impl_0结构体类型的自动变量,即栈上生成的__main_block_impl_0结构体实例)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
//实现
int main() {
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
//struct __main_block_impl_0 *blk = &tmp;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// (*blk->impl.FuncPtr)(blk);
return 0;
}
这里__main_block_impl_0相当于objc_object
__main_block_impl_0中的isa 相当于objc_object中的isa
那么__main_block_impl_0中的isa指向的_NSConcreteStackBlock相当于objc_object中的isa指向的class_t(?objc_class)结构体实例。
因此Block作为Objective-C的对象处理时,关于该类的信息放置于_NSConcreteStackBlock中。(block是一个仿对象)
截获自动变量的值
int main() {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d ";
void (^blk)(void) = ^{fmt;val;};
blk();
return 0;
}
clang编译如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
fmt;val;}
//注意这里fmt和val被作为成员变量追加到了__main_block_impl_0的结构体中。这里在Block中没有使用的dmy并不会被追加,Block的自动变量截获只针对Block中使用的自动变量。
这里可以解释为什么Block不能截获c语言数组
char a[10] = {2};
char b[10] = a;//C语言中这样赋值是不允许的,Blocks是更遵循C语言规范的。
//以下是可以的
// char *a = "2";
// char *b = a;
__block说明符
Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
若是想改变的话有两种方法:
(1)使用静态局部变量,静态全局变量,全局变量
这里Block依然会捕获静态局部变量(不会捕获静态全局变量和全局变量),但是使用的是指针,如下:
struct __main_block_iml_0{
struct __block_impl impl;
sturct __main_block_desc_0* Desc;
int *static_val;
}
使用: int *static_val = __cself->static_val;
(自动变量不使用指针的原因是,自动变量超过作用域后就会被废弃,通过指针也无法访问到原来的自动变量)
(2)__block存储域类说明符 (C语言中有typedef extern static(静态变量数据区域) auto(栈) register)
__block修饰后,增加了很多编译代码,直接将原来的局部变量,变为了(生成在栈上的)结构体实例。
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;//原来的局部变量
};//单独生成的原因,是可以在多个Block中使用__block变量。反过来也可以一个Block中使用多个_block变量。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
Block和__block变量的存储域
通过前面的说明,我们得知了Block与__block变量的实质分别如下:
Block的实质是栈上的Block结构体实例;__block变量的实质是栈上的__block变量的结构体实例。
将Block作为OC对象看时,该Block类可能为如下三种(存储区域):
_NSConcreteStackBlock(栈):一般情况下都是这种。
_NSConcreteGlobalBlock(数据区)(全局块,不捕捉任何状态,全部信息都能在编译期运行):(1)在记述全局变量的地方使用Block语法生成的Block对象(因为全局区不存在对自动变量的截获,因此Block结构体实例的内容不依赖于程序的执行状态,整个程序只需要一个实例,因此存储在数据去中)(2)Block语法的表达式中不使用应截获的自动变量时。(存疑?使用clang发现还是stackBlock)
_NSConcreteMallocBlock(堆):
(存在原因之一)这个结构式Block超出作用域存在的原因;(存在原因之二)也是之前__block变量用结构成员变量__forwading存在的原因。
对于:
void (^block)();//注意这种用法
if (/*some condition*/){
block = ^{
NSLog(@"Block A");
}
}else{
block = ^{
NSLog(@"Block B");
}
}
block();//由于block块只在if else语句范围内有效,这样写出来的代码可以编译,但是运行起来有时正确有时错误。此时可以给块对象发送copy消息以拷贝之。这样的话,可以把块从栈复制到堆(堆块)。
下面详细描述_NSConcreteMallocBlock的存在原因之一:
将Block从栈上复制到堆上,即使Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
(1)Block作为函数返回值时,编译器会自动生成复制到堆上的代码。例如编译器代码(和书上写的有所不同,这里是sel_registerName("copy");):
return (blk_t)((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)((int (*)(int))&__func_block_impl_0((void *)__func_block_func_0, &__func_block_desc_0_DATA, rate)), sel_registerName("copy"));
(2)Block作为函数或方法参数时,需要自己添加copy。例外不需要自己添加copy:Cocoa框架的方法切方法名中含有usingBlock时,GCD的api,因为已经实现。
举个例子:
id getBlockArray()
{
int val = 2;
return [[NSArray alloc]initWithObjects:[^{NSLog(@"%d",val);} copy],[^{NSLog(@"%d",val);} copy],nil];//这里如果不加copy,就可能会有各种各样的错误。
}
int main(){
id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk_t blk1 = (blk_t)[obj objectAtIndex:1];
blk();
blk1();
}
注意:将Block从栈上复制到堆上是相当消耗CPU资源的。pps:block语法和block变量都可以直接调用copy方法。
对于Block进行copy:只对_NSConcreteStackBlock是从栈复制到堆。对_NSConcreteMallocBlock只是引用计数增加。对_NSConcreteGlobalBlock什么也不做。
(3)还有一种情况栈上的Block会被复制到堆:
将Block赋值给类中的(以__strong修饰)成员变量时候。(在ARC状态下都是以__strong修饰的)
综上:所以说在ARC状态下只要指针过一下__strong指针或者由函数返回都会将block栈移动到堆上。
下面详细描述_NSConcreteMallocBlock的存在原因之二:(__block变量存储域)
当Block从栈复制到堆时,__block变量也会从栈复制到堆,并被Block持有。(注意在栈中的Block只是使用在栈中的__block,而不是持有关系)。如果__block变量在堆中,那么copy会增加引用计数。
且对于在栈中的__block变量结构体中的__forwading是指向自己本身的指针。而当复制到堆之后,栈中__block变量的__forwading和堆中的__block变量的__forwading都会指向堆中的__block变量本身。
截获对象
对于id array = [[NSMutableArray alloc]init];
在Block结构体中截获的是id __strong array;(虽说c语言的结构体不能含有赋有__strong修饰符的变量,因为编译器不知道应合适进行c语言结构体的初始化和废弃操作,不能很好的管理内存。但是Block结构体总即使含有__strong或__weak修饰的变量,也可以恰当的进行初始化和废弃,因为其在__main_block_desc_0中增加了copy(__main_block_copy_0其实调用的是_Block_object_assign相当于retain)和dispose (__main_block_dispose_0其实调用的是_Block_object_dispose相当于relesase)成员变量))
这里注意:
单纯截获,不加修饰符:对于截获的对象会产生copy和release方法(且copy和release方法的最后一个参数是BLOCK_FIELD_IS_OBJECT),对于简单的变量int val这种则不会产生。
加__block修饰符:对于无论是对象还是简单变量都会产生copy和dispose方法,最后一个参数是BLOCK_FIELD_IS_BYREF。不同的是对象还会调用
__Block_byref_id_object_copy_131这个方法?131表示BLOCK_BYREF_CALLER
|BLOCK_FIELD_IS_OBJECT。
注意:__block与__weak一起使用和__weak单独使用效果是一样的。__autoreleasing 不能与__block一同使用。
避免循环引用
(1)__weak修饰 (2)也可使用__block修饰tmp,只是需要在函数语法中tmp=nil(缺点是如果不调用的话,就会造成内存泄漏。优点是可控制对象的持有期间)。
(以下注意self不能在类方法中使用)
如果将块定义在了OC类的实例方法里,那么除了可以访问类的所有实例变量之外,还可以使用self变量。块总能修改实例变量,所以在声明时也无需加_block。不过,如果通过读取或写入操作捕获了实例变量(这里所指的并不单单指用self,所以说只要是用到了实例变量就会捕捉self?),那么也会自动把self变量一并捕获,因为实例变量与self所指代的实例是关联在一起的。定义块的时候其所占内存区域是分配到栈中的(栈块)。
MRC下用_block修饰不会引起循环引用(相反MRC下用__block来解除引用)。ARC下用_block修饰就会引起循环引用。
38 为常用的块类型创建typedef
总结:每个块都具备其“固有类型”(inherent type),这个由块所接受的参数及其返回值组成。可以为同一个块签名定义多个类型别名。
39 用handler块降低代码分散程度
总结:(注意161页推荐网络请求时候将成功和失败的情况放在一个块中处理)。当某些代码必须运行在特定线程上,可以用handler来实现。设置api时如果用到了handler块,可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行。
40 用块引用其所属对象时不要出现保留环
总结:一定要找个适当的时机解除保留环,而不能把责任推给API的调用者。
番外:NSString *a = @“hello”;a为常量变量(存储在内存中的常量区)。@“hello”为常量。不加__block会引用常量的地址(浅拷贝)。加__block类型block会去引用常量变量的地址。
如下示例:
NSString *str = @"hello";
NSLog(@"hello======%p",str);
void (^print)(void) = ^{
NSLog(@"block=str======%p",str);
};
str = @"hello1";
NSLog(@"hello1======%p",str);
print();
不加__block打印:(此时引用的是常量@“hello”地址)
2017-04-12 19:56:38.667 BAFParking[27300:1591355] hello======0x105c0e5f0
2017-04-12 19:56:38.667 BAFParking[27300:1591355] hello1======0x105c0e650
2017-04-12 19:56:38.668 BAFParking[27300:1591355] block=str======0x105c0e5f0
加__block打印:(此时引用的是常量变量a的地址)
2017-04-12 19:55:21.262 BAFParking[27244:1590097] hello======0x10d9345f0
2017-04-12 19:55:21.263 BAFParking[27244:1590097] hello1======0x10d934650
2017-04-12 19:55:21.263 BAFParking[27244:1590097] block=str======0x10d934650
block会拷贝变量内容到自己的栈内存上,以执行时可以调用。但并不是重新申请内存。