首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

iOS 異步 Block 中返回值的正確模式:回調而非直接返回

     編輯:程式設計綜合問答

問題本質

原代碼嘗試在同步方法中返回異步 Block 執行結果:

-(NSInteger)globalRecord {
    __block NSInteger globalRecord = 0;
    [GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards, NSError *error) {
        // ... 異步嵌套 ...
        [globalTaps loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
            if ([scores count] != 0) {
                globalRecord = topScore.value; // 42
            }
        }];
    }];
    return globalRecord; // 此時異步未完成,返回 0
}

**核心誤區**:異步操作 (網絡請求、Block 回調) 在當前線程**不會阻塞等待**,方法會立即返回,此時回調尚未執行。

正確模式:回調模式 (Completion Handler)

將返回值改為參數傳遞給回調 Block:

// 1. 方法簽名改為 void,增加 completion 參數
- (void)fetchGlobalRecordWithCompletion:(void (^)(NSInteger record))completion {
    [GKLeaderboard loadLeaderboardsWithCompletionHandler:^(NSArray *leaderboards, NSError *error) {
        GKLeaderboard *globalTaps = nil;
        for (GKLeaderboard *lb in leaderboards) {
            if ([lb.category isEqualToString:kGlobalLeaderboard]) {
                globalTaps = lb;
                break;
            }
        }
        if (globalTaps) {
            [globalTaps loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
                NSInteger record = 0;
                if (scores.count > 0) {
                    GKScore *topScore = scores[0];
                    record = topScore.value;
                }
                // 2. 在主線程回調結果 (UI 更新通常需主線程)
                if (completion) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        completion(record);
                    });
                }
            }];
        } else {
            if (completion) completion(0);
        }
    }];
}

// 3. 調用示例
[self fetchGlobalRecordWithCompletion:^(NSInteger record) {
    NSLog(@"最高分: %ld", record); // 42
    // 更新 UI、存儲等後續邏輯
}];

關鍵點解析

  • 方法不再返回值:改為 void,結果通過 Block 參數傳遞。
  • 層層透傳回調:每一層異步調用都接收 completion,最終在最內層調用。
  • 主線程回調:網絡回調通常在後台線程,UI 更新需 dispatch_async(dispatch_get_main_queue(), ...)。
  • 錯誤處理:建議回調簽名包含錯誤信息 (NSInteger record, NSError *error)。

現代化寫法:Promise / Future (第三方庫)

使用 PromiseKit、RxSwift 或 Swift async/await (iOS 13+) 可將回調地獄扁平化:

// PromiseKit 示例
- (Promise<NSInteger>)fetchGlobalRecord {
    return [GKLeaderboard loadLeaderboards].then(^(NSArray *leaderboards) {
        GKLeaderboard *global = [leaderboards firstObjectMatching:^(GKLeaderboard *lb) {
            return [lb.category isEqualToString:kGlobalLeaderboard];
        }];
        return [global loadScores];
    }).then(^(NSArray *scores) {
        return @(scores.firstObject.value);
    });
}

// 調用
[self fetchGlobalRecord].done(^(NSInteger record) {
    NSLog(@"%ld", record);
}).catch(^(NSError *error) {
    // 錯誤處理
});

Swift async/await (iOS 13+, 推薦新項目使用)

func fetchGlobalRecord() async throws -> Int {
    let leaderboards = try await GKLeaderboard.loadLeaderboards()
    guard let global = leaderboards.first(where: { $0.category == kGlobalLeaderboard }) else { return 0 }
    let scores = try await global.loadScores()
    return scores.first?.value ?? 0
}

// 調用
Task {
    do {
        let record = try await fetchGlobalRecord()
        print("最高分: \(record)")
    } catch {
        print("錯誤: \(error)")
    }
}

總結

**異步操作無法通過返回值獲取結果**。必須採用**回調 Block、Delegate、Promise、async/await** 等異步編程模式。Objective-C 時代標準做法是 **Completion Handler 回調**,並注意線程切換與錯誤傳遞。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved