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

