You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何确保API数据获取完成后再执行依赖该数据的方法?

Hey Leo, this is a super common pain point when dealing with async network calls and persistent storage—let’s walk through a few reliable solutions to make sure your second method only runs once the data’s safely saved to UserDefaults.


1. Use a Completion Handler (Most Straightforward for Simple Flows)

Wrap your API fetch + UserDefaults save logic in a function with a completion closure. This lets you explicitly trigger your second method only after the data is successfully stored.

func fetchDataAndSaveToUserDefaults(completion: @escaping (Bool) -> Void) {
    // Replace with your actual API URL
    guard let apiURL = URL(string: "https://your-api-endpoint.com/data") else {
        completion(false)
        return
    }

    URLSession.shared.dataTask(with: apiURL) { data, response, error in
        guard let data = data, error == nil else {
            print("API request failed: \(error?.localizedDescription ?? "Unknown error")")
            completion(false)
            return
        }

        // Parse and save your data (example with a custom model)
        do {
            let parsedData = try JSONDecoder().decode(YourCustomModel.self, from: data)
            let encodedData = try JSONEncoder().encode(parsedData)
            
            UserDefaults.standard.set(encodedData, forKey: "SavedAPIData")
            // Synchronize is optional now (iOS handles auto-sync), but can help for immediate reads
            UserDefaults.standard.synchronize()
            
            completion(true)
        } catch {
            print("Parsing/saving failed: \(error.localizedDescription)")
            completion(false)
        }
    }.resume()
}

// How to use it
fetchDataAndSaveToUserDefaults { success in
    if success {
        // Data is ready—run your second method
        self.executeSecondMethod()
    } else {
        // Handle failure: show alert, retry, etc.
        self.showError(message: "Failed to load data")
    }
}

2. Use NotificationCenter (Great for Cross-Component Communication)

If your second method lives in a different part of your app (e.g., another view controller), send a notification once the data is saved. The target component can listen for this notification and run the method when it arrives.

// Step 1: In your API fetch function, post a notification after saving
func fetchAndSaveData() {
    // ... same API/save logic as above ...
    if dataSavedSuccessfully {
        // Always dispatch to main thread for UI-related actions
        DispatchQueue.main.async {
            NotificationCenter.default.post(
                name: NSNotification.Name(rawValue: "APIDataSavedToUserDefaults"),
                object: nil
            )
        }
    }
}

// Step 2: In the component with your second method, listen for the notification
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(executeSecondMethod),
        name: NSNotification.Name(rawValue: "APIDataSavedToUserDefaults"),
        object: nil
    )
}

@objc func executeSecondMethod() {
    // Read from UserDefaults and process
    if let savedData = UserDefaults.standard.data(forKey: "SavedAPIData") {
        let parsedData = try? JSONDecoder().decode(YourCustomModel.self, from: savedData)
        // Use the data here
    }
}

// Step 3: Don't forget to clean up the observer
deinit {
    NotificationCenter.default.removeObserver(self)
}

3. Combine Framework (iOS 13+ for Reactive Flows)

If your app uses Combine, you can create a publisher that emits a signal when the data is saved. This is clean for reactive architectures and integrates well with SwiftUI or UIKit.

import Combine

func fetchAndSaveData() -> AnyPublisher<Void, Error> {
    guard let apiURL = URL(string: "https://your-api-endpoint.com/data") else {
        return Fail(error: NSError(domain: "Invalid URL", code: 0, userInfo: nil)).eraseToAnyPublisher()
    }

    return URLSession.shared.dataTaskPublisher(for: apiURL)
        .map(\.data)
        .decode(type: YourCustomModel.self, decoder: JSONDecoder())
        .tryMap { model in
            let encoded = try JSONEncoder().encode(model)
            UserDefaults.standard.set(encoded, forKey: "SavedAPIData")
            return ()
        }
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
}

// Subscribe to the publisher
var cancellables = Set<AnyCancellable>()

fetchAndSaveData()
    .sink(
        receiveCompletion: { completion in
            if case .failure(let error) = completion {
                print("Error: \(error)")
            }
        },
        receiveValue: { _ in
            // Data is saved—run your method
            self.executeSecondMethod()
        }
    )
    .store(in: &cancellables)

4. State Management (For Complex Apps)

For larger apps, use a view model or state container to track the data's status. Use @Published (for ObservableObject) to let your views/controllers observe when the data is ready.

class DataViewModel: ObservableObject {
    @Published var savedData: YourCustomModel?
    private var cancellables = Set<AnyCancellable>()

    func fetchAndSave() {
        guard let apiURL = URL(string: "https://your-api-endpoint.com/data") else { return }

        URLSession.shared.dataTaskPublisher(for: apiURL)
            .map(\.data)
            .decode(type: YourCustomModel.self, decoder: JSONDecoder())
            .sink(
                receiveCompletion: { completion in
                    if case .failure(let error) = completion {
                        print("Fetch failed: \(error)")
                    }
                },
                receiveValue: { [weak self] model in
                    guard let self = self else { return }
                    do {
                        let encoded = try JSONEncoder().encode(model)
                        UserDefaults.standard.set(encoded, forKey: "SavedAPIData")
                        DispatchQueue.main.async {
                            self.savedData = model
                        }
                    } catch {
                        print("Save failed: \(error)")
                    }
                }
            )
            .store(in: &self.cancellables)
    }
}

// In your view controller
let viewModel = DataViewModel()

override func viewDidLoad() {
    super.viewDidLoad()
    viewModel.$savedData
        .compactMap { $0 } // Ignore nil values
        .sink { [weak self] data in
            self?.executeSecondMethod(with: data)
        }
        .store(in: &cancellables)

    // Trigger the fetch
    viewModel.fetchAndSave()
}

Quick Notes

  • Avoid overusing UserDefaults.standard.synchronize()—iOS automatically syncs changes, but it can help in edge cases where you need immediate access.
  • Always handle failure paths (API errors, parsing errors) to avoid silent failures.
  • If multiple parts of your app need this data, consider caching it in memory (e.g., in a singleton or view model) to avoid redundant API calls.

内容的提问来源于stack exchange,提问作者Leo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 20:39:04