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)
        }
    }
}