与Objective-C代码的互操作
在许多iOS项目中,Swift和Objective-C代码共存,尤其是在遗留项目中。Swift的async/await带来了现代并发编程的便利,但Objective-C主要依赖闭包和GCD的传统异步模型,这要求开发者在混合代码中实现无缝互操作。前一节探讨了GCD到async/await的迁移,本节将深入分析Swift与Objective-C在并发上的集成,介绍桥接方法、常见问题及最佳实践,确保你在混合环境中高效工作。
互操作的挑战
Objective-C和Swift在并发模型上的差异带来以下挑战:
- 语法差异:Objective-C无
async/await,依赖闭包回调。 - 错误处理:Objective-C常用NSError指针,而Swift用
throws。 - 线程管理:Objective-C需手动切换主线程,Swift有
@MainActor。 - 类型桥接:Swift与Objective-C类型需正确映射。
示例(Objective-C代码):
@interface LegacyAPI : NSObject
- (void)fetchDataWithCompletion:(void (^)(NSString *data, NSError *error))completion;
@end
@implementation LegacyAPI
- (void)fetchDataWithCompletion:(void (^)(NSString *data, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); // 模拟延迟
dispatch_async(dispatch_get_main_queue(), ^{
completion(@"Objective-C数据", nil);
});
});
}
@end
- 问题:Swift需桥接闭包到
async/await。
桥接方法
以下是将Objective-C异步代码与Swiftasync/await互操作的主要方法:
1. 使用Continuation桥接闭包
Swift提供withCheckedThrowingContinuation将闭包转为异步调用:
定义Objective-C接口
在.h文件中声明:
@interface LegacyAPI : NSObject
- (void)fetchDataWithCompletion:(void (^)(NSString *data, NSError *error))completion;
@end
Swift桥接
在Swift中定义扩展,使用async/await调用:
extension LegacyAPI {
func fetchData() async throws -> String {
try await withCheckedThrowingContinuation { continuation in
fetchDataWithCompletion { data, error in
if let error = error {
continuation.resume(throwing: error)
} else if let data = data {
continuation.resume(returning: data)
} else {
continuation.resume(throwing: NSError(domain: "", code: -1))
}
}
}
}
}
withCheckedThrowingContinuation:将闭包转为async。- 错误通过
resume(throwing:)抛出。
使用示例
在Swift中调用:
@MainActor
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
private let api = LegacyAPI()
override func viewDidLoad() {
super.viewDidLoad()
Task { try await updateUI() }
}
func updateUI() async throws {
let data = try await api.fetchData()
label.text = data
}
}
- 效果:Objective-C闭包转为
async,主线程更新UI。
2. 从Swift调用Objective-C
Objective-C无法直接调用Swift的async方法,需提供闭包接口:
Swift定义
在Swift中定义async方法和闭包桥接:
@objc class DataProvider: NSObject {
@objc func fetchData(completion: @escaping (String?, Error?) -> Void) {
Task {
do {
let data = try await fetchDataAsync()
completion(data, nil)
} catch {
completion(nil, error)
}
}
}
private func fetchDataAsync() async throws -> String {
try await Task.sleep(nanoseconds: 1_000_000_000)
return "Swift数据"
}
}
Objective-C调用
在Objective-C中调用:
DataProvider *provider = [[DataProvider alloc] init];
[provider fetchDataWithCompletion:^(NSString *data, NSError *error) {
if (error) {
NSLog(@"错误:%@", error);
} else {
NSLog(@"数据:%@", data);
}
}];
- 效果:Swift异步逻辑暴露为闭包,Objective-C可直接使用。
实战案例:混合网络请求
假设项目中有一个Objective-C网络模块,需在Swift中异步调用:
Objective-C模块
// NetworkService.h
@interface NetworkService : NSObject
- (void)fetchPostsWithCompletion:(void (^)(NSArray *posts, NSError *error))completion;
@end
// NetworkService.m
@implementation NetworkService
- (void)fetchPostsWithCompletion:(void (^)(NSArray *posts, NSError *error))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); // 模拟延迟
NSArray *posts = @[@{@"title": @"Post 1"}, @{@"title": @"Post 2"}];
dispatch_async(dispatch_get_main_queue(), ^{
completion(posts, nil);
});
});
}
@end
Swift桥接
在Swift中定义扩展:
struct Post: Codable {
let title: String
}
extension NetworkService {
func fetchPosts() async throws -> [Post] {
try await withCheckedThrowingContinuation { continuation in
fetchPostsWithCompletion { posts, error in
if let error = error {
continuation.resume(throwing: error)
return
}
do {
let data = try JSONSerialization.data(withJSONObject: posts, options: [])
let decoded = try JSONDecoder().decode([Post].self, from: data)
continuation.resume(returning: decoded)
} catch {
continuation.resume(throwing: error)
}
}
}
}
}
Swift使用
在视图控制器中调用:
@MainActor
class PostsViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
private let service = NetworkService()
private var posts: [Post] = []
override func viewDidLoad() {
super.viewDidLoad()
Task { try await loadPosts() }
}
func loadPosts() async throws {
posts = try await service.fetchPosts()
tableView.reloadData()
}
}
extension PostsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = posts[indexPath.row].title
return cell
}
}
- 效果:Objective-C网络请求转为
async,Swift异步加载并更新UI。
注意事项
性能开销
频繁桥接可能增加线程切换开销,建议批量处理:// 避免多次桥接 Task { let results = try await fetchMultiple() await MainActor.run { updateUI(results) } }错误映射
Objective-C的NSError需转为Swift的Error,确保类型一致。Objective-C暴露
Swift的@objc方法需正确标注,确保Objective-C可调用。测试验证
运行Thread Sanitizer,确保桥接无竞争。
小结
Swift与Objective-C在并发上的互操作通过withCheckedThrowingContinuation和闭包桥接实现。本节通过网络请求案例展示了桥接过程,从定义到UI集成,体现了混合项目的兼容性。掌握这些技巧,你将能在Swift和Objective-C共存的项目中无缝协作。下一节将探讨混合并发模型的注意事项,进一步完善你的迁移能力。
内容说明
- 结构:从挑战到桥接方法,再到案例、注意事项和总结。
- 代码:包含Swift到Objective-C和反向桥接示例,突出实用性。
- 语气:实践性且深入,适合技术书籍核心章节。
- 衔接:承接前节(GCD迁移),预告后续(混合注意事项)。
