iOS-關於Block,你不得不知
一、前言
代碼塊Block是蘋果在iOS4開始引入的對C語言的擴展,用來實現匿名函數的特性,Block是一種特殊的數據類型,其可以正常定義變數、作為參數、作為返回值,特殊的Block還可以保存一段代碼,在需要的時候調用,目前Block已經廣泛應用於iOS開發中,常用於GCD、動畫、排序及各類回調等。(為了方便理解,全文以舉例代碼為主)
二、Block常規使用
1.Block屬性
// 格式
@property (nonatomic, copy) return_type (^blockName) (var_type);
// 例如 用於發送本類中的異常時
@property (nonatomic, copy) void(^postErrorMessageHandler)(NSString *errorMsg, CGFloat offsetY);
// 實現如下
- (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;
// 實現如下
- (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的過程中,我們可能需要重複地聲明多個相同返回值相同參數列表的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引發的內存泄露
1.在Block的內存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進行強引用,但是在Block被釋放時會自動去掉對該對象的強引用,所以不會造成內存泄漏
User *user = [[User alloc] init];
void(^invokingBlock)() = ^{
NSLog(@"log user : %@", user);
};
invokingBlock();
// user對象在這裡可以正常被釋放
2.如果對象內部有一個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];
user.invokingBlock = ^{
NSLog(@"log user : %@", user);
};
user.invokingBlock();
// 因為invokingBlock作為User的屬性,採用copy修飾符修飾(這樣才能保證Block在堆裡面,以免Block在棧中被系統釋放),所以Block會對User對象進行一次強引用,導致循環引用無法釋放
另一種情況
@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方法實現中,Block內部對self進行了一次強引用,導致循環引用無法釋放
3.如果對象內部有一個Block屬性,而在Block內部又訪問了該對象,那麼會造成循環引用,解決循環引用的辦法是使用一個弱引用的指針指向該對象,然後在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的常規使用及注意事項(主要是循環引用導致的內存泄露),內容不深,還是以舉例用法為主;
本人經歷了 ->不太會用 -> 經常會有 -> 內存泄露 -> 正確使用,由衷的喜歡Block;
Block是OC里很優秀的東西,捕獲變數、代碼傳遞、代碼內聯等特性賦予了它多於代理機制的功能和靈活性;
希望讀者有所收穫!
※當十二星座碰上程序員,火花四射啊
※iOS利用Jenkins實現自動化打包
※iOS 實現一個容器視圖控制器
※仿QQ錄音以及振幅動畫實現
※iOS感測器:使用陀螺儀完成一個小球撞壁的小遊戲
TAG:Cocoa開發者社區 |