如何用Swift #Observable宏结合Objective-C协议实现双向属性更新?
问题描述
我正在SwiftUI中使用Objective-C编写的模型,目标是既能通过Swift(比如TextField)修改Car实例属性,也能通过Objective-C代码(比如调用car.changeColor())修改属性后同步更新SwiftUI界面。目前Swift端修改能正常刷新界面,但调用OC的changeColor()方法时界面无更新。我采用面向协议的设计,需要基于协议实现,想请教如何用WWDC23推出的@Observable宏实现预期效果。
Objective-C模型代码
@protocol CarProtocol <NSObject> @property (strong, readwrite) NSString * color; - (void)changeColor; @end @protocol PersonProtocol <NSObject> @property (strong, readwrite) NSString * name; - (void)changeName; @end @interface Car : NSObject <CarProtocol> @property (strong, readwrite) NSString * color; - (instancetype)initWithColor:(NSString *)color; - (void)changeColor; @end @implementation Car - (instancetype)init { if ((self = [super init])) { self.color = @""; } return self; } - (instancetype)initWithColor:(NSString *)color { if ((self = [super init])) { self.color = color; } return self; } - (void)changeColor { NSArray *colors = @[@"Red", @"Blue", @"Green", @"Yellow", @"Black", @"White"]; NSString *newColor = self.color; while (self.color == nil || [newColor isEqualToString:self.color]) { newColor = colors[arc4random_uniform((uint32_t)colors.count)]; } self.color = newColor; NSLog(@"The car color has been changed to %@", self.color); } @end
我尝试的Swift代码(存在界面不更新问题)
import SwiftUI import Combine extension Car: ObservableObject { public var objectWillChange: AnyPublisher<Void, Never> { publisher(for: \.color, options: .prior) .map { _ in } .eraseToAnyPublisher() } public static func randomCar() -> Car { let colors = ["Red", "Blue", "Green", "Yellow", "Purple", "Orange"] return Car(color: colors.randomElement() ?? "Red") } } @Observable class ObservableCar<CarT> where CarT: CarProtocol { public var car: CarT public init(_ car: CarT) { self.car = car } } struct SwiftObservableView<CarT>: View where CarT: CarProtocol & ObservableObject { @State var car: ObservableCar<CarT> public init(car: CarT) { self.car = ObservableCar(car) } var body: some View { NavigationStack { Form { Section(header: Text("Modification Form")) { VStack { TextField("Color", text: $car.car.color) } } Section(header: Text("Results")) { VStack { HStack() { Text("Color") Spacer() Text(car.car.color) } } } Section(header: Text("Actions")) { Button("Random Car") { self.car = ObservableCar<CarT>(Car.randomCar() as! CarT) } Button("Assign from Objective-C") { self.car.car.changeColor() } } } .navigationTitle("Car Data (Swift)") } } } #Preview { SwiftObservableView(car: Car(color: "Green")) }
附:我用Combine实现的非协议方案(仅作参考)
import SwiftUI import Combine extension PersonCombine: ObservableObject { public var objectWillChange: AnyPublisher<Void, Never> { publisher(for: \.name, options: .prior) .map { _ in } .eraseToAnyPublisher() } } extension CarCombine: ObservableObject { public var objectWillChange: AnyPublisher<Void, Never> { Publishers.Merge3( owner.objectWillChange, publisher(for: \.owner, options: .prior).map { _ in }, publisher(for: \.color, options: .prior).map { _ in } ) .eraseToAnyPublisher() } public static func randomCar() -> CarCombine { let colors = ["Red", "Blue", "Green", "Yellow", "Purple", "Orange"] return CarCombine(color: colors.randomElement() ?? "Red") } } struct CombineView: View { @StateObject var car: CarCombine var body: some View { NavigationStack { Form { Section(header: Text("Modification Form")) { VStack { TextField("Color", text: $car.color) } VStack { TextField("Owner Name", text: $car.owner.name) } } Section(header: Text("Results")) { VStack { HStack { Text("Color") Spacer() Text(car.color) } } VStack { HStack { Text("Owner") Spacer() Text(car.owner.name) } } } Section(header: Text("Actions")) { Button("Random Car") { let randomCar = CarCombine.randomCar() car.color = randomCar.color } Button("Assign Car.Color from Objective-C") { car.changeColor() } Button("Assign Person.Name from Objective-C") { car.owner.changeName() } } Section(header: Text("Owner Details")) { NavigationLink(destination: PersonDetailCombineView(person: car.owner)) { HStack { Text("Owner") Spacer() Text(car.owner.name) } } } } .navigationTitle("Car Data (Combine)") } } } #Preview { CombineView(car: CarCombine()) }
解决方案
核心问题在于:OC对象本身不支持@Observable宏的自动观察,且你之前的ObservableCar包装类无法感知OC内部修改属性的动作。需要通过KVO监听OC属性变化,再结合@Observable的手动通知来触发界面更新。
步骤1:修改协议,确保OC属性支持KVO
OC默认通过@property合成的属性自带KVO支持,因此原CarProtocol无需额外修改,直接使用即可。
步骤2:使用@Observable包装OC模型,添加KVO监听
替换原来的ObservableCar和Car的ObservableObject扩展,用@Observable类包装OC实例,并监听属性变化:
import SwiftUI @Observable class ObservableCar<CarT: CarProtocol> { var car: CarT private var observation: NSKeyValueObservation? init(_ car: CarT) { self.car = car // 监听color属性变化,触发Observable的界面更新通知 observation = car.observe(\.color, options: [.new]) { [weak self] _, _ in self?.objectWillChange.send() } } // 封装OC的changeColor方法,统一调用入口 func changeColor() { car.changeColor() } // 保持随机生成Car的静态方法 static func randomCar() -> ObservableCar<Car> { let colors = ["Red", "Blue", "Green", "Yellow", "Purple", "Orange"] return ObservableCar(Car(color: colors.randomElement() ?? "Red")) } }
步骤3:修改SwiftUI视图,直接使用包装类
简化视图代码,不再依赖OC类实现ObservableObject,而是直接使用ObservableCar:
struct SwiftObservableView<CarT: CarProtocol>: View { @State var car: ObservableCar<CarT> init(car: CarT) { self.car = ObservableCar(car) } var body: some View { NavigationStack { Form { Section(header: Text("Modification Form")) { TextField("Color", text: $car.car.color) } Section(header: Text("Results")) { HStack { Text("Color") Spacer() Text(car.car.color) } } Section(header: Text("Actions")) { Button("Random Car") { if let randomCar = ObservableCar.randomCar() as? ObservableCar<CarT> { self.car = randomCar } } Button("Assign from Objective-C") { car.changeColor() } } } .navigationTitle("Car Data (Swift)") } } } #Preview { SwiftObservableView(car: Car(color: "Green")) }
关键说明
- KVO监听:通过
NSKeyValueObservation监听OC对象的color属性变化,当OC内部调用changeColor()修改color时,会触发KVO回调,此时调用objectWillChange.send()通知@Observable更新界面。 - 包装类封装:将OC的方法封装到
ObservableCar中,统一入口,确保所有修改动作都能被监听。 - 面向协议兼容:包装类的泛型约束依然基于
CarProtocol,符合你的面向协议设计要求。
扩展:处理多属性或嵌套对象
如果你的OC模型有多个属性或者嵌套的PersonProtocol对象,可以扩展KVO监听:
@Observable class ObservablePerson<PersonT: PersonProtocol> { var person: PersonT private var observation: NSKeyValueObservation? init(_ person: PersonT) { self.person = person observation = person.observe(\.name, options: [.new]) { [weak self] _, _ in self?.objectWillChange.send() } } func changeName() { person.changeName() } } // 嵌套使用示例 @Observable class ObservableCarWithOwner<CarT: CarProtocol, PersonT: PersonProtocol> { var car: CarT var owner: ObservablePerson<PersonT> private var carObservation: NSKeyValueObservation? init(car: CarT, owner: PersonT) { self.car = car self.owner = ObservablePerson(owner) carObservation = car.observe(\.color, options: [.new]) { [weak self] _, _ in self?.objectWillChange.send() } } }
内容的提问来源于stack exchange,提问作者OzB
相关产品推荐
相关产品推荐

