7.3 与老剧团同台:Foundation与Objective-C


7.3 与老剧团同台:Foundation 与 Objective-C

本节摘要:界面脚下有两层地基:Foundation 提供日期、数据、格式化与编解码的日用工具,Objective-C 则是上一代主演,大量老工程仍在用它写核心模块。本节讲 Foundation 的四件高频工具(Date 与格式化、Data、JSON 解码、通知中心),再讲 Swift 与 Objective-C 的双向调用规则与混编注意事项。结论先行:Swift 调 Objective-C 几乎无感,反向调用需要暴露标记;桥接类型的相互转换是混编的日常。

排片界面的一生(第7章前两节)跑在两代框架上,而框架脚下还有一层更老的库房——这一节开门盘点。

Foundation:四件高频日用工具

排片业务每天都要处理"时间"与"数据",全是 Foundation 的地盘。

第一件:Date 与格式化。 影评常说"今晚 19:00 场",用户说的是墙上的钟,程序里存的是绝对时间点:

import Foundation var calendar = Calendar.current let components = DateComponents(year: 2026, month: 8, day: 21, hour: 19) let showtime = calendar.date(from: components) ?? Date() let formatter = DateFormatter() formatter.dateFormat = "M月d日 HH:mm" print(formatter.string(from: showtime)) // 输出:8月21日 19:00 formatter.dateFormat = "EEEE" // 星期几(随系统语言) print(formatter.string(from: showtime)) // 输出(中文环境):星期五

第二件:Data 与字符串互转。 网络与文件世界说字节(Data),界面世界说字符(String):

let json = #"{"film":"雾中灯塔","seats":12}"# let data = json.data(using: .utf8)! // 字符串 → 字节 print("字节长度 \(data.count)") // 输出:字节长度 30(UTF-8 下中文占 3 字节) let back = String(data: data, encoding: .utf8)! print(back == json ? "无损往返" : "损坏") // 输出:无损往返

第三件:JSON 解码——2.3 节 decodeFailed 事故的正源。 Codable 协议让结构与 JSON 自动对接:

