我得到了一个 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 的组合是一个糟糕的组合。这些问题在这里适用吗?
更根本的是,我的委托适配器的整体方法是正确的方法吗?如果没有,那就完全改变了我的问题。
我不知道这是否回答了您的问题,但是将基于委托的 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 并发延续中。