iOS編寫高質量Objective-C程式碼(六)
《編寫高質量OC程式碼》已經順利完成一二三四五六七篇!
附上鍊接:
iOS 編寫高質量Objective-C程式碼(一)—— 簡介
iOS 編寫高質量Objective-C程式碼(二)—— 物件導向
iOS 編寫高質量Objective-C程式碼(三)—— 介面和API設計
iOS 編寫高質量Objective-C程式碼(四)—— 協議與分類
iOS 編寫高質量Objective-C程式碼(五)—— 記憶體管理機制
iOS 編寫高質量Objective-C程式碼(六)—— block專欄
iOS 編寫高質量Objective-C程式碼(七)—— GCD專欄
本篇的主題是iOS中的 “Block的原理及應用”。
先簡單介紹一下今天的主角:block
。
- block(塊):是一種 “ 詞法閉包 ”,通過block,開發者可將程式碼塊像物件一樣傳遞。
一、理解“block”的概念:
1. block的資料結構:
通過clang命令列工具(OC轉C++),我們先來看一下block
的內部資料結構大概是什麼樣子的?
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
解析:很顯然,Block_layout是一個結構體:裡面有一個isa指標,指向Class物件。還有一個函式指標,指向了塊的實現程式碼。
2. block的三種型別:全域性塊、棧塊、堆塊。
根據block在記憶體中的位置,block被分成三種型別:
型別 | 記憶體位置 | 介紹 |
---|---|---|
__NSStackBlock__ | 棧區 | 棧內有效,出棧後銷燬。 |
__NSMallocBlock__ | 堆區 | copy到堆空間上。可以在定義的那個範圍之外使用。 |
__NSGlobalBlock__ | 全域性區 | 不捕捉任何外部變數,全部資訊在編譯器就已確定。 |
-
1. NSStackBlock 棧塊:
棧塊儲存於棧區,超出變數作用域,棧上的block
以及宣告的_block
都會被銷燬。
例如:
__block NSString *name = @"QiShare";
void (^block)(void) = ^{
NSLog(@"%@ is an iOS team which loves to share technology.", name);
};
NSLog(@"block = %@", block);
小知識點:當block內部需要修改或訪問外部變數時,外部變數需要額外用
__block
修飾。否則修改不了。
我們來看下列印:
什麼?居然是
__NSMallocBlock__
(堆塊)?
那是因為ARC環境下,編譯器自動幫我們加了copy操作。
這時我們關掉ARC:設定Objective-C Automatic Reference Counting = NO
。再來看下列印:
-
2. NSMallocBlock 堆塊:
堆block記憶體位於堆區,在變數作用域結束時依然可以使用。
通過上面的例子:
在ARC下,block會預設加上copy操作:變成__NSMallocBlock__
。
-
3. NSGlobalBlock 全域性塊:
塊中無任何外界物件,所需的記憶體在編譯時就可以確定,記憶體位於全域性區。
類似於“單例”,copy是一個空操作。
例如:
void (^qiShare)(void) = ^{
NSLog(@"We love sharing.");
};
NSLog(@"%@",qiShare);
二、為常用的block型別建立typedef
為了增加程式碼的可讀性 和 可擴充性,
需要為常用的block起個別名。
以typedef
為塊起別名,也可令塊變數用起來更加簡單~
比如:
- (void)getDataWithToken:(NSString *)token success:(void (^)(id responseDic))success;
//! 以上要改成下面這種
typedef void (^SuccessBlock)(id responseDic);
- (void)getDataWithToken:(NSString *)token success:(SuccessBlock)success;
三、用handler塊降低程式碼分散程度
在我們iOS開發中,經常會非同步執行一些任務,等待任務執行結束後再通知物件呼叫相關方法。
一般有兩種做法:
- 第一種:使用NSNotificationCenter:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
- 第二種:使用委託協議:詳情見[iOS 編寫高質量Objective-C程式碼(四)]()。
- 第三種:使用block回撥:直接把block物件當做引數傳給相關方法執行。
舉個例子:AFNetworking的API設計及使用就是block回撥
- 介面設計:
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {
return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}
- 使用:
AFHTTPSessionManager *manger =[AFHTTPSessionManager manager];
NSString *urlString = @"";
NSMutableDictionary *parameter= @{@"":@"",@"":@""};
[manger POST:urlString
parameters:parameter
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"成功");
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error);
}];
}
四、用block引用其所屬物件時避免出現迴圈引用
在我們日常開發中,如果block使用不當,很容易導致記憶體洩漏。
- 理由:如果
block
被當前ViewController(self
)持有,這時,如果block內部再持有ViewController(self
),就會造成迴圈引用。 - 解決方案:在
block
外部弱化self
,再在block內部強化已經弱化的weakSelf
For Example:
__weak typeof(self) weakSelf = self;
[self.operationQueue addOperationWithBlock:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (completionHandler) {
KTVHCLogDataStorage(@"serial reader async end, %@", request.URLString);
completionHandler([strongSelf serialReaderWithRequest:request]);
}
}];
當然,也不是所有block中使用到self
都要先弱化成weakSelf
,再強化成strongSelf
,
只要block
沒有被self
所持有的,在block
中就可以使用self
。
比如下面:
[QiNetwork requestBlock:^(id responsObject) {
NSLog(@"%@",self.name);
}];
另外,記憶體洩漏檢測相關詳情請看:iOS 記憶體洩漏排查方法及原因分析
相關文章
- iOS 編寫高質量Objective-C程式碼(六)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(三)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(八)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(四)iOSObjectC程式
- iOS編寫高質量Objective-C程式碼(二)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(五)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(七)iOSObjectC程式
- iOS 編寫高質量Objective-C程式碼(一)—— 簡介iOSObjectC程式
- iOS 有效編寫高質量Objective-C方法(一)iOSObject
- iOS-有效編寫高質量Objective-C方法-三iOSObject
- [轉]高質量JAVA程式碼編寫規範Java
- 怎樣編寫高質量的java程式碼Java
- 編寫高質量的程式碼,從命名入手
- [編寫高質量iOS程式碼的52個有效方法](九)塊(block)iOSBloC
- 編寫高質量程式碼
- 如何編寫高質量的C#程式碼(一)C#
- 編寫高質量iOS與OS X程式碼的52個有效方法(一)iOS
- 編寫高質量iOS與OS X程式碼的52個有效方法(五)iOS
- 編寫高質量iOS與OS X程式碼的52個有效方法(二)iOS
- 編寫高質量iOS有效方法總結(一)iOS
- 如何編寫高質量和可維護的程式碼
- [編寫高質量iOS程式碼的52個有效方法](七)記憶體管理(上)iOS記憶體
- [編寫高質量iOS程式碼的52個有效方法](八)記憶體管理(下)iOS記憶體
- [編寫高質量iOS程式碼的52個有效方法](十)Grand Central Dispatch(GCD)iOSGC
- 我們應該如何編寫高質量的前端程式碼前端
- 藉助 SublimeLinter 編寫高質量的 JS & CSS 程式碼JSCSS
- 藉助SublimeLinter編寫高質量的JavaScript & CSS程式碼JavaScriptCSS
- 編寫高質量程式碼的思考
- 《Effective JavaScript 編寫高質量JavaScript程式碼的68個有效方法》JavaScript
- 如何書寫高質量的jQuery程式碼jQuery
- 編寫靈活、穩定、高質量的HTML程式碼的規範HTML
- 編寫靈活、穩定、高質量的CSS程式碼的規範CSS
- iOS-《編寫高質量程式碼》筆記-第一章iOS筆記
- 提高程式碼質量:如何編寫函式函式