如何从具有返回值且无法使用完成处理程序的同步方法中调用 Swift 异步方法?

问题描述 投票:0回答:1

我得到了一个 Objective-C 框架,它具有以下 API(针对这个问题进行了简化):

@protocol SomeClassDelegate;

NS_REFINED_FOR_SWIFT
@interface SomeClass

@property (weak) id<SomeClassDelegate> delegate;

initWithDelegate:(id<SomeClassDelegate>)delegate NS_SWIFT_NAME(init(delegate:));

@end

NS_REFINED_FOR_SWIFT
@protocol SomeClassDelegate

- (BOOL)someClass:(SomeClass *)object shouldProcessData:(UUID *)recordID NS_SWIFT_NAME(someClass(_:shouldProcessData:));
- (nullable SomeHelper *)someClass:(SomeClass *)object helperFor:(UUID *)recordID NS_SWIFT_NAME(someClass(_:helerFor:));

@end

此 API 的 Swift 版本被指定为:

public class SomeClass {
    public weak var delegate: SomeClassDelegate

    init(delegate: SomeClassDelegate)
}

public protocol SomeClassDelegate : AnyObject {
    func shouldProcessData(recordID: UUID, someClass: SomeClass) async -> Bool
    func helperFor(recordID: UUID, someClass: SomeClass) async -> SomeHelper?
}

鉴于这些 API,我需要实现现有 Objective-C 框架的 Swift 包装器。我最大的障碍是 Swift 委托方法应该是

async
但 Objective-C 委托方法不是异步的并且没有完成处理程序。

这是我第一次使用 Swift 包装器,其中包括一个委托适配器。

public class SomeClass {
    public weak var delegate: SomeClassDelegate

    init(delegate: SomeClassDelegate) {
        wrapper = __SomeClass()
        delegateAdapter = SomeClassDelegateAdapter(self, delegate: delegate)
        wrapper.delegate = delegateAdapter
    }

    private wrapper: __SomeClass
    private delegateAdapter: SomeClassDelegateAdapter
}

fileprivate class SomeClassDelegateAdapter: NSObject, __SomeClassDelegate {
    private let delegate: SomeClassDelegate
    private let wrapper: SomeClass

    init(_ wrapper: SomeClass, delegate: SomeClassDelegate) {
        self.wrapper = wrapper
        self.delegate = delegate
    }

    func someClass(_ someClass: __SomeClass, shouldProcessData recordID: UUID) -> Bool {
        // The following gives "'async' call in a function that does not support concurrency"
        //return delegate.shouldProcessData(recordID, someClass: wrapper)
        // Adding await doesn't change anything
        //return await delegate.shouldProcessData(recordID, someClass: wrapper)
        // Wrapping in Task gives "Cannot convert return expression of type 'Task<Bool, Never>' to return type 'Bool'"
        Task {
            return await delegate.shouldProcessData(recordID, someClass: wrapper)
        }
        // Adding .value to the Task gets me back to the first error. Ugh!
    }

    func someClass(_ someClass: __SomeClass, helperFor recordID: UUID) -> __SomeHelper? {
        // Similar issues to above
    }
}

那么我该如何解决这个问题呢?我已经进行了大量搜索,所有解决方案都涉及使用完成处理程序。但我不能这样做,因为 Objective-C API 是固定且完整的。我不知道为什么 Swift 版本的 delegate API 会标有

async
。那是我无法控制的。我需要一个解决方案,允许我围绕给定的 Objective-C API 实现给定的 Swift API。

是否有一种有效的方法可以从 Swift

async
方法获取返回值并从非异步方法内部返回它?根据我的搜索,使用 GCD 或信号量与 Swift async/await 的组合是一个糟糕的组合。这些问题在这里适用吗?

更根本的是,我的委托适配器的整体方法是正确的方法吗?如果没有,那就完全改变了我的问题。

swift objective-c async-await adapter synchronous
1个回答
0
投票

我不知道这是否回答了您的问题,但是将基于委托的 API 包装在

async
方法中的总体思路是编写一个委托对象,该对象可由
withCheckedContinuation
withCheckedThrowingContinuation
提供的延续使用。

例如,考虑这个假设的 Objective-C 接口,您在其中调用

startWithInput:
来启动一些异步进程,并在完成时调用
didFinishWithValue:error:

//  Foo.h

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol FooDelegate
- (void)didFinishWithValue:(nullable NSString *)value error:(nullable NSError *)error;
@end

@interface Foo: NSObject
@property (nonatomic, weak, nullable) id<FooDelegate> delegate;
- (void)startWithInput:(NSString *)input;
@end

NS_ASSUME_NONNULL_END

要在 Swift 中使用它,您可以首先编写一个 Swift

FooDelegate
对象,该对象在调用委托方法时调用闭包:

class FooDelegateWithClosure: NSObject {
    var completion: (String?, Error?) -> Void

    init(completion: @escaping (String?, Error?) -> Void) {
        self.completion = completion
        super.init()
    }
}

extension FooDelegateWithClosure: FooDelegate {
    func didFinish(withValue value: String?, error: Error?) {
        completion(value, error)
    }
}

然后我可以使用该委托对象

withCheckedThrowingContinuation
:

func string(for input: String) async throws -> String {
    let foo = Foo()
    var delegate: FooDelegate?

    return try await withCheckedThrowingContinuation { continuation in
        delegate = FooDelegateWithClosure { value, error in
            guard let value else {
                continuation.resume(throwing: error!)
                return
            }
            continuation.resume(returning: value)
        }
        foo.delegate = delegate
        foo.start(withInput: input)
    }
}

然后您可以像任何其他 Swift 并发方法一样

await
async
方法。


就我个人而言,如果我可以访问 Objective-C 代码,我会编写一个采用完成处理程序块参数的方法的再现,将该完成块保存在由

UUID
键控的某个字典中,然后让完成例程查看是否存在该
UUID
的块,如果找到则调用它,否则调用委托方法。

这将简化

withChecked[Throwing]Continuation
代码,消除对委托对象的需要。但想法是相同的:将 Objective-C 接口包装在 Swift 并发延续中。

© www.soinside.com 2019 - 2024. All rights reserved.