最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - How to make overridden methods run on @MainActor? - Stack Overflow

programmeradmin1浏览0评论

I am trying to refactor my existing implementation for an app so it conforms to Swift 6. To do this I have to touch the following class that provides an "open url in safari" share extension:

import Foundation
import UIKit

public final class SafariActivity: UIActivity {

    var url: URL?
    
    // MARK: - UIActivity Implementation
    
    public override var activityType: UIActivity.ActivityType {
        .openInSafari
    }
    
    public override var activityTitle: String? {
        "Open in Safari"
    }
    
    public override var activityImage: UIImage? {
        UIImage(systemName: "safari")?.applyingSymbolConfiguration(.init(scale: .large))
    }
    
    public override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
        // ERROR: Main actor-isolated class property 'shared' can not be referenced from a nonisolated context
        activityItems.contains { $0 is URL ? UIApplication.shared.canOpenURL($0 as! URL) : false }
    }
    
    public override func prepare(withActivityItems activityItems: [Any]) {
        // ERROR: Main actor-isolated class property 'shared' can not be referenced from a nonisolated context
        url = activityItems.first { $0 is URL ? UIApplication.shared.canOpenURL($0 as! URL) : false } as? URL
    }
    
    public override func perform() {
        if let url = url {
            // ERROR: Call to main actor-isolated instance method 'open(_:options:completionHandler:)' in a synchronous nonisolated context
            UIApplication.shared.open(url)
        }
        self.activityDidFinish(true)
    }
}

extension UIActivity.ActivityType {
    static let openInSafari = UIActivity.ActivityType(rawValue: "openInSafari")
}

extension UIActivity {
    @MainActor public static let openInSafari = SafariActivity()
}

I struggle to understand the correct way to restrict methods that are overrides to the main thread. How can I make the methods I have overridden from UIActivity isolate to @MainActor?

I am trying to refactor my existing implementation for an app so it conforms to Swift 6. To do this I have to touch the following class that provides an "open url in safari" share extension:

import Foundation
import UIKit

public final class SafariActivity: UIActivity {

    var url: URL?
    
    // MARK: - UIActivity Implementation
    
    public override var activityType: UIActivity.ActivityType {
        .openInSafari
    }
    
    public override var activityTitle: String? {
        "Open in Safari"
    }
    
    public override var activityImage: UIImage? {
        UIImage(systemName: "safari")?.applyingSymbolConfiguration(.init(scale: .large))
    }
    
    public override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
        // ERROR: Main actor-isolated class property 'shared' can not be referenced from a nonisolated context
        activityItems.contains { $0 is URL ? UIApplication.shared.canOpenURL($0 as! URL) : false }
    }
    
    public override func prepare(withActivityItems activityItems: [Any]) {
        // ERROR: Main actor-isolated class property 'shared' can not be referenced from a nonisolated context
        url = activityItems.first { $0 is URL ? UIApplication.shared.canOpenURL($0 as! URL) : false } as? URL
    }
    
    public override func perform() {
        if let url = url {
            // ERROR: Call to main actor-isolated instance method 'open(_:options:completionHandler:)' in a synchronous nonisolated context
            UIApplication.shared.open(url)
        }
        self.activityDidFinish(true)
    }
}

extension UIActivity.ActivityType {
    static let openInSafari = UIActivity.ActivityType(rawValue: "openInSafari")
}

extension UIActivity {
    @MainActor public static let openInSafari = SafariActivity()
}

I struggle to understand the correct way to restrict methods that are overrides to the main thread. How can I make the methods I have overridden from UIActivity isolate to @MainActor?

Share Improve this question asked Feb 7 at 14:35 dual0jdual0j 253 bronze badges 1
  • I suggest to put that code in separate module and compile it with swift 5, because obviously UIActivity is not ready yet for swift 6 compatibility. – Cy-4AH Commented Feb 7 at 15:42
Add a comment  | 

1 Answer 1

Reset to default 1

The main problem here is that UIApplication.shared is main actor-isolated. canOpenURL is non-isolated, so there's no problem there.

If you just grab UIApplication.shared when you know you are in a main actor-isolated context, you can store it in a property and use it.

For example:

var url: URL?
let sharedApp: UIApplication

override init() {
    // make sure to always call SafariActivity.init in a main actor isolated context!
    sharedApp = MainActor.assumeIsolated { UIApplication.shared }
}

Unfortunately we cannot mark init as @MainActor because it would override NSObject.init, which is non-isolated. If this is directly inheriting from NSObject, then Swift can know this is safe, but we are inheriting UIActivity, so Swift can't tell.

You can also add an extra parameter to take in the UIApplication, so the init doesn't override NSObject.init,

@MainActor
init(_ app: UIApplication) {
    sharedApp = app
}
// usage:
SafariActivity(.shared)

Then, you can just replace all the UIApplication.shared with sharedApp.

Finally, despite not being marked @MainActor, perform is actually documented to only be called on the main thread, so it is safe to just use MainActor.assumeIsolated.

public override func perform() {
    if let url = url {
        MainActor.assumeIsolated {
            UIApplication.shared.open(url)
        }
    }
    self.activityDidFinish(true)
}
发布评论

评论列表(0)

  1. 暂无评论