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

Swift中Sendable、nonisolated与Actor的逻辑流理解是否准确?

你的解释准确性分析及逻辑补全

你的解释核心方向是正确的,但存在几处细节偏差,以下是完整的逻辑流程梳理:

1. TripsFeedViewModel的Actor绑定

@Observable修饰的类默认隐式运行在MainActor,所有属性访问和方法调用都会被调度到主线程执行,天然避免了UI相关的数据竞争。调用tripService.getTrips()时,由于TripService是独立Actor,当前MainActor的任务会挂起,等待TripService的执行队列空闲后再执行其方法,而非直接移交后台线程。

2. TripService的任务执行逻辑

进入TripService的执行队列后,networkService作为该Actor的属性,默认其方法会在TripService的Actor上下文中执行。如果sendRequest未标记nonisolated,整个网络请求和数据解码过程都会占用TripService的Actor锁,导致后续对TripService的调用被阻塞。

标记sendRequest为nonisolated后,该方法会脱离TripService的Actor上下文,直接在系统调度的后台线程执行,不会占用TripService的队列锁,从而提升并发性能。执行完成后,结果会自动传递回TripService的队列,继续后续逻辑。

3. NetworkRequestService的Sendable要求

NetworkRequestService标记为Sendable是因为它作为Actor的属性,可能会被跨Actor传递。由于该类是final且没有可变状态(所有属性都是初始化时赋值,后续无修改),完全符合Sendable的安全要求,编译器可以确认其跨线程传递不会引发数据竞争。

4. TripPrivateResponse的Sendable与解码

TripPrivateResponse是值类型结构体,且所有存储属性都是Sendable合规类型(Int、String等),因此默认自动符合Sendable,无需显式声明(显式声明是可选的,用于明确告知编译器)。

另外,结构体不存在“默认运行在MainActor”的说法——值类型没有Actor绑定,只有引用类型(类、Actor)才有Actor上下文。标记nonisolated在这里没有实际意义(结构体不能被隔离到Actor),解码操作会在sendRequest所在的后台线程执行,由于TripPrivateResponse是Sendable,解码后的结果可以安全传递回TripService和MainActor。


完整代码示例

TripsFeedViewModel

import Foundation

@Observable
class TripsFeedViewModel {
    var trips: [Trip] = []
    
    private let tripService: TripServiceProtocol
    
    init(tripService: TripServiceProtocol) {
        self.tripService = tripService
    }
    
    func getTrip() async -> Void {
        do {
            let trips = try await tripService.getTrips()
            await MainActor.run {
                self.trips = trips.compactMap {
                    Trip(
                        id: $0.id,
                        tripName: $0.tripName,
                        location: $0.location,
                        budget: $0.budget,
                        isFavorite: $0.isFavorite,
                        startDate: $0.startDate,
                        endDate: $0.endDate,
                        imageURLString: $0.imageURL
                    )
                }
            }
        } catch {
            print("There was an error get your trips: \(error.localizedDescription)")
        }
    }
}

TripService

import Foundation

actor TripService: TripServiceProtocol {
    private let networkService: NetworkRequestService
    private let keychainService: KeychainService
    private var activeTask: Task<[TripPrivateResponse], Error>?

    init(networkService: NetworkRequestService, keychainService: KeychainService) {
        self.networkService = networkService
        self.keychainService = keychainService
    }
    
    func getTrips() async throws -> [TripPrivateResponse] {
        if let existing = activeTask {
            return try await existing.value
        }
        
        let task = Task<[TripPrivateResponse], Error> {
            guard let url = URL(string: "local host url") else {
                throw APIError.invalidURL
            }
            
            var request = URLRequest(url: url)
            request.httpMethod = "GET"
            
            if let token = keychainService.getToken() {
                request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
            }
            
            return try await networkService.sendRequest(request: request, responseType: [TripPrivateResponse].self)
        }
        
        activeTask = task
        
        defer { activeTask = nil }
        
        return try await task.value
    }
}

TripPrivateResponse

struct TripPrivateResponse: Codable, Sendable {
    let id: Int
    let tripName: String
    let location: String
    let budget: Int
    let isFavorite: Bool
    let startDateString: String
    let endDateString: String
    let imageURLString: String
    
    enum CodingKeys: String, CodingKey {
            case id
            case tripName = "title"
            case location
            case budget
            case isFavorite = "is_favorite"
            case startDateString = "start_date"
            case endDateString = "end_date"
            case imageURLString = "cover_image_url"
        }
}

NetworkRequestService

final class NetworkRequestService: Sendable {
    // MARK: Sends the request and returns the response from FastAPI
    nonisolated func sendRequest<Output: Decodable>(request: URLRequest, responseType: Output.Type) async throws -> Output {
        do {
            /// 1. this sends the data to FastAPI then waits for a response
            let (data, response) = try await URLSession.shared.data(for: request)
            
            print("FASTAPI RESPONSE: \(String(data: data, encoding: .utf8) ?? "No Data")")
            
            /// 2. checks the response (convert it to HTTPURLResponse type) making sure it has a successful status code
            guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
                throw APIError.invalidResponse
            }
            
            /// 3. decode it to the DTO (UserPrivateResponse)
            return try JSONDecoder().decode(responseType, from: data)
        } catch let error as URLError {
            throw APIError.networkError(error)
        } catch let error as DecodingError {
            throw APIError.decoding(error)
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 19:34:51