struct SessionDTO: Codable { let film: String let seats: Int } do { let session = try JSONDecoder().decode(SessionDTO.self, from: data) print("解码成功:\(session.film) 余 \(session.seats) 座") // 输出:解码成功:雾中灯塔 余 12 座 } catch { print("解码失败:\(error)") // 字段名对不上时走这里(2.3 节的 decodeFailed) }

第四件:通知中心——界面间的一对多广播。 票务系统出票后,排片页、统计页、消息页都要刷新:

final class TicketNotifier { static let paid = Notification.Name("ticket.paid") } let center = NotificationCenter.default var observer: NSObjectProtocol? observer = center.addObserver(forName: TicketNotifier.paid, object: nil, queue: .main) { note in let seats = note.userInfo?["seats"] as? Int ?? 0 print("收到出票广播,余票更新为 \(seats)") // 输出:收到出票广播,余票更新为 11 } center.post(name: TicketNotifier.paid, object: nil, userInfo: ["seats": 11]) // 用完必须移除观察者(现代 API 会自动移除,但手动管理的心智仍要保留——第4章纪律) if let observer = observer { center.removeObserver(observer) }

⚠️ 常见坑:DateFormatter 是重对象,循环里反复创建会拖慢列表滚动——建成属性复用。通知观察者忘了移除,在旧式 API 下会造成回调打到已释放界面的经典崩溃(又回到第4章)。

Objective-C:上一代主演的同台规则

大量真实工程里,Swift 界面之下埋着 Objective-C 写的支付、播放器、老网络层。好消息是 Swift 调 Objective-C 几乎无感——桥接头文件把老类的声明翻译过来,调用方写的是 Swift 语法:

// 假设老工程有一个 Objective-C 票务类(经桥接头暴露): // OC 侧声明示例(理解用,不必默写): // LGTicketManager.h 里: // @interface LGTicketManager : NSObject // - (void)purchaseTicket:(NSInteger)count // completion:(void (^)(BOOL success, NSString *message))completion; // @end // Swift 侧调用:方法名被自动映射,选择器冒号消失、参数带标签 let manager = LGTicketManager() manager.purchaseTicket(2) { success, message in print(success ? "出票成功:\(message)" : "失败:\(message)") // 闭包捕获 self 时的 weak 规则与第4.2 节完全一致——混编不豁免内存纪律 }

映射规则记两条就够:OC 的多段方法名在 Swift 里变成"方法名 + 参数标签";OC 的 NSError 双星指针参数在 Swift 里变成 throws(2.3 节的桥接伏笔在此兑现——老代码的错误处理自动翻译成 Swift 的 do-catch 世界)。

**反方向(Swift 给 OC 用)**需要显式声明:类要继承 NSObject(或标 @objc),成员要暴露给 OC 运行时:

final class QuickPayBridge: NSObject { @objc func handleCoupon(_ code: String) -> Bool { // @objc:登记进 OC 运行时 return code.count == 6 // 会员码六位才有效 } } // 老代码即可通过选择器调用:选择器名 handleCoupon: 与方法自动对应

不能暴露的 Swift 特性(泛型成员、结构体方法、可选协议要求之外的 Swift 协议)会在编译时被指出——混编边界其实就是"OC 运行时认识什么"的边界。

完整案例:排片页接入老票务 SDK

背景:排片页(SwiftUI 或 UIKit 均可)要调用老 OC 票务 SDK 出票,回调可能晚于界面退场。操作三步:调用、weak 守卫、错误翻译:

final class PurchaseCoordinator: NSObject { private let manager = LGTicketManager() func buy(count: Int, onFinish: @escaping (String) -> Void) { // 老接口常以 NSError 返回错误,桥接后自动变 throws 风格或回调双参 manager.purchaseTicket(count) { [weak self] success, message in guard let self = self else { return } // 界面已退场:4.2 节守卫 onFinish(success ? "票已出:\(message)" : "出票失败:\(message)") _ = self // 保住 self 引用语义的说明位 } } } let coordinator = PurchaseCoordinator() coordinator.buy(count: 2) { result in print(result) // 输出:票已出:2 张 19:00 场 }

结果解读:Swift 侧只需按 4.2 节纪律写 weak;错误信息经回调字符串带出(老 SDK 不抛 Swift 错误时,用 2.3 节的 Result 在边界上翻译成 Swift 风格)。变式:给 coordinator 加一个 func buyAsync(count: Int) async throws -> String,用 withCheckedThrowingContinuation 把回调式老接口包装成 async/await(5.2 节)——这是混编工程里"新语法包老接口"的标准手法。

混编注意事项清单

场景 规则 一句话记忆
Swift 调 OC 类 桥接头自动翻译 无感调用
OC 调 Swift 类 继承 NSObject、成员标 @objc 显式暴露
错误传递 OC 的 NSError 参数自动变 throws 双星变问号
空值语义 OC 的 nil 指针对应 Swift 的可选 桥接可选化
命名映射 多段方法名变参数标签 冒号变标签
不能暴露 泛型成员、纯 Swift 协议、结构体方法 边界即 OC 运行时的认知边界

💡 关键直觉:混编工程的心智模型是"一个工程、两套运行时的交集"。交集之外各写各的,交集之内(桥接头、@objc、NSError)按映射表办事。判断某个 API 能否给 OC 用,本质是判断它是否只用了交集里的特性。

库房锁门

  • Foundation 四件套:Date 与 DateFormatter(复用实例)、Data 与字符串互转、Codable 解码、通知中心广播;
  • Swift 调 OC 无感:桥接头 + 命名映射,错误自动 throws 化;
  • OC 调 Swift 要暴露:NSObject 子类 + @objc 标记;
  • 内存与并发纪律不豁免:weak 守卫、取消任务在混编里同样生效;
  • 回调式老接口可用延续包装成 async——新旧两代在边界上和平共处。

至此界面的一生全部演完:从 Swift 剧本到两代框架再到老剧团同台。终幕收拾行囊——工具链、测试与代码风格,让这个剧组能长期巡演。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U