block写法:
普通写法:
int (^blk)(int) = ^(int count) {
return count + 1;
};
作为参数:
- (void)func:(int (^)(int))blk {
NSLog(@"Param:%d", blk(2));
}
作为返回值:
- (int(^)(int))funcR {
return^(int count) {
return count ++;
};
} 注意:- (int(^)(int))funcR声明中int(^)(int)是个block整体
调用: int(^test)(int)=[self funcR];
NSLog(@"%d",test(19)); //输出19
借助typedef可简写:
typedef int (^blk_k)(int);
blk_k blk = ^(int a){
return 0;
};
Block定义:带有自动变量(局部变量)的匿名函数,在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态。
如:
int val = 10;
void (^blk)(void) = ^{ printf("val=%d\n",val);
};
val = 2;
blk(); //输出为10,而不是2。
block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝;换句话说block截获自动变量的瞬时值;或者block捕获的是自动变量的副本。
解决block不能修改自动变量的值,这一问题的另外一个办法是使用__block修饰符。
当block作用对象指针时:
block不会强引用 block内部的局部变量和 weak弱指针,只会强引用 block 外部strong指针,也不会强引用对象的属性
如:
classA *a = [[classA alloc]init];
self.A = a;
__weak classA * weakSelf = a;
a.blk = ^{weakSelf.A = self.A;}; //并不会导致循环引用,因为block并不会引用self对象的A属性
Block位置:block有三种类型:NSGlobalBlock,NSStackBlock,NSMallocBlock,(注意这三种类型是代表block体的位置,不是block指针的位置,指针肯定在栈上)
ARC下:
情景一:
void (^blk)(void) = ^{
NSLog(@"Block");
};
NSLog(@"%@",[blk class]);
输出:__NSGlobalBlock__ 此时block在内存 data 段,就像 C 函数一样,属于代码的一部分
情景二:
int i =1;
void(^blk)(void) = ^{
NSLog(@"Capture:%d", i);
};
输出:__NSMallocBlock__ 此时block就像对象一样位于堆区(mrc下位于栈区);由于block位于堆区,int i位于栈区,所以当block执行时i可能释放了,所以block会捕获变量将变量拷贝到堆区
以下几种情况栈上的Block会自动复制到堆上:
调用Block的copy方法
将Block作为函数返回值时
将Block赋值给__strong修饰的变量时
向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
NSLog(@"Stack Block:%@", [^{NSLog(@"Stack Block:%d",i);} class]); //这种情况下由于没有将Block赋值给__strong修饰的变量所以输出为:__NSStackBlock__
Block对对象的处理:
Person*person = [[Personalloc]init];
NSLog(@"%p",&person); //输出0x7ffeec85bad8
void(^captureBlk)(void) = ^{
NSLog(@"Capture:%p", &person);
};
NSLog(@"%@",captureBlk); //输出:<__NSMallocBlock__: 0x60000025d7f0>
captureBlk(); //输出:0x60000025d810 (内存地址表明在堆上)
内存问题:mrc下MRC情况下,用__block可以避免block retain变量从而消除循环引用。
ARC情况下,必须用弱引用才可以解决循环引用问题,iOS 5之后可以直接使用__weak,之前则只能使用__unsafe_unretained了,__unsafe_unretained缺点是指针释放后自己不会置nil
简单的循环引用例子:
detailViewController:
@property(nonatomic,copy) void(^testMemoryLeaksBLock)(detailViewController*detailVC);
-(void)dealloc {
NSLog(@"detail dealloc");
}
ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
detailViewController *detailVC = [detailViewController new];
detailVC.testMemoryLeaksBLock = ^(detailViewController *d) {
NSLog(@"d: %@--", detailVC);
};
}//发生循环引用detailVC强引用block,block强引用detailVC
第二个例子:
- (void)viewDidLoad {
[super viewDidLoad];
detailViewController *detailVC = [detailViewController new];
detailVC.testMemoryLeaksBLock = ^(detailViewController *d) {
NSLog(@"d: %@--", detailVC);
};
detailVC.testMemoryLeaksBLock(detailVC);
}//未发生循环应用因为detailViewController是以参数的形式传入的
第三个例子:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
detailViewController *detailVC = [detailViewController new];
detailViewController*__weakweakDetail = detailVC;
detailVC.testMemoryLeaksBLock = ^(detailViewController *d) {
detailViewController*__strongstriongDetail = weakDetail;
NSLog(@"d: %@--", striongDetail);
};
detailVC.testMemoryLeaksBLock(detailVC);
} ////未发生循环应用,block不会强引用 block内部的局部变量和 weak弱指针,只会强引用 block 外部strong指针
第四个例子:
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
detailViewController *detailVC = [detailViewController new];
detailViewController*__weakweakDetail = detailVC;
detailVC.testMemoryLeaksBLock = ^(detailViewController *d) {
detailViewController*__strong striongDetail = weakDetail;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"d: %@--", striongDetail);
});
};
detailVC.testMemoryLeaksBLock(detailVC);
} //detailViewController 过3秒才释放,因为当testMemoryLeaksBLock执行时生成了一个striongDetail强指针,强指针保持detailViewController不释放,当过3秒block执行完毕之后,这个强指针释放,detailViewController也释放;如果将block改为:detailVC.testMemoryLeaksBLock = ^(detailViewController *d) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
detailViewController*__strong striongDetail = weakDetail;
NSLog(@"d: %@--", striongDetail);
});
}; //block会立即释放,并且打印对象为null
特殊方法解决循环引用:
- (void)viewDidLoad {
[super viewDidLoad];
detailViewController *detailVC = [detailViewController new];
testVC.myLayer= detailVC;
detailVC.testMemoryLeaksBLock = ^(detailViewController *d) {
NSLog(@"d: %@--",detailVC);
};
detailVC.testMemoryLeaksBLock = nil;
} //虽然detailVC包含block,block包含detailVC,但是将detailVC中的detailVC.testMemoryLeaksBLock置为nil,仍能解决循环引用
附:解决循环引用问题主要有两个办法:
1)自己明确知道这里会存在循环引用,在合理的位置主动断开环中的一个引用(置为nil),使得对象得以回收;
2)使用弱引用。
block的原理:
1.内部没有变量的情况下
//新建一个a.m文件
#import
int main(){
void(^block)(void) = ^{
printf("hello world");
};
block();
return 0;
}
执行命令clang -rewrite-objc a.m;得到一个将近十万行的代码文件a.cpp
a.cpp内的关键代码主要在.cpp文件的底部,寻找有用的信息:
//block的结构体
struct __block_impl {
void*isa; //isa指针,oc中任何对象都有isa指针
int Flags; //按bit位表示一些block的附加信息。在block copy的实现时就会使用。
int Reserved; //保留变量
void *FuncPtr; //函数指针指向block声明的方法
};
//block要执行的函数
static void__main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
//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)};
//block的入口
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void*fp,struc t__main_block_desc_0 *desc,int flags=0) { //初始化函数
impl.isa = &_NSConcreteStackBlock; //block的类型
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
上面是一些结构体定义,下面是执行:
int main(){
void(*block)(void) = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA)); //定义并初始化了block类型的变量
((void(*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); //调用block
return 0;
}
2.内部有变量的情况下:
int main(){
int num =1;
void(^block)(void) = ^{
printf("hello wrold:%d",num);
};
block();
return 0;
}
继续用clang编译:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num; //这里保存了num,可以看到是拷贝一份
__main_block_impl_0(void*fp,struct__main_block_desc_0 *desc,int _num,int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void__main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num;// 从__main_block_impl_0内获取num
printf("hello wrold:%d",num);
}
可以看到 __main_block_impl_0 中添加了 一个int num的变量。在 __main_block_func_0中使用了该变量。
从这里可以看出来 这里是 值拷贝,不能修改,只能访问。
__block的用途:
int main(){
__block int num =1;
void(^block)(void) = ^{
printf("hello wrold:%d",num);
};
block();
num =2;
return 0;
}
编译后:
//用于封装 __block 修饰的外部变量,注意: __block int num这句代码 变成了 __Block_byref_num_0指针变量
struct __Block_byref_num_0 {
void*__isa;// 对象指针
__Block_byref_num_0 *__forwarding;// 指向 拷贝到堆上的 指针
int __flags;// 标志位变量
int __size;// 结构体大小
int num;// 外部变量
};
//__main_block_impl_0 中增加了 __Block_byref_num_0类型的指针变量。所以__block的变量之所以可以修改 是因为 指针传递。所以block内部修改了值,外部也会改变:
struct __main_block_impl_0 {
struct __block_impl impl;
struct__main_block_desc_0* Desc;
__Block_byref_num_0 *num;// __block int num 变成了 __Block_byref_num_0指针变量。也就是 __block的变量通过指针传递给block
__main_block_impl_0(void*fp,struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num,int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
修改__block变量时也是修改的__Block_byref_num_0结构体: