iOS開發之基礎篇(14)—— Block
版本
Xcode 9.1
block簡介
block是一個OC物件,於iOS4開始引入。其本身封裝了一段程式碼,可被當作變數、當作引數或作為返回值。block常用於GCD、動畫、排序及各類回撥傳值中。
block程式碼結構圖
注:圖片來自這裡
示例1:
// 建立一個block
int(^myBlock)(int) = ^(int num) {
return num * 3;
};
// 呼叫block
NSLog(@"%d",myBlock(3)); // 結果輸出9
示例2:
如果需要重複宣告多個相同引數和返回值的block,我們可以用typedef來定義block型別。
// 宣告一個block
typedef int(^myBlock)(int);
// 例項化第一個block
myBlock block1 = ^(int num) {
return num * 3;
};
// 例項化第二個block
myBlock block2 = ^(int num) {
return num * 4;
};
// 呼叫block
NSLog(@"%d, %d",block1(3),block2(3)); // 結果為9, 12
block分類
按照儲存區域可分為三類:
- NSGlobalBlock
- NSStackBlock
- NSMallocBlock
1. NSGlobalBlock
全域性block——沒有用到外界變數,或者只用到全域性變數、靜態(static)變數。生命週期從建立到應用程式結束。
示例:
/* 情況1:不使用外部變數 */
// 宣告一個沒有用到外部變數的block
void (^globalBlock1)(void) = ^{
};
// 呼叫block
globalBlock1();
// 檢視block型別
NSLog(@"globalBlock1:%@",globalBlock1);
/* 情況2:使用全域性變數和靜態變數 */
// 新建靜態變數
static NSString *staticStr = @"我是靜態變數";
// 宣告一個使用全域性變數和靜態變數的block
void (^globalBlock2)(void) = ^{
NSLog(@"%@, %@",globalStr,staticStr);
};
// 呼叫block
globalBlock2();
// 檢視block型別
NSLog(@"globalBlock2:%@",globalBlock2);
結果如下:
2. NSStackBlock
棧block——用到區域性變數、成員屬性/變數,且沒有強指標引用。生命週期由系統管理,超出作用域(函式返回時)馬上被銷燬。
/* 情況1:用到區域性變數 */
// 新建一個區域性變數
NSString *localStr = @"我是區域性變數";
// 檢視block型別, 直接在NSLog裡面呼叫使用了局部變數的block,這樣就沒有強指標引用了
NSLog(@"stackBlock1:%@",^{NSLog(@"%@",localStr);});
/* 情況2:用到成員屬性/變數 */
// 給成員變數、成員屬性賦值
_variateStr = @"我是成員變數";
self.propertyStr = @"我是成員屬性";
// 檢視block型別, 直接在NSLog裡面呼叫使用了成員屬性/變數的block,這樣就沒有強指標引用了
NSLog(@"stackBlock2:%@",^{NSLog(@"%@, %@",_variateStr,self.propertyStr);});
結果:
3. NSMallocBlock
堆block——有強指標引用或使用有copy修飾的成員屬性/變數。沒有強指標引用即銷燬,生命週期需手動管理。
/* 情況1:用到強指標引用 */
// 新建一個區域性變數
NSString *localStr = @"我是區域性變數";
// 宣告一個使用強指標引用的block
void (^mallocBlock1)(void) = ^{
NSLog(@"%@",localStr);
};
// 呼叫block
mallocBlock1();
// 檢視block型別
NSLog(@"mallocBlock1:%@",mallocBlock1);
/* 情況2:用到有copy修飾的成員屬性/變數 */
// 給成員變數、成員屬性賦值
_variateStr = @"我是成員變數";
self.propertyStr = @"我是成員屬性";
// 宣告一個使用有copy修飾的成員屬性的block
void (^mallocBlock2)(void) = ^{
NSLog(@"%@, %@",_variateStr, self.propertyStr);
};
// 呼叫block
mallocBlock2();
// 檢視block型別
NSLog(@"mallocBlock2:%@",mallocBlock2);
結果:
block訪問外部變數
1. 訪問區域性變數
在block中可以訪問區域性變數,但不能直接修改區域性變數。
示例1:
// 宣告區域性變數local
int local = 100;
// 宣告block
void(^myBlock)(void) = ^{
// local++; // 修改local值會報錯
NSLog(@"local1 = %d", local);
};
// 在呼叫block之前改變local的值
local = 101;
// 呼叫block
myBlock();
// 檢視local改變後的值
NSLog(@"local2 = %d", local);
列印結果:
原理分析:
在block定義時是將區域性變數的值傳給block變數所指向的結構體,因此在呼叫block之前對區域性變數進行修改並不會影響block內部的值。同時傳進來的內部值也是不可修改的。
但是,我們有時候又想在block內部修改block外的區域性變數,應該怎麼辦呢?
使用__block修飾的區域性變數,在block中可以直接修改
示例2:
// 宣告區域性變數local (用__block修飾)
__block int local = 100;
// 宣告block
void(^myBlock)(void) = ^{
NSLog(@"修改前local1 = %d", local);
local++; // 不會報錯
NSLog(@"修改後local1 = %d", local);
};
// 在呼叫block前修改local值
local = 200;
// 呼叫block
myBlock();
// 檢視local改變後的值
NSLog(@"local2 = %d", local);
列印結果:
原理分析:
在區域性變數前使用__block修飾,block定義時是將區域性變數的指標傳給block變數所指向的結構體,因此在呼叫block之前對區域性變數進行修改會影響block內部的值。同時內部的值也是可以修改的。
2. 訪問靜態變數
在block中可以訪問靜態變數,也可以直接修改靜態變數。
示例:
// 宣告靜態變數staticInt
static int staticInt = 100;
// 宣告block
void(^myBlock)(void) = ^{
NSLog(@"修改前staticInt1 = %d", staticInt);
staticInt++; // 不會報錯
NSLog(@"修改後staticInt1 = %d", staticInt);
};
// 在呼叫block前修改staticInt值
staticInt = 200;
// 呼叫block
myBlock();
// 檢視staticInt改變後的值
NSLog(@"staticInt2 = %d", staticInt);
列印結果:
原理分析:
block定義時是將靜態變數的指標傳給Block變數所指向的結構體,因此在呼叫block之前對靜態變數進行修改會影響block內部的值。同時內部的值也是可以修改的。
3. 訪問全域性變數
在block中可以訪問全域性變數,也可以直接修改全域性變數。
示例:
// 先在@implementation前定義一個全域性變數globalInt
// 宣告block
void(^myBlock)(void) = ^{
NSLog(@"修改前globalInt1 = %d", globalInt);
globalInt++; // 不會報錯
NSLog(@"修改後globalInt1 = %d", globalInt);
};
// 在呼叫block前修改globalInt值
globalInt = 200;
// 呼叫block
myBlock();
// 檢視globalInt改變後的值
NSLog(@"globalInt2 = %d", globalInt);
列印結果:
原理分析:
全域性變數所佔用的記憶體只有一份,供所有函式(方法)共同呼叫,在block定義時並未將全域性變數的值或者指標傳給block變數所指向的結構體,因此在呼叫Block之前對區域性變數進行修改會影響block內部的值。同時內部的值也是可以修改的。
block在ARC下的記憶體管理
如果物件內部有一個block屬性,而在block內部又訪問了該物件,那麼會造成迴圈引用
錯誤示例:
// 宣告block成員屬性
@property (nonatomic, copy) void(^myBlock)(void);
// 定義block
self.myBlock = ^{
NSLog(@"%@", self); // 此句造成迴圈引用,編譯報錯
};
// 執行block
self.myBlock();
解決辦法:使用一個弱引用的指標指向該物件,然後在Block內部使用該弱引用指標來進行操作。
正確示例:
// 宣告block成員屬性
@property (nonatomic, copy) void(^myBlock)(void);
// 宣告一個弱引用指標物件
__weak typeof(self) weakSelf = self;
// 定義block
self.myBlock = ^{
NSLog(@"%@", weakSelf); // 使用弱引用指標物件
};
// 執行block
self.myBlock();
如果擔心在呼叫Block之前引用的物件已經被釋放,那麼我們需要在Block內部再定義一個強指標來指向該物件
官方示例:
// 宣告block成員屬性
@property (nonatomic, copy) void(^myBlock)(void);
// 宣告一個弱引用指標物件
__weak typeof(self) weakSelf = self;
// 定義block
self.myBlock = ^{
typeof(self) strongSelf = weakSelf; // 宣告一個強引用指標物件
NSLog(@"%@", strongSelf); // 使用強引用指標物件
};
// 執行block
self.myBlock();
block的傳值應用
先看看效果:
從介面1載入介面2,然後在介面2返回時回傳textField裡的text。方法步驟:
- 介面1(接收方):
1、定義(實現)一個block並傳給介面2(的block屬性)
2、在block中處理傳遞過來的值 - 介面2(傳值方):
1、宣告一個block屬性,其具體實現由接收方來完成
2、在需要傳值的地方呼叫block
介面1 .m檔案:
// 跳轉到介面2
- (IBAction)btnShowViewController2:(UIButton *)sender {
// 從storyboard例項化介面2
ViewController2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewController2"];
// 定義(實現)一個block並傳給介面2(的block屬性)
vc2.textBlock = ^(NSString *str) {
self.label.text = str; // 處理傳回來的值
};
// 載入介面2
[self presentViewController:vc2 animated:YES completion:nil];
}
介面2 .h檔案
@interface ViewController2 : UIViewController
// 宣告block屬性
@property (nonatomic, copy) void(^textBlock)(NSString *);
@end
介面2 .m檔案:
// 返回介面1
- (IBAction)btnBack:(UIButton *)sender {
// 先判斷block是否為空,為空則報錯
if (self.textBlock) {
// 返回textField裡的text
self.textBlock(self.textField.text);
}
// 退出介面2
[self dismissViewControllerAnimated:YES completion:nil];
}