當前位置:
首頁 > 最新 > iOS-關於Block,你不得不知

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里很優秀的東西,捕獲變數、代碼傳遞、代碼內聯等特性賦予了它多於代理機制的功能和靈活性;

希望讀者有所收穫!


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Cocoa開發者社區 的精彩文章:

當十二星座碰上程序員,火花四射啊
iOS利用Jenkins實現自動化打包
iOS 實現一個容器視圖控制器
仿QQ錄音以及振幅動畫實現
iOS感測器:使用陀螺儀完成一個小球撞壁的小遊戲

TAG:Cocoa開發者社區 |