下面的Swift UIKit代码中的第authController.signInAnonymouslyIfNecessary()行(接近结尾)产生警告:"Cannot access property 'authController'with a non—sendable type 'AuthController'from non—isolated deinit;this is an error in Swift 6:

import UIKit

class AuthController {
    func signInAnonymouslyIfNecessary() {
        
    }
}

class AuthFormNavC: UINavigationController {
    let authController: AuthController

    init(authController: AuthController) {
        self.authController = authController
        super.init(rootViewController: ConsentVC())
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        authController.signInAnonymouslyIfNecessary()
    }
}

Swift 5.10,Xcode 15.3,具有完全严格的并发判断.

解决办法是什么?

请不要问我为什么要做我正在做的事情或任何与这个问题无关的事情.

如果你想知道为什么我想在导航控制器被非初始化时调用authController.signInAnonymouslyIfNecessary(),我的目标是在导航控制器被解除(或弹出)时调用它,我认为视图控制器的go 初始化器是唯一一个在我的例子中当且仅当视图控制器被解除(或弹出)时调用的方法.我曾经try 使用KVO观察像isViewLoaded这样的变量,但是我无法让它在observe(_:options:changeHandler:)中传递任意选项组合.

推荐答案

首先,从答案开始,然后我将解释为什么这里有一个真正的问题,然后我将努力解决为什么这个真正的问题,而不仅仅是一种"让编译器高兴"的方法."

答案是:

  • 将授权控制器标记为@MainActor
  • signInAnonymouslyIfNecessary分到nonisolated
  • signInAnonymouslyIfNecessary个任务:
@MainActor final class AuthController {
    nonisolated func signInAnonymouslyIfNecessary() {
        Task { @MainActor in
            /// ...
        }
    }
}

这是为什么

UINavigationController(和扩展AuthFormNavC)是@MainActor.但是,不能保证deinit会在MainActor上被调用.(事实证明,through a great deal of work from the UIKit team,非常有可能在MainActor上调用UI View和UI ViewController,但这没有明确promise ,并且通常不是真的.

这意味着警告是正确的.在任意线程上访问self.authController无效.这是一个长期存在的真实原因,当试图删除deinit中的KVO观察时,这就是为什么我们需要像PMKVObserver这样的包.

从创建authController的上下文(即MainActor)以外的上下文调用authController.signInAnonymouslyIfNecessary()也是无效的.AuthController不是Scrum(并且可能不是线程安全的).

我说所有这些都是为了指出,这个代码实际上从来都不是合法的,在一般情况下(在UIKit的魔力之外),它是现实世界崩溃的一个长期原因,Swift Concurrency在这里帮助消除.

但我们该怎么办?

首先,很明显,AuthController打算成为MainActor,所以应该这样标记:

@MainActor class AuthController {

第二,虽然在deinit中延长self的生命周期 是非法的,但延长self财产的生命周期 肯定不是非法的.这里有一个直接的方法来做到这一点(尽管有一个更安全的方法,我将得到下一步).

    // This is for illustration, see below for a safer solution
    deinit {
        // Do not overlook the `[authController]` here!
        Task { @MainActor [authController] in
            authController.signInAnonymouslyIfNecessary()
        }
    }

我必须强调逮捕名单的重要性.这使得authController成为Task内部的本地复制变量,避免捕获self.如果不包含这个捕获将崩溃,编译器不会对此发出警告.列表中已经讨论过几次了,至少有一个关于它的FB.我也打开了一个bug report.好消息是,捕获deinit中的self将可靠地崩溃,而不仅仅是未定义的行为.Swift checks the retain countdeinit的末尾以确保self没有逃脱.

因此,将事情打包在一个任务中解决了问题,但它也带来了新的头痛.我们必须非常小心我们的捕获名单.deinit个代码通常没有得到很好的测试,最小的错误都可能导致程序崩溃.如果我们能说"deinit个任务中没有任务,它们太危险了."我们可以.

保持AuthController为@MainActor,我们可以说从任何使用nonisolated的线程调用signInAnonymouslyIfNecessary是安全的:

@MainActor final class AuthController {
    nonisolated func signInAnonymouslyIfNecessary() {
        Task { @MainActor in
            /// This block will likely capture `self`. That's good.
            /// There is no reason for a `weak` dance here; there's no cycle.
            /// We want `self` to live long enough to finish the job.
            /// ...
        }
    }
}

有了这个,你的deinit是好的.即使您不控制AuthController,您也可以扩展它以提供一个调用Task中实际方法的nonisolated方法.

作为一项规则,应该避免deinit,特别是对于任何重要的事情.它总是有许多棘手的角落情况,一直是挑战性的使用可靠,even in the ObjC days with -dealloc.Swift并发只是expose 了许多长期存在的问题.正如@ Sweeper所指出的,这个特殊的情况可能应该在viewDidDisappear中完成,这可能是你真正想要的行为,或者是响应模型的改变,而不是依赖于视图控制器的内存管理的副作用.但这也是一个重要的问题,一般情况值得探讨.

下面是一个简化版本的代码,可以用来玩,看看什么有效和不有效:

@MainActor class Service {
    func doThings() {}
}

@MainActor class Controller {
    let service: Service

    init(service: Service) { self.service = service }

    func printMe() { print("Controller lives") }

    deinit {
        print("Controller deinit")

        // As written, this crashes. Adding [service] here will fix it.
        Task { @MainActor in
            service.doThings()
        }
    }
}

let service = Service()

do {
    let controller = Controller(service: service)
    controller.printMe()
}

Swift相关问答推荐

动画过程中的SwiftUI重绘视图

向文本元素添加背景色失败

RealityKit(VisionOS)中的PhysicBodyComponent和DragGesture

SWIFT闭包使用的是陈旧的值,即使它是S@转义

按SWIFT中每个ID的最大值进行筛选/排序

在MacOS上检测键盘是否有Globe键

带DispatchSourceTimer的定时器

在这个使用 URLSession 的简单 case 中是否创建了一个强引用循环?

如何获得不同的插入和移除过渡动画?

Xcode:方法参数的代码完成

如何以快速 Select 器菜单样式调整图像大小

在 Swift 4 中实现自定义解码器

理解 Swift 2.2 Select 器语法 - #selector()

仅在 Swift 中创建 Setter

在 Swift 中强制崩溃的最简单方法

你如何在 UIBarItem 中使用 setTitleTextAttributes:forState?

将强制向下转换视为可选将永远不会产生零

Swift - 迭代 struct 对象时如何对其进行变异

URLComponents.url 为零

Swift 2.0 最低系统版本要求(部署目标)