一、是什么
Block本質上也是一個OC對象,底層也是一個結構體,內部也有isa指針。
封裝了函數調用以及函數調用環境的OC對象。
底層結構如 圖所示:
二、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類型(和指向函數的指針很像)
- 格式
typedef 返回值類型 (^新別名)(參數類型列表);
typedef int (^myblock)(int , int);
以后就可以利用這種類型來定義block變量了;
typedef void (^Block)();
Block b1;
//Block類型的變量
b1 = ^{
NSLog(@"myblock");
};
//有參數有返回值
typedef int (^newType)(int , int);
newType nt1 = ^(int a, int b){
retutn a +b;
};
int s = nt1 (12 , 23);
NSLog(@"%d",s);
//聯系定義多個newType的變量
newType n1,n2,n3
n1 = ^(int x, int y){
retutn x*y;
};
四、block對變量的捕獲
//auto 可以不寫,默認的值,離開作用域會自動銷毀
// auto int age = 10;
int a = 10;//傳的變量,block內部存儲的是個變量
static int b = 10;//傳的地址值,block內部存儲的是個地址值
void (^block)(void) = ^{
NSLog(@"捕獲的值局部變量a=%d,靜態變量b=%d,全局變量=%d",a,b,c);
};
a = 20;
b = 30;
c = 40;
block();
//1、為什么局部變量需要捕獲,全局變量不需要捕獲?
// 因為在底層代碼實現里面: 局部變量需要跨函數訪問,所以需要捕獲,而全局變量在哪個函數都可以直接訪,不需要捕獲。
//1、為什么2者有這的差異?
//auto類型代碼執行完就要銷毀,所以是值傳遞,不然再去訪問就會出現問題,因為局部變量已釋放。
// 而靜態變量一直在內存中, 不會銷毀,所以是指針傳遞
void (^block1)(void) = ^{
NSLog(@"self=%p",self);
};
block1();
// self也會被捕獲
2021-09-18 15:16:46.086277+0800 OCStudy[90772:4443186] 捕獲的值a=10,b=30
代碼內部實現:
五、block的類型
block 有3種類型,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
-
MRC環境下的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修飾。
將棧的數據拷貝到堆上,防止局部變量的數據釋放掉以后,block還去訪問無數據的問題。
-
ARC環境下的Block類型:
ARC環境下,編譯器會根據情況自動將棧上的block復制到堆上,有以下情況:
- block作為函數返回值時
- 將block賦值給__strong指針時
- cocoa API中,將block作為參數的,比如GCD的函數
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不能修飾局部變量、靜態變量
編譯器會將__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);
七、 循環引用
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相互強引用,造成循環引用,所以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內部,有一個2秒后需要執行的代碼,也需要用到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");
我們發現后面再去使用age的值時,person已經死掉了,這個時候可以使用使用__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 內如果需要訪問 self 的方法、變量,建議使用 weakSelf。
如果在 Block 內需要多次 訪問 self,則需要使用 strongSelf。
八、關于block的問題
1、怎么解決block循環引用的問題?
__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,因為引用持有關系是:
UIView 的某個負責動畫的對象持有block,block 持有了self因為 self 并不持有 block,所以就沒有循環引用產生,因為就不需要使用 weak self 了。
[UIView animateWithDuration:0.2 animations:^{
self.alpha = 1;
}];
3、為什么 block 里面還需要寫一個 strong self,如果不寫會怎么樣?
在 block 中先寫一個 strong self,其實是為了避免在 block 的執行過程中,突然出現 self 被釋放的尷尬情況。通常情況下,如果不這么做的話,還是很容易出現一些奇怪的邏輯,甚至閃退。
我們以 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 的那行代碼,那么后面的每一行代碼執行時,self 都可能被釋放掉了,這樣很可能造成邏輯異常。
4、block 里 strong self 后,block 不是也會持有 self 嗎?而 self 又持有 block ,那不是又循環引用了?
__weak __typeof(self)weakSelf = self; //1
[self.context performBlock:^{
[weakSelf doSomething]; //2
__strong __typeof(weakSelf)strongSelf = weakSelf; //3
[strongSelf doAnotherSomething];
}];
1.使用__weak __typeof是在編譯的時候,另外創建一個局部變量weak對象來操作self,引用計數不變。block 會將這個局部變量捕獲為自己的屬性,訪問這個屬性,從而達到訪問 self 的效果,因為他們的內存地址都是一樣的
2.因為weakSelf和self是兩個變量,doSomething有可能就直接對self自身引用計數減到0了,所以在[weakSelf doSomething]的時候,你很難控制這里self是否就會被釋放了.weakSelf只能看著
3.__strong __typeof在編譯的時候,實際是對weakSelf的強引用.指針連帶關系self的引用計數會增加.但是你這個是在block里面,生命周期也只在當前block的作用域.所以,當這個block結束, strongSelf隨之也就被釋放了.不會影響block外部的self的生命周期.
5、關于Masonry里面的Block,為什么不需要使用?
關于Masonry里面的Block:函數參數里面的Block是局部的block(棧上),block內部引用self不會造成循環引用;是否會循環引用只看函數內部是否copy了這個block(比如把它付給全局的Block)
6、block的原理是怎樣的?本質是什么?
Block本質上也是一個OC對象,底層也是一個結構體,內部也有isa指針。
封裝了函數調用以及函數調用環境的OC對象
7、__block的作用是什么?有什么使用注意點?
_block可以用于解決block內部無法修改auto變量值的問題
__block不能修飾全局變量、靜態變量(static)
編譯器會將__block變量包裝成一個對象
注意:循環引用問題
8、block的屬性修飾詞為什么是copy?
在MRC環境下,block用Copy修飾。將棧的數據拷貝到堆上,防止局部變量的數據釋放掉以后,block還去訪問無數據的問題。
在ARC環境下,使用strong修飾也可以,編譯器會根據情況自動將棧上的block復制到堆上
9、block在修改NSMutableArray,需不需要添加__block?
不需要
NSMutableArray *arr = [NSMutableArray array];
void (^block)(void) = ^{
arr = nil; 這是修改值
[arr addObject:@"123"]; 不是 修改指針,是使用指針進行操作
[arr addObject:@"456"];
};