一、什么是block?
按照蘋果官方文檔的說法,block是一個oc對象。(原文:Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. )其實,本質就是一個函數。
當然,block也可以理解成oc的一種數據類型,我個人認為把block理解成一種數據類型更便于我們理解和記憶。下面我就從這個角度詳細介紹block。
二、block的介紹
我們在定義一個變量的時候,都包含了以下三個步驟:
1、確定這個變量的數據類型
2、聲明變量
3、定義變量
// 1.比如確定這個數據是 int 類型
int value ; // 2.聲明
value = 15 ; // 3.定義
// 當然,也可以聲明的同時進行定義
int value = 15 ;
從代碼中認識block
例如:
// 1.一個有參數有返回值的block類型:int (^) (int ,int)
int (^block1) (int ,int); // 2.block的聲明
block1 = ^int(int n1,int n2){ // 3.block的定義
int sum = n1 + n2;
return sum;
}
block1(10,20); // 4.block的調用
// 當然,也可以聲明的同時進行定義
void(^block2)(int) = ^(int a){
NSLog(@"你輸入的值是:%d",a );
};
block2(999);
// 我們可以看出block2的類型是:void(^)(int)
1.block的類型
格式:返回值類型(^)(block參數類型)
block的類型大致可以分為四種:
a: 無參數,無返回值
b: 無參數,有返回值
c: 有參數,無返回值
d: 有參數,有返回值
2.block的聲明
格式:**返回值類型(^Block變量名)(block參數類型 參數變量名) **
int (^block11)(int a,int b);
int (^block22)(int,int); // 注意:參數變量名可以省略
3.block的定義
格式:^返回值類型(參數類型 參數變量名) { // 代碼塊 } ;
// 注意1:聲明的時候參數的變量名可以省略,當定義是參數的變量名一定不能省略
void(^block111)(int) = ^void(int a) {
};
int(^block222)(int) = ^int(int a) {
return 2;
};
// 注意2:返回值的類型是可以省略的
void(^block111)(int a) = ^(int a) {
};
int(^block222)(int a) = ^(int a) {
return 2;
};
// 注意3: 當沒有返回值,沒有參數時,可以省略括號
void(^block333)() = ^{
};
4.block的調用
格式:block變量名(參數);
5.如何聲明一個block屬性
方式一:
@interface ViewController ()
@property (nonatomic, strong) void(^block)(); // 方式一
@end
方式二:
typedef void(^BlockType)(); // BlockType:block類型別名
@interface ViewController ()
@property (nonatomic, assign) BlockType block1; // 方式二
@end
三、block的內存管理
我們都知道內存是可以劃分成堆,棧,方法區,常量區,全局區5個區。而block是可能被存放在以下三個區中的任意一個中。
1.在全局靜態區:_NSConcreteGlobalBlock
2.在棧中:_NSConcreteStackBlock
3.在堆中:_NSConcreteMallocBlock
MRC中:
1.如果block沒有訪問外部的局部變量或者局部變量被static修飾,block默認存放在"全局區"
2.如果block訪問外部的局部變量,block存放在"棧"里面
MRC:不能使用retain聲明block,依然放在棧里面,會自動銷毀.
MRC:使用copy聲明block,才會放在堆里面.
ARC如何管理Block:
1.如果block沒有訪問外部的局部變量或者局部變量被static修飾,block默認存放在"全局區"
2.如果block訪問外部的局部變量,block存放在"堆"里面
ARC:使用strong聲明block,不要使用weak.
四、block的循環引用問題
------------“你強我就強,你弱我就弱”------------
在block的內部訪問了一個對象:
1.如果這個對象本來就有強指針指著,那么這個block也會強引用這個對象;
2.如果這個對象只有弱指針引著,那么這個block就不會對這個對象進行強引用。
Person類:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,strong)NSString *name; // 姓名
@property(nonatomic,assign)int age; // 年齡
@property(nonatomic,assign)int height; // 體重
// 聲明block
@property(nonatomic,strong) void(^block)();
- (void)eat;
@end
@implementation Person
- (void)eat
{
self.height += 1 ;
_block = ^{
// 在block的內部訪問了自身這個對象。
NSLog(@"吃完飯后的體重是:%d",self.height);
};
_block();
}
@end
Person類外部使用:
// 這時候,p1這個強指針引著Person對象。
Person *p1 = [[Person alloc] init];
// eat內部調用block時,block就會強引用這person對象;
// 而本身person對象內部就強引著block。
[p1 eat];
// person對象和block兩者互相強引著,誰也不能釋放。
解決方案:
- (void)eat
{
self.height += 1 ;
//聲明weakSelf弱指針(解決方案)
__weak typeof(self) weakSelf = self;
_block = ^{
NSLog(@"吃完飯后的體重是:%d",weakSelf.height);
};
_block();
}
更為復雜的情況:?? 延遲操作或者異步任務 ??
- (void)viewDidLoad
// 聲明weakSelf弱指針
__weak typeof(self) weakSelf = self;
_block = ^{
//因為這里如果不用強指針的話,延時后,可能訪問的這個對象就會銷毀了,那么這個block的內部就沒辦法訪問這個對象了
//這樣用強指針引著,就保證了只要block還在,那么這個對象就不會銷毀
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf);
});
};
_block();
}