一抓督、是什么
Block本質(zhì)上也是一個OC對象裹粤,底層也是一個結(jié)構(gòu)體,內(nèi)部也有isa指針恬吕。
封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象儒喊。
底層結(jié)構(gòu)如 圖所示:
二、block的基本使用
2.1 block的形式
- 無參無返回值
//定義一個block
void (^myblock1)()=^(){
NSLog(@"myblock1");
};
//使用block
myblock1();
- 無參有返回值
int (^myblock2)()=^(){
return 8;
NSLog(@"myblock2");
};
myblock2();
- 有參無返回值
void (^myblock3)(int,int)=^(int a, int b){
NSLog(@"%d",a+b);
};
myblock3(3,7);
//也可以先定義變量币呵,再賦值
myblock3 =^(int x,int y){
NSLog(@"%d",x-y);
};
myblock3(8,7);
- 有參有返回值
int (^myblock4)(int,int)=^(int a, int b){
NSLog(@"%d",a+b);
return a + b;
};
int sum = myblock4(3,4);
NSLog(@"%d",sum);
三怀愧、block的typedef
利用typedef定義block類型(和指向函數(shù)的指針很像)
- 格式
typedef 返回值類型 (^新別名)(參數(shù)類型列表);
typedef int (^myblock)(int , int);
以后就可以利用這種類型來定義block變量了余赢;
typedef void (^Block)();
Block b1;
//Block類型的變量
b1 = ^{
NSLog(@"myblock");
};
//有參數(shù)有返回值
typedef int (^newType)(int , int);
newType nt1 = ^(int a, int b){
retutn a +b;
};
int s = nt1 (12 , 23);
NSLog(@"%d"芯义,s);
//聯(lián)系定義多個newType的變量
newType n1,n2,n3
n1 = ^(int x, int y){
retutn x*y;
};
四、block對變量的捕獲
//auto 可以不寫妻柒,默認的值扛拨,離開作用域會自動銷毀
// auto int age = 10;
int a = 10;//傳的變量,block內(nèi)部存儲的是個變量
static int b = 10;//傳的地址值举塔,block內(nèi)部存儲的是個地址值
void (^block)(void) = ^{
NSLog(@"捕獲的值局部變量a=%d,靜態(tài)變量b=%d绑警,全局變量=%d",a,b,c);
};
a = 20;
b = 30;
c = 40;
block();
//1、為什么局部變量需要捕獲央渣,全局變量不需要捕獲计盒?
// 因為在底層代碼實現(xiàn)里面: 局部變量需要跨函數(shù)訪問,所以需要捕獲芽丹,而全局變量在哪個函數(shù)都可以直接訪北启,不需要捕獲。
//1拔第、為什么2者有這的差異咕村?
//auto類型代碼執(zhí)行完就要銷毀,所以是值傳遞蚊俺,不然再去訪問就會出現(xiàn)問題懈涛,因為局部變量已釋放。
// 而靜態(tài)變量一直在內(nèi)存中泳猬, 不會銷毀批钠,所以是指針傳遞
void (^block1)(void) = ^{
NSLog(@"self=%p",self);
};
block1();
// self也會被捕獲
2021-09-18 15:16:46.086277+0800 OCStudy[90772:4443186] 捕獲的值a=10,b=30
代碼內(nèi)部實現(xiàn):
五宇植、block的類型
block 有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型价匠,最終都是繼承自NSBlock類型
-
MRC環(huán)境下的Block類型:
打開MRC
void (^block)(void) = ^{
NSLog(@"hello");
};
NSLog(@"block的類型=%@",[block class]);
NSLog(@"block的父類=%@",[[block class] superclass]);
NSLog(@"block的父類的父類=%@",[[[block class] superclass] superclass]);
block的類型=__NSGlobalBlock__
block的父類=NSBlock
block的父類的父類=NSObject
void (^block)(void) = ^{//__NSGlobalBlock__
NSLog(@"hello");
};
NSLog(@"block的類型=%@",[block class]);
int a = 10;
void (^block1)(void) = ^{//__NSStackBlock__
NSLog(@"age=%d",a);
};
NSLog(@"block1的類型=%@",[block1 class]);
NSLog(@"block2的類型=%@",[[block1 copy] class]);//__NSMallocBlock__
NSLog(@"block3的類型=%@",[[block copy] class]);//__NSGlobalBlock__
2021-09-20 15:50:48.324228+0800 dddd[50990:5734435] block的類型=__NSGlobalBlock__
2021-09-20 15:50:48.325460+0800 dddd[50990:5734435] block1的類型=__NSStackBlock__
2021-09-20 15:50:48.325632+0800 dddd[50990:5734435] block2的類型=__NSMallocBlock__
2021-09-20 15:50:48.325756+0800 dddd[50990:5734435] block3的類型=__NSGlobalBlock__
所以在MRC時代当纱,block用Copy修飾。
將棧的數(shù)據(jù)拷貝到堆上踩窖,防止局部變量的數(shù)據(jù)釋放掉以后坡氯,block還去訪問無數(shù)據(jù)的問題。
-
ARC環(huán)境下的Block類型:
ARC環(huán)境下,編譯器會根據(jù)情況自動將棧上的block復制到堆上洋腮,有以下情況:
- block作為函數(shù)返回值時
- 將block賦值給__strong指針時
- cocoa API中箫柳,將block作為參數(shù)的,比如GCD的函數(shù)
void (^block)(void) = ^{//__NSGlobalBlock__
NSLog(@"hello");
};
int a = 10;
void (^block1)(void) = ^{//__NSMallocBlock__
NSLog(@"age=%d",a);
};
NSLog(@"block1的類型=%@",[block1 class]);
2021-09-20 16:15:44.560817+0800 OCStudy[51711:5753274] block的類型=__NSGlobalBlock__
2021-09-20 16:15:44.561154+0800 OCStudy[51711:5753274] block1的類型=__NSMallocBlock__
六啥供、 __block修改局部auto變量
__block不能修飾局部變量悯恍、靜態(tài)變量
編譯器會將__block變量包裝成一個對象
__block int age = 10;//會將age包裝成一個對象
NSLog(@"age的值=%d,地址=%p",age,&age);
NSMutableArray *arr = [NSMutableArray array];
void (^block)(void) = ^{
age = 20;
NSLog(@"age的值=%d,地址=%p",age,&age);
// arr = nil;//這是修改值
[arr addObject:@"123"];//不是 修改指針,是使用指針
[arr addObject:@"456"];
};
block();
NSLog(@"age的值=%d,地址=%p",age,&age);
NSLog(@"arr的值=%@",arr);
七伙狐、 循環(huán)引用
typedef void (^TestBlock)(void)
@interface Person
@property (nonatomic , strong) TestBlock testblock;
@end
-(void)dealloc{
NSLog(@"person-delloc,死了");
}
- 不使用weakself的情況涮毫。
Person *person = [[Person alloc] init];
person.age = 100;
person.testblock = ^{
NSLog(@"age2 = %d",person.age);
};
person.testblock();
NSLog(@"end");
person和block相互強引用,造成循環(huán)引用贷屎,所以person對象無法dealloc罢防。
- 使用weakself
Person *person = [[Person alloc] init];
person.age = 100;
__weak typeof(person) weakPerson = person;
person.testblock = ^{
NSLog(@"age1 = %d",weakPerson.age);
};
person.testblock();
NSLog(@"end");
由于一個使用__weak修飾,破除了相互強引用唉侄,所以peron可以delloc
- 來看下面一段代碼 咒吐,在block內(nèi)部,有一個2秒后需要執(zhí)行的代碼属划,也需要用到age的值
Person *person = [[Person alloc] init];
person.age = 100;
__weak typeof(person) weakPerson = person;
person.testblock = ^{
NSLog(@"age1 = %d",weakPerson.age);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age2 = %d",weakPerson.age);
});
};
person.testblock();
NSLog(@"end");
我們發(fā)現(xiàn)后面再去使用age的值時恬叹,person已經(jīng)死掉了,這個時候可以使用使用__strong
Person *person = [[Person alloc] init];
person.age = 100;
__weak typeof(person) weakPerson = person;
person.testblock = ^{
NSLog(@"age1 = %d",weakPerson.age);
__strong __typeof(weakPerson)strongPerson = weakPerson;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age2 = %d",strongPerson.age);
});
};
person.testblock();
NSLog(@"end");
在 Block 內(nèi)如果需要訪問 self 的方法同眯、變量绽昼,建議使用 weakSelf。
如果在 Block 內(nèi)需要多次 訪問 self嗽测,則需要使用 strongSelf绪励。
八、關(guān)于block的問題
1唠粥、怎么解決block循環(huán)引用的問題?
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
});
2停做、什么時候在 block里面用self晤愧,不需要使用weakself?
block和其他的對象之間不會相互強引用
比如UIView的動畫代碼,我們在使用UIView animateWithDuration:方法做動畫的時候蛉腌,并不需要使用weakself官份,因為引用持有關(guān)系是:
UIView 的某個負責動畫的對象持有block只厘,block 持有了self因為 self 并不持有 block,所以就沒有循環(huán)引用產(chǎn)生舅巷,因為就不需要使用 weak self 了羔味。
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
3、為什么 block 里面還需要寫一個 strong self钠右,如果不寫會怎么樣赋元?
在 block 中先寫一個 strong self,其實是為了避免在 block 的執(zhí)行過程中飒房,突然出現(xiàn) self 被釋放的尷尬情況搁凸。通常情況下,如果不這么做的話狠毯,還是很容易出現(xiàn)一些奇怪的邏輯护糖,甚至閃退。
我們以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代碼舉例:
__weak__typeof(self)weakSelf =self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong__typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus= status;
if(strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}};
如果沒有 strongSelf 的那行代碼嚼松,那么后面的每一行代碼執(zhí)行時嫡良,self 都可能被釋放掉了,這樣很可能造成邏輯異常献酗。
4寝受、block 里 strong self 后,block 不是也會持有 self 嗎凌摄?而 self 又持有 block 羡蛾,那不是又循環(huán)引用了?
__weak __typeof(self)weakSelf = self; //1
[self.context performBlock:^{
[weakSelf doSomething]; //2
__strong __typeof(weakSelf)strongSelf = weakSelf; //3
[strongSelf doAnotherSomething];
}];
1.使用__weak __typeof是在編譯的時候,另外創(chuàng)建一個局部變量weak對象來操作self锨亏,引用計數(shù)不變痴怨。block 會將這個局部變量捕獲為自己的屬性,訪問這個屬性器予,從而達到訪問 self 的效果浪藻,因為他們的內(nèi)存地址都是一樣的
2.因為weakSelf和self是兩個變量,doSomething有可能就直接對self自身引用計數(shù)減到0了,所以在[weakSelf doSomething]的時候,你很難控制這里self是否就會被釋放了.weakSelf只能看著
3.__strong __typeof在編譯的時候,實際是對weakSelf的強引用.指針連帶關(guān)系self的引用計數(shù)會增加.但是你這個是在block里面,生命周期也只在當前block的作用域.所以,當這個block結(jié)束, strongSelf隨之也就被釋放了.不會影響block外部的self的生命周期.
5、關(guān)于Masonry里面的Block,為什么不需要使用乾翔?
關(guān)于Masonry里面的Block:函數(shù)參數(shù)里面的Block是局部的block(棧上)爱葵,block內(nèi)部引用self不會造成循環(huán)引用;是否會循環(huán)引用只看函數(shù)內(nèi)部是否copy了這個block(比如把它付給全局的Block)
6反浓、block的原理是怎樣的萌丈?本質(zhì)是什么?
Block本質(zhì)上也是一個OC對象雷则,底層也是一個結(jié)構(gòu)體辆雾,內(nèi)部也有isa指針。
封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象
7月劈、__block的作用是什么度迂?有什么使用注意點藤乙?
_block可以用于解決block內(nèi)部無法修改auto變量值的問題
__block不能修飾全局變量、靜態(tài)變量(static)
編譯器會將__block變量包裝成一個對象
注意:循環(huán)引用問題
8惭墓、block的屬性修飾詞為什么是copy坛梁?
在MRC環(huán)境下,block用Copy修飾腊凶。將棧的數(shù)據(jù)拷貝到堆上划咐,防止局部變量的數(shù)據(jù)釋放掉以后,block還去訪問無數(shù)據(jù)的問題吭狡。
在ARC環(huán)境下尖殃,使用strong修飾也可以,編譯器會根據(jù)情況自動將棧上的block復制到堆上
9、block在修改NSMutableArray划煮,需不需要添加__block送丰?
不需要
NSMutableArray *arr = [NSMutableArray array];
void (^block)(void) = ^{
arr = nil; 這是修改值
[arr addObject:@"123"]; 不是 修改指針,是使用指針進行操作
[arr addObject:@"456"];
};