首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

Objective-C Block 完整指南:定義、使用、記憶體管理、循環引用解決

     編輯:關於C

Block:Objective-C 的閉包實作,捕獲上下文的匿名函數

Block 是 iOS/macOS 開發核心技術,廣泛用於枚舉、動畫、排序、網路回調、多執行緒。本文系統整理語法、捕獲語意、記憶體管理與最佳實務。

一、Block 語法基礎

完整宣告語法

返回類型 (^Block名稱)(參數列表) = ^返回類型(參數列表) { 實作代碼 }; 

常見形式

// 無參無回傳
void (^simpleBlock)(void) = ^{ NSLog(@"Hello"); };

// 有參有回傳
int (^addBlock)(int, int) = ^int(int a, int b) { return a + b; };

// 簡寫:無參時 ^ 後可省略 ()
void (^block)() = ^{ ... };

// 呼叫
simpleBlock();
int sum = addBlock(3, 5); // 8

二、Block 作為參數傳遞 (最常見用法)

NSArray *array = @[@"張三", @"李四", @"王五", @"趙六"];

// enumerateObjectsUsingBlock:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    NSLog(@"第 %lu 項: %@", idx, obj);
    if ([@"王五" isEqualToString:obj]) {
        *stop = YES; // 提前結束枚舉
    }
}];

三、變數捕獲規則

變數類型捕獲方式可否修改說明
區域變數 (自動)值捕獲 (複製)預設唯讀Block 建立時複製當前值
__block 區域變數引用捕獲可修改共享同一記憶體位置
全域/靜態變數引用捕獲可修改直接存取原位址
物件指標強引用 (retain)可修改指向Block 持有物件
self (隱含)強引用-易造成循環引用

__block 修飾符範例

__block int counter = 0;
__block BOOL found = NO;

[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    counter++; // 可修改
    if ([@"目標" isEqualToString:obj]) {
        found = YES;
        *stop = YES;
    }
}];
NSLog(@"總檢查 %d 項,找到: %@", counter, found ? @"是" : @"否");

四、typedef 簡化 Block 類型宣告

typedef double (^MathBlock)(double, double);

MathBlock area = ^(double w, double h) { return w * h; };
MathBlock sum  = ^(double a, double b) { return a + b; };

NSLog(@"面積: %.2f", area(10, 20)); // 200.00
NSLog(@"加總: %.2f", sum(10, 20));   // 30.00

五、Block 加入集合 (陣列/字典)

@property (nonatomic, strong) NSMutableArray *blocks;

- (void)setupBlocks {
    int (^add)(int, int) = ^(int x, int y) { return x + y; };
    [self.blocks addObject:add]; // Block 自動 copy 到堆上
    
    int (^mul)(int, int) = ^(int x, int y) { return x * y; };
    [self.blocks addObject:mul];
}

- (int)executeBlockAtIndex:(NSUInteger)idx withX:(int)x y:(int)y {
    int (^block)(int, int) = self.blocks[idx];
    return block(x, y);
}

六、循環引用問題與解決

問題場景

// self 持有 blocks 陣列
// block 內呼叫 self.method -> block 持有 self
// 形成:self → blocks → block → self (強引用環)
@property (nonatomic, strong) NSMutableArray *blocks;

- (void)badExample {
    int (^block)(int, int) = ^(int x, int y) {
        return [self compute:x y:y]; // 捕獲 self (強引用)
    };
    [self.blocks addObject:block];
    // self 永不釋放!
}

解決方案:__weak 弱引用

- (void)goodExample {
    __weak typeof(self) weakSelf = self; // 或 __weak SelfType *weakSelf = self;
    
    int (^block)(int, int) = ^(int x, int y) {
        // 使用 weakSelf,Block 不持有 self
        return [weakSelf compute:x y:y];
    };
    [self.blocks addObject:block];
    
    // 進階:在 block 內轉強引用確保執行期間不被釋放
    int (^safeBlock)(int, int) = ^(int x, int y) {
        __strong typeof(self) strongSelf = weakSelf;
        if (strongSelf) {
            return [strongSelf compute:x y:y];
        }
        return 0; // self 已釋放
    };
}

七、Block 在 iOS 開發中的核心應用場景

場景API 範例用途
集合枚舉enumerateObjectsUsingBlock:替代 for 迴圈,支援提前停止
視圖動畫animateWithDuration:animations:聲明式動畫配置
排序sortedArrayUsingComparator:自訂比較邏輯
通知中心addObserverForName:object:queue:usingBlock:區域性通知處理
錯誤處理completionHandler (NSError *)非同步結果回傳
多執行緒dispatch_async(queue, ^{...})GCD 任務提交
網路請求dataTaskWithRequest:completionHandler:非同步回調

八、記憶體管理核心規則

  1. Block 建立在棧上:離開作用域即銷毀
  2. copy 移至堆上:[block copy] 或屬性 copy 修飾
  3. Block 捕獲物件 → 強引用:需警惕循環引用
  4. __block 變數 → 堆上共享結構:多個 Block 可共享修改
  5. ARC 自動處理 copy:傳遞給 API 時自動 copy,但手動存屬性需宣告 copy

九、除錯技巧

  • Instruments → Leaks:檢測 Block 循環引用洩漏
  • Debug Memory Graph:Xcode 8+ 視覺化引用關係
  • 靜態分析:Product → Analyze 偵測潛在循環引用
  • Block 斷點:在 Block 內打斷點檢查捕獲變數值
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved