一、block定義
- 概念
block是將函數(shù)及其上下文封裝起來的對象。
block也是一個指針,保存的是一段代碼塊在內(nèi)存中的空間 (棧內(nèi)存)
//blcok定義:后者return_type可省,參數(shù)為void參數(shù)可省略
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
// statements
};
blockName(var);
//利用typedef簡化Block的聲明:
typedef return_type (^BlockTypeName)(var_type);
- block的使用場景
- 把block保存到對象中,恰當(dāng)時機(jī)的時候才去調(diào)用
- (void)block1
{
Person *p = [[Person alloc] init];
void(^block)() = ^() {
NSLog(@"執(zhí)行對象中block");
};
p.operation = ^(){
NSLog(@"執(zhí)行對象中block");
};
p.operation = block;
_p = p;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_p.operation();
}
// block:ARC使用strong,非ARC使用copy
// block類型:void(^)()
@property (nonatomic, strong) void(^operation)();
- 把block當(dāng)做方法的參數(shù)使用,外界不調(diào)用,都是方法內(nèi)部去調(diào)用,Block實現(xiàn)交給外界決定.
- (void)block2
{
Person *p = [[Person alloc] init];
// 傳入block給參數(shù)的Block賦值
[p eat:^{
NSLog(@"吃東西");
}];
}
//Person中eat方法
- (void)eat:(void (^)())block
{
block();
}
- 把block當(dāng)做方法的返回值,目的就是為了代替方法.block交給內(nèi)部實現(xiàn),外界不需要知道Block怎么實現(xiàn),只管調(diào)用
- (void (^)(int))run
{
return ^(int meter){
NSLog(@"跑了%d米",meter);
};
}
- (void)block3
{
Person *p = [[Person alloc] init];
p.run(2);
// void(^run)() = p.run;
//run();
}
二、block捕獲變量
1. block使用外部變量,會將外界變量拷貝一份到堆內(nèi)存(在給block塊分配內(nèi)存空間的時候),調(diào)用block之前修改變量,不影響block內(nèi)的該變量取值。該block是不允許更改該變量的
- (void)test01
{
int a = 10;
void(^block)(int x) = ^(int x){
NSLog(@"== %d", a);//10, a的地址和外面a不同
//variable is not assignable (missing __block type specifier )不允許修改a的值
};
a = 20;
block(a);
}
2. __block的作用
- 基本類型
局部變量 : 在聲明前加__block,可以直接讀寫該變量,是地址傳遞,會影響外界值。會否拷貝該值分為ARC、MRC二種情況
靜態(tài)變量、全局變量:不會對原來的值進(jìn)行copy,直接讀寫,地址不變
- (void)test01
{
//int sum = 5; //( variable is not assignable (missing __block type specifier ))
__block int sum = 5;
NSLog(@"1---%p --- %d", &sum, sum);
sum = 10;
void (^sumBlock) (int m, int n) = ^(int m, int n) {
sum = m + n;
NSLog(@"2---%p --- %d", &sum, sum);
};
sum = 15;
sumBlock(1, 2);
NSLog(@"3---%p --- %d", &sum, sum);
__block NSString *name = @"小明";
NSLog(@"1---%@---%p", name, &name);
void (^stringBlock) (void) = ^(void) {
name = @"小麗";
NSLog(@"2---%@---%p", name, &name);
};
name = @"ls";
stringBlock();
NSLog(@"3---%@---%p", name, &name);
}
//ARC
//1---0x7ffee12688a8 --- 5
// 2---0x60000118ced8 --- 3
// 3---0x60000118ced8 --- 3
// 1---小明---0x7ffee1268848
// 2---小麗---0x600001f8da68
// 3---小麗---0x600001f8da68
//MRC
//1---0x7ffee0d888a8 --- 5
//2---0x7ffee0d888a8 --- 3
//3---0x7ffee0d888a8 --- 3
//1---小明---0x7ffee0d88848
//2---小麗---0x7ffee0d88848
//3---小麗---0x7ffee0d88848
如果想在block內(nèi)修改某局部變量需加__block. MRC 環(huán)境下block在使用過程中不會對原來值進(jìn)行copy,可以直接修改該變量 ,ARC環(huán)境下會對原值進(jìn)行copy,內(nèi)存地址發(fā)生變化。
block可以直接修改全局和靜態(tài)變量 ,不會copy該變量的值
- 指針類型的變量
- (void)test02
{
Person *people = nil;
people = [[Person alloc] init];
people.name = @"zhangsan";
NSLog(@"1---%@---%p--%@", people, &people, people.name);
void (^peopleBlock) (void) = ^(void) {
NSLog(@"2---%@---%p--%@", people, &people, people.name);
people.name = @"wangwu";
/*
people = [[Person alloc] init];
people.name = @"zhaoliu";
*/
};
people.name = @"lisi";
peopleBlock();
NSLog(@"3---%@---%p--%@", people, &people, people.name);
}
ARC、MRC不使用__block
//1---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--zhangsan
//2---<Person: 0x6000021ab3b0>---0x600002de3500--lisi
//3---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--wangwu
MRC使用__block
//1---<Person: 0x6000027ec760>---0x7ffeeec238a8--zhangsan
//2---<Person: 0x6000027ec760>---0x7ffeeec238a8--lisi
//3---<Person: 0x6000027ec760>---0x7ffeeec238a8--wangwu
ARC使用__block
//1---<Person: 0x6000034d8390>---0x7ffee03568a8--zhangsan
//2---<Person: 0x6000034d8390>---0x600003881018--lisi
//3---<Person: 0x6000034d8390>---0x600003881018--wangwu
不加__block:
MRC 和 ARC block內(nèi)都是對(原來指針的copy),也就是有兩個不同的指針,指向同一個對象。在block內(nèi)可以更改對象的屬性值,但是不可以更改對象
使用__block:
MRC環(huán)境block中不會對原來的指針進(jìn)行copy,所以可以更改屬性,也可以更改對象本身 。
ARC環(huán)境則是新增指針地址并賦值給原指針(對原對象的copy?內(nèi)存地址也發(fā)生變化?)。
指針類型全局和靜態(tài)變量block內(nèi)可以直接修改,不會copy值和指針。
三、關(guān)鍵字__weak, __strong,copy
- __weak
- (void)test03
{
Person *p = [[Person alloc]init];
p.name = @"myObject";
NSLog(@"1---%@---%p--%@", p, &p,p.name);
__weak Person *weakObj = p;
NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
void(^testBlock)(void) = ^(){
NSLog(@"3---%@---%p--%@", weakObj, &weakObj,weakObj.name);
};
testBlock();
p = nil; // 這邊值nil 用來判斷block是否復(fù)制了對象
testBlock();
}
不使用__weak(另MRC 是沒有__weak關(guān)鍵字的)
//1---<Person: 0x600000c20d50>---0x7ffee5e148a8--myObject
//2---<Person: 0x600000c20d50>---0x7ffee5e148a0--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject
使用__weak
//1---<Person: 0x6000037f8d90>---0x7ffeee15d8a8--myObject
//2---<Person: 0x6000037f8d90>---0x7ffeee15d8a0--myObject
//3---<Person: 0x6000037f8d90>---0x600003becb30--myObject
//3---(null)---0x600003becb30--(null)
不使用 __weak, p = nil 后block塊內(nèi)打印出的對象仍不為空,說明block中新增了強指針引用
使用 __weak p = nil 后person對象為nil ,說明block內(nèi)新增了弱指針引用,對象釋放后 weakObj 也不在持有, 并會被置nil 防止野指針報錯。
//Capturing 'self' strongly in this block is likely to lead to a retain cycle
- (void)test04
{
__weak typeof(self)weakSelf = self;
self.block = ^(NSString *name){
NSLog(@"arr:%@", weakSelf);
// [self weakTest];
// p.name = @"haha";
};
self.block(@"123");
}
block在copy時都會對block內(nèi)部用到的對象進(jìn)行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環(huán)境下對block使用不當(dāng)都會引起循環(huán)引用問題 。
一般表現(xiàn)為,某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身,
簡單說就是self.block = ^(Type var){
[self dosomething];
或者 self.otherVar = XXX;
或者 _otherVar = ...
};
block的這種循環(huán)引用會被編譯器捕捉到并及時提醒
解決方法 __weak typeof(self)weakSelf = self;
- __strong
__weak 可能產(chǎn)生的問題: weakSelf 指針是沒有對象持有權(quán)的,那么外部對象被提前釋放了怎么辦?block內(nèi)部的執(zhí)行豈不是會出錯 ?這個問題又當(dāng)如何解決呢? 神奇的 strong 關(guān)鍵字來了, 先看代碼
- (void)test04
{
Person* p = [[Person alloc]init];
p.name = @"myObject";
NSLog(@"1---%@---%p--%@", p, &p,p.name);
__weak Person *weakObj = p;
NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
void(^testBlock)(void) = ^(){
__strong Person *strongObj = weakObj;
NSLog(@"3---%@---%p--%@", strongObj, &strongObj,strongObj.name);
NSLog(@"w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
};
testBlock();
p = nil;
testBlock();
}
//1---<Person: 0x600001002fb0>---0x7ffeee9d28a8--myObject
//2---<Person: 0x600001002fb0>---0x7ffeee9d28a0--myObject
//3---<Person: 0x600001002fb0>---0x7ffeee9d27c8--myObject
//w---<Person: 0x600001002fb0>---0x600001c48260--myObject
//3---(null)---0x7ffeee9d27c8--(null)
//w---(null)---0x600001c48260--(null)
發(fā)現(xiàn) __strong 修飾的對象仍被置nil 了 怎么回事呢 ?? 接著看.....
- (void)test05
{
Person* p = [[Person alloc]init];
p.name = @"myObject";
NSLog(@"0p---%@---%p--%@", p, &p, p.name);
__weak Person *weakObj = p;
NSLog(@"0w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong Person *strongObj = weakObj;
NSLog(@"0s---%@---%p--%@", strongObj, &strongObj,strongObj.name); //先打印這行
sleep(3); // 睡眠三秒確保 p 被置 nil 后執(zhí)行接下來的代碼
NSLog(@"2w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
NSLog(@"2s---%@---%p--%@", strongObj, &strongObj,strongObj.name);
});
sleep(1); //睡眠1秒讓異步線程block塊執(zhí)行
p = nil; //執(zhí)行過程中將 p 對象置 nil
NSLog(@"1p---%@---%p--%@", p, &p, p.name);
NSLog(@"1w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
sleep(4); //異步線程結(jié)束后 再打印出 person 對象
NSLog(@"3w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
}
//0p---<Person: 0x6000031ac320>---0x7ffee1aa68a8--myObject
//0w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//0s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//1p---(null)---0x7ffee1aa68a8--(null)
//1w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//2w---<Person: 0x6000031ac320>---0x600003de98e0--myObject
//2s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//3w---(null)---0x7ffee1aa68a0--(null)
p = nil 后由__strong 修飾的對象仍然存在。在block執(zhí)行過程中,如果對象用 __strong 修飾 block內(nèi)部依然會繼續(xù)強引用它 。__weak修飾的只要有強指針指向,會一直存在。上面的例子是因為下面的代碼是后續(xù)執(zhí)行的,所以打印出結(jié)果為 nil 。
block 內(nèi)部的 __strong 會在執(zhí)行期間進(jìn)行強引用操作,保證在 block 內(nèi)部 strongObj 始終是可用的。既避免了循環(huán)引用的問題,又可以在 block 內(nèi)部持有該變量
我們平時在使用時,常常先判斷 strongObj 是否為空,然后再執(zhí)行后續(xù)代碼,如下方式
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong Person *strongObj = weakObj;
if (strongObj) {
//
}
});
- copy:block變量定義時為什么用copy關(guān)鍵字
默認(rèn)情況下,block是存檔在棧中,可能被隨時回收,故需要copy操作。這也就是我們在定義block的時候用得時copy (arc 下也可以用strong), 而不是weak等
block默認(rèn)存儲于棧區(qū),訪問外界對象,不會對對象retain。copy會轉(zhuǎn)移至堆,訪問外界對象,會對對象retain,使用__block修飾,則不會對對象retain。
使用copy修飾block是將block轉(zhuǎn)移至堆,而不是copy一份,使用copy保住外界對象,避免使用時對象已釋放。copy操作后需要在對象的dealloc下對block進(jìn)行block_release.