一梧兼、前言
代碼塊Block是蘋果在iOS4開始引入的對C語言的擴展萝挤,用來實現(xiàn)匿名函數(shù)的特性御毅,Block是一種特殊的數(shù)據(jù)類型,其可以正常定義變量怜珍、作為參數(shù)端蛆、作為返回值,特殊的Block還可以保存一段代碼酥泛,在需要的時候調用今豆,目前Block已經廣泛應用于iOS開發(fā)中,常用于GCD柔袁、動畫呆躲、排序及各類回調等。(為了方便理解瘦馍,全文以舉例代碼為主)
二歼秽、Block常規(guī)使用
1.Block屬性
// 格式
@property (nonatomic, copy) return_type (^blockName) (var_type);
// 例如 用于發(fā)送本類中的異常時
@property (nonatomic, copy) void(^postErrorMessageHandler)(NSString *errorMsg, CGFloat offsetY);
// 實現(xiàn)如下
- (void)postErrorMsgWith:(NSString *)errorMsg offsetY:(CGFloat)offSetY {
? ? if (self.postErrorMessageHandler) {
? ? ? ? self.postErrorMessageHandler(errorMsg, offSetY);
? ? }
}
// 調用
__weak typeof(self)weakSelf = self;
obj.postErrorMessageHandler = ^(NSString *errorMsg, CGFloat offsetY) {
? ? ?__strong typeof(weakSelf)strongSelf = weakSelf;
? ? ?[strongSelf showToastWith:errorMsg offsetY:offsetY];
};
2.Block作為形參
// 格式
- (void)yourMethod:(return_type (^)(var_type))blockName;
// 例如 用于獲取用戶信息時
- (void)fetchUserInfoWithCompletion:(void (^)(User *userInfo, GetInfoError *error))completion;
// 實現(xiàn)如下
- (void)fetchUserInfoWithCompletion:(void (^)(User *userInfo, GetInfoError *error))completion {
? ? if (self.isHaveCompletionInfo == NO) {
? ? ? ? completion(nil, [GetInfoError errorWithMessage:@"請完善用戶信息" success:NO]);
? ? } else {
? ? ? ? completion(self.userInfo, nil);
? ? }
}
// 調用
__weak typeof(self)weakSelf = self;
[obj fetchUserInfoWithCompletion:^(User *userInfo, GetInfoError *error) {
? ? ?__strong typeof(weakSelf)strongSelf = weakSelf;
? ? ?if (error) {
? ? ? ? ?[strongSelf postErrorMsgWith:error.message];
? ? ?} else {
? ? ? ? ?SaveInfo(userInfo);
? ? ?}
}];
3.使用typedef定義Block類型
觀察上面的使用情況不難看出,在實際使用Block的過程中情组,我們可能需要重復地聲明多個相同返回值相同參數(shù)列表的Block變量燥筷,如果總是重復地編寫一長串代碼來聲明變量會非常繁瑣,所以我們可以使用typedef來定義Block類
// 格式
typedef return_type(^BlockName)(var_type);
// 例如(由上例變形)
typedef void(^MessageHandler)(NSString *errorMsg, CGFloat offsetY);
@property (nonatomic, copy) MessageHandler postErrorMessageHandler;
// 再例如(由上例變形)
typedef void(^Completion)(User *userInfo, GetInfoError *error);
- (void)fetchUserInfoWithCompletion:(Completion)completion;
三院崇、在Block內部訪問局部變量
1.在Block中可以訪問局部變量
// 聲明局部變量age
int age = 18;
void(^logAgeBlock)() = ^{
? ? NSLog(@"current age = %d", age);
};
// 調用后控制臺輸出"current age = 18"
logAgeBlock();
2.Block內局部變量的值在聲明Block確定肆氓,不會隨后面修改而改變,在調用Block時局部變量值是聲明Block時的舊值
int age = 18;
void(^logAgeBlock)() = ^{
? ? NSLog(@"current age = %d", age);
};
age = 24;
// 調用后控制臺輸出"current age = 18"
logAgeBlock();
3.在Block中不可以直接修改局部變量
int age = 18;
void(^logAgeBlock)() = ^{
? ? age ++; //這句報錯: Variable is not assignable(missing __block type specifier)
? ? NSLog(@"current age = %d", age);
};
4.該如何修改局部變量底瓣?
// 聲明局部變量global
__block int age = 18;
void(^logAgeBlock)() = ^{
? ? NSLog(@"current age = %d", age);
};
age = 24;
// 調用后控制臺輸出"current age = 24"
logAgeBlock();
或者
__block int age = 18;
void(^logAgeBlock)() = ^{
? ? age ++; // 這句正確
? ? NSLog(@"current age = %d", age);
};
// 調用后控制臺輸出"current age = 19"
logAgeBlock();
四谢揪、Block引發(fā)的內存泄露
1.在Block的內存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進行強引用,但是在Block被釋放時會自動去掉對該對象的強引用,所以不會造成內存泄漏
User *user = [[User alloc] init];
void(^invokingBlock)() = ^{
? ? NSLog(@"log user : %@", user);
};
invokingBlock();
// user對象在這里可以正常被釋放
2.如果對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環(huán)引用
@interface User : NSObject
@property (nonatomic, copy) void(^invokingBlock)();
@end
@implementation User
- (void)dealloc {
? ? NSLog(@"%s dealloc", __func__);
}
@end
User *user = [[User alloc] init];
user.invokingBlock = ^{
? ? NSLog(@"log user : %@", user);
};
user.invokingBlock();
// 因為invokingBlock作為User的屬性蕉陋,采用copy修飾符修飾(這樣才能保證Block在堆里面,以免Block在棧中被系統(tǒng)釋放)拨扶,所以Block會對User對象進行一次強引用,導致循環(huán)引用無法釋放
另一種情況??????
@interface User : NSObject
@property (nonatomic, copy) void(^invokingBlock)();
- (void)resetBlock;
@end
@implementation User
- (void)resetBlock {
? ? self.invokingBlock = ^{
? ? ? ? NSLog(@"log user : %@", self);
? ? };
}
- (void)dealloc {
? ? NSLog(@"%s dealloc", __func__);
}
@end
User *user = [[User alloc] init];
[user resetBlock];
// User對象在這里無法正常釋放,在resetBlock方法實現(xiàn)中,Block內部對self進行了一次強引用,導致循環(huán)引用無法釋放
3.如果對象內部有一個Block屬性凳鬓,而在Block內部又訪問了該對象,那么會造成循環(huán)引用,解決循環(huán)引用的辦法是使用一個弱引用的指針指向該對象患民,然后在Block內部使用該弱引用指針來進行操作缩举,這樣避免了Block對對象進行強引用
@interface User : NSObject
@property (nonatomic, copy) void(^invokingBlock)();
@end
@implementation User
- (void)dealloc {
? ? NSLog(@"%s dealloc", __func__);
}
@end
User *user = [[User alloc] init];
__weak typeof(user) weakUser = user;
user.invokingBlock = ^{
? ? NSLog(@"log user : %@", weakUser);
};
user.invokingBlock();
// User對象在這里可以正常被釋放
另一種情況??????
@interface User : NSObject
@property (nonatomic, copy) void(^invokingBlock)();
- (void)resetBlock;
@end
@implementation User
- (void)resetBlock {
? ? // 這里為了通用一點,可以使用__weak typeof(self) weakUser = self;
? ? __weak User *weakUser = self;
? ? self.invokingBlock = ^{
? ? ? ? NSLog(@"log user : %@", weakUser);
? ? };
}
- (void)dealloc {
? ? NSLog(@"%s dealloc", __func__);
}
@end
User *user = [[User alloc] init];
[user resetBlock];
// User對象在這里可以正常被釋放
五、寫在最后
本文旨在淺析Block的常規(guī)使用及注意事項(主要是循環(huán)引用導致的內存泄露)匹颤,內容不深仅孩,還是以舉例用法為主;
本人經歷了 ->不太會用 -> 經常會有 -> 內存泄露 -> 正確使用印蓖,由衷的喜歡Block辽慕;
Block是OC里很優(yōu)秀的東西,捕獲變量赦肃、代碼傳遞溅蛉、代碼內聯(lián)等特性賦予了它多于代理機制的功能和靈活性;