Ios 如何定义符合协议的通用属性
很抱歉,这个问题的标题有些模糊,但我不确定哪个标题更合适 让我先解释一下我的设置和我想要实现的目标 我已经定义了一个名为Ios 如何定义符合协议的通用属性,ios,swift,Ios,Swift,很抱歉,这个问题的标题有些模糊,但我不确定哪个标题更合适 让我先解释一下我的设置和我想要实现的目标 我已经定义了一个名为DeviceInterface的接口。我有两个符合该接口的对象:一个名为MockedDevice的模拟对象和一个名为DeviceImplementation的实际实现对象 计划使用MockedDevice进行SwiftUI预览,并在模拟器中运行应用程序(某些设备操作/值不可用),使用DeviceImplementation进行设备上执行 问题出现在DeviceApp中,我用符合
DeviceInterface
的接口。我有两个符合该接口的对象:一个名为MockedDevice
的模拟对象和一个名为DeviceImplementation
的实际实现对象
计划使用MockedDevice
进行SwiftUI预览,并在模拟器中运行应用程序(某些设备操作/值不可用),使用DeviceImplementation
进行设备上执行
问题出现在DeviceApp
中,我用符合DeviceInterface
的对象实例化了主应用程序视图。我定义了一个类型为DeviceInterface
的泛型属性,我试图根据代码是在模拟器上执行还是在设备上执行来设置该属性
当我尝试将该属性传递给应用程序的主视图(ContentView
,该视图使用符合接口DeviceInterface
的泛型类型进行初始化)时,我收到以下错误:
协议类型“DeviceInterface”的值不能与“DeviceInterface”一致;只有结构/枚举/类类型可以符合协议
将属性直接初始化为
let device=DeviceImplementation(设备:UIDevice.current)
或
let device=MockedDevice(设备:UIDevice.current)
(通过省略类型)然后传递这个值就可以了,所以我的问题似乎出在属性的类型定义上
我知道我可以稍微重新排列代码,然后在#if TARGET_IPHONE_SIMULATOR
案例中实例化ContentView
,使用上面省略类型定义的工作实例化方法,但我想了解我做错了什么,以及如何使下面的代码工作
请参见下面的示例,以演示我试图实现的目标。请记住,这是我要解决的问题的一个快速而简单的演示
// MARK: - Interfaces
protocol DeviceInterface {
var name: String { get }
}
protocol ObservableDevice: DeviceInterface, ObservableObject {}
// MARK: - Implementations
class MockedDevice: ObservableDevice {
@Published
var name: String = ""
init(name: String) {
self.name = name
}
}
class DeviceImplementation: ObservableDevice {
@Published
private(set) var name: String = ""
let device: UIDevice
init(device: UIDevice) {
self.device = device
name = device.name
}
}
// MARK: - App
@main
struct DeviceApp: App {
var body: some Scene {
WindowGroup {
let device: ObservableDevice = {
#if TARGET_IPHONE_SIMULATOR
return MockedDevice(name: "Mocked device")
#else
return DeviceImplementation(device: UIDevice.current)
#endif
}()
ContentView(device: device) // Generates error: Value of protocol type 'DeviceInterface' cannot conform to 'DeviceInterface'; only struct/enum/class types can conform to protocols
}
}
}
// MARK: - Main view
struct ContentView<T>: View where T: ObservableDevice {
@ObservedObject
private(set) var device: T
var body: some View {
Text("Hello World")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let device = MockedDevice(name: "Mocked device")
ContentView(device: device)
}
}
//标记:-接口
协议设备接口{
变量名称:字符串{get}
}
协议可观察设备:设备接口,可观察对象{}
//标记:-实现
类模拟设备:可观察设备{
@出版
变量名称:String=“”
init(名称:String){
self.name=名称
}
}
类设备实现:可观察设备{
@出版
私有(集合)变量名称:String=“”
let设备:UIDevice
初始化(设备:UIDevice){
self.device=设备
name=device.name
}
}
//马克:-应用程序
@主要
结构设备应用程序:应用程序{
var body:一些场景{
窗口组{
let设备:可观察设备={
#if TARGET_IPHONE_模拟器
返回MockedDevice(名称:“模拟设备”)
#否则
返回设备实现(设备:UIDevice.current)
#恩迪夫
}()
ContentView(设备:设备)//生成错误:协议类型“DeviceInterface”的值不能与“DeviceInterface”一致;只有结构/枚举/类类型才能与协议一致
}
}
}
//马克:主视图
结构ContentView:视图,其中T:ObservableDevice{
@观察对象
专用(set)var设备:T
var body:一些观点{
文本(“你好,世界”)
}
}
结构内容视图\u预览:PreviewProvider{
静态var预览:一些视图{
let device=MockedDevice(名称:“Mocked device”)
ContentView(设备:设备)
}
}
首先,我将如何真正做到这一点:子类化。您已经有了一个类,“抽象”版本正是您的“模拟”版本。所以我会继续做一个子类:
// "Abstract" version (just set the values and they never change)
class Device: ObservableObject {
@Published
fileprivate(set) var name: String
@Published
fileprivate(set) var batteryLevel: Float
init(name: String = "", batteryLevel: Float = -1) {
self.name = name
self.batteryLevel = batteryLevel
}
}
// Self-updating version based on UIKit. Similar version could be made for WatchKit
class UIKitDevice: Device {
private var token: NSObjectProtocol?
init(device: UIDevice) {
super.init(name: device.name, batteryLevel: device.batteryLevel)
device.isBatteryMonitoringEnabled = true
token = NotificationCenter.default.addObserver(forName: UIDevice.batteryLevelDidChangeNotification,
object: device,
queue: nil) { [weak self] _ in
self?.batteryLevel = device.batteryLevel
}
}
deinit {
NotificationCenter.default.removeObserver(token!)
}
}
那么设备
的定义是:
let device: Device = {
#if TARGET_IPHONE_SIMULATOR
return Device(name: "Mocked device")
#else
return UIKitDevice(device: .current)
#endif
}()
简单。我喜欢
但我不太喜欢Swift中的子类化。在这种情况下,它工作得很好,但在一般情况下,继承在我看来并不是一件好事。那么,您将如何使用组合而不是继承来实现这一点呢 首先,提取一个可以更新电池信息的东西:
import Combine // For Cancellable
protocol BatteryUpdater {
func addBatteryUpdater(update: @escaping (Float) -> Void) -> Cancellable
}
然后接受一个电池水(标记final
只是为了证明我可以;我不主张将final
洒得到处都是):
所以,现在设备只能正常保存数据,但它可以要求其他设备更新其电池电量。它可以取消这个请求。我也可以在这里使用关键路径或其他更高级的组合工具,但这说明了这一点。抽象掉变化的东西,即“电池值如何变化”。不要抽象掉不变的东西,即“我有一个电池电量,当它变化时通知观察者。”
有了这一点,看看追溯合规的力量。UIDevice可以是电池水:
extension UIDevice: BatteryUpdater {
func addBatteryUpdater(update: @escaping (Float) -> Void) -> Cancellable {
let nc = NotificationCenter.default
let token = nc.addObserver(forName: UIDevice.batteryLevelDidChangeNotification,
object: self, queue: nil) { _ in
// This retains self as long as the observer exists. That's intentional
update(self.batteryLevel)
}
return AnyCancellable {
nc.removeObserver(token)
}
}
}
方便的初始值设定项使从UIDevice轻松创建:
extension Device {
convenience init(device: UIDevice) {
self.init(name: device.name, batteryLevel: device.batteryLevel, batteryUpdater: device)
}
}
现在创建设备的过程如下所示:
let device: Device = {
#if TARGET_IPHONE_SIMULATOR
return Device(name: "Mocked device")
#else
return Device(device: .current)
#endif
}()
它只是一个装置。没有嘲笑。没有协议存在。没有泛型
如果有更多的东西需要更新,而不仅仅是BatteryLevel呢?不断通过越来越多的闭包可能会让人恼火。因此,您可以通过传递整个设备,将BatteryUpdater转变为完整的设备更新程序:
protocol DeviceUpdater {
func addUpdater(for device: Device) -> Cancellable
}
设备基本上是一样的,只是添加了proximityState以更新其他内容:
final class Device: ObservableObject {
@Published
private(set) var name: String
@Published
fileprivate(set) var batteryLevel: Float
@Published
fileprivate(set) var proximityState: Bool
private var updateObserver: Cancellable?
init(name: String = "", batteryLevel: Float = -1, proximityState: Bool = false,
updater: DeviceUpdater? = nil) {
self.name = name
self.batteryLevel = batteryLevel
self.proximityState = proximityState
updateObserver = updater?.addUpdater(for: self)
}
deinit {
updateObserver?.cancel()
}
}
UIDevice也遵循同样的方式,只是通过直接更新设备
“由内而外”
extension UIDevice: DeviceUpdater {
func addUpdater(for device: Device) -> Cancellable {
let nc = NotificationCenter.default
let battery = nc.addObserver(forName: UIDevice.batteryLevelDidChangeNotification,
object: self, queue: nil) { [weak device] _ in
device?.batteryLevel = self.batteryLevel
}
let prox = nc.addObserver(forName: UIDevice.proximityStateDidChangeNotification,
object: self, queue: nil) { [weak device] _ in
device?.proximityState = self.proximityState
}
return AnyCancellable {
nc.removeObserver(battery)
nc.removeObserver(prox)
}
}
}
这会强制属性为非私有属性。如果需要的话,可以通过传递可写密钥路径来解决这个问题。很多方法都可以奏效。但所有这些都遵循抽象更新的模式,而不是模拟最终的数据存储。首先,我将如何真正做到这一点:子类化。Y
extension UIDevice: DeviceUpdater {
func addUpdater(for device: Device) -> Cancellable {
let nc = NotificationCenter.default
let battery = nc.addObserver(forName: UIDevice.batteryLevelDidChangeNotification,
object: self, queue: nil) { [weak device] _ in
device?.batteryLevel = self.batteryLevel
}
let prox = nc.addObserver(forName: UIDevice.proximityStateDidChangeNotification,
object: self, queue: nil) { [weak device] _ in
device?.proximityState = self.proximityState
}
return AnyCancellable {
nc.removeObserver(battery)
nc.removeObserver(prox)
}
}
}