【问题标题】:Swift - toggle model to readonly momentarilySwift - 暂时将模型切换为只读
【发布时间】:2021-01-08 18:50:45
【问题描述】:

我有一个电话号码模型,如下所示:

import UIKit
import Foundation

struct PhoneValidation : OptionSet {
    let rawValue: Int
    
    static let phoneInValid = PhoneValidation(rawValue: 1 << 0)
    static let phoneValid = PhoneValidation(rawValue: 1 << 1)
    static let smsValidationAttempted = PhoneValidation(rawValue: 1 << 2)
    static let smsValidationFailed = PhoneValidation(rawValue: 1 << 3)
    static let smsValidationSuccessful = PhoneValidation(rawValue: 1 << 4)      // OTP is successfully validated in backend. The field should be non-editable in this duration
    static let smsValidationOTPTriggered = PhoneValidation(rawValue: 1 << 5)    // OTP validation triggered. The field should be non-editable in this duration
}

class PhonesViewModel: NSCopying {

    public var phoneType: PhoneNumberType = PhoneNumberType.mobile
    
    public var phone: String?
    
    public var code: String?
    
    public var countryCode: String?
    
    public var isValid : PhoneValidation?
    
    func copy(with zone: NSZone? = nil) -> Any {
        let copy = PhonesViewModel()
        copy.phoneType = phoneType
        copy.phone = phone
        copy.code = code
        copy.countryCode = countryCode
        copy.isValid = isValid
        return copy
    }
}

正如您在上面看到的,手机模型可以在不同状态之间转换。 SMS 验证适用于少数国家,少数国家不适用。因此,我计划在 SMS 验证适用于某个国家/地区并且正在进行验证时设置 smsValidationOTPTriggered 状态。

我需要的是,在设置状态 smsValidationOTPTriggeredsmsValidationSuccessful 时,我不希望应用程序的任何模块修改模型的值(phoneType、phone、code、countryCode)。换句话说,我希望模型在模型中设置这两种状态时切换到只读模式,并希望在尝试修改时通知模块错误或异常。

对于我在这里想要实现的目标,是否已有最佳实践可用?在提出这个问题之前我已经搜索过,但没有找到。我怎样才能做到这一点?

谢谢, 拉吉·帕万·古姆达尔

【问题讨论】:

  • 为什么不从你的类的不可变版本开始呢?就像字符串和可变字符串、数组和可变数组等等。因此,没有人可以保留对您的模型的可变引用。
  • 另外,无论如何您都不应该允许直接访问模型属性。所有属性都应该是私有的访问器。所以访问者可以锁门。

标签: ios swift xcode cocoa-touch immutability


【解决方案1】:

您可以使用名为“popsicle immutability”的技术。对象最初是可变的,但可以“冻结”。禁止对冻结对象进行修改。在您的情况下,当 isValid 属性具有值 smsValidationOTPTriggeredsmsValidationSuccessful 时,PhonesViewModel 被冻结。

让我们添加Freezable 协议,以满足对象的要求,这些对象可以变得不可变并符合PhonesViewModel

protocol Freezable: class {
    var isFrozen: Bool { get }
}

extension PhonesViewModel: Freezable {
    var isFrozen: Bool {
        isValid == .smsValidationOTPTriggered || isValid == .smsValidationSuccessful
    }
}

现在我们必须在分配属性时为isFrozen 值添加验证。它可以添加到属性观察器中,例如:

...
public var phone: String? {
    didSet {
        validate()
    }
}
...
private func validate() {
    assert(!isFrozen)
}

或者使用属性包装器:

@propertyWrapper
struct Guarded<Value> {
    private var value: Value

    init(wrappedValue: Value) {
        value = wrappedValue
    }

    @available(*, unavailable)
    var wrappedValue: Value {
        get { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
        set { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
    }

    static subscript<EnclosingSelf: Freezable>(
        _enclosingInstance object: EnclosingSelf,
        wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
        storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
    ) -> Value {
        get {
            object[keyPath: storageKeyPath].value
        }
        set {
            precondition(!object.isFrozen, "Object \(object) is frozen! Modifications are forbidden")
            object[keyPath: storageKeyPath].value = newValue
        }
    }
}

所以你的班级看起来像:

class PhonesViewModel: NSCopying {

    @Guarded
    public var phoneType: PhoneNumberType = PhoneNumberType.mobile

    @Guarded
    public var phone: String?

    @Guarded
    public var code: String?

    @Guarded
    public var countryCode: String?

    @Guarded
    public var isValid : PhoneValidation?

    func copy(with zone: NSZone? = nil) -> Any {
        let copy = PhonesViewModel()
        copy.phoneType = phoneType
        copy.phone = phone
        copy.code = code
        copy.countryCode = countryCode
        copy.isValid = isValid
        return copy
    }
}

【讨论】:

    【解决方案2】:

    这样的事情怎么样,我认为最好为您的情况使用属性包装器!以下不是一个确切的解决方案,但可以修改/更改以满足您的需要 导入 UIKit

     enum PhoneNumberType {
        case mobile
    }
    
    enum PhoneValidation {
        case phoneInValid
        case phoneValid
        case smsValidationAttempted
        case smsValidationFailed
        case smsValidationSuccessful
        case smsValidationOTPTriggered
    }
    
    struct PhonesViewModel {
        public var phoneType: PhoneNumberType = PhoneNumberType.mobile
        public var phone: String?
        public var code: String?
        public var countryCode: String?
        public var phoneValidation : PhoneValidation?
        
        func validate(value: [PhoneValidation]) -> Bool {
            //add proper check here
            return false 
        }
    }
    
    
    @propertyWrapper
    struct Wrapper {
        private(set) var value: PhonesViewModel? = nil
        var validators: [PhoneValidation] = []
        
        var wrappedValue: PhonesViewModel? {
            get { value }
            set {
                if let model = newValue, model.validate(value: validators) {
                    value = newValue
                    print("Value assigned")
                } else {
                    print("Value not assigned")
                }
            }
        }
    }
    
    struct SomeOtherClass {
        @Wrapper(validators: [PhoneValidation.phoneInValid])
        var model: PhonesViewModel?
    }
    
    var a = SomeOtherClass()
    a.model = PhonesViewModel()
    a.model = PhonesViewModel()
    

    【讨论】:

    • 感谢您的回答。我认为这解决了a.model 不能被任何模块更改的问题,这很好。我还需要的是没有人应该能够做到a.model.phone = "SomeOtherNumber"。这意味着对象的内部属性也应该是只读的。我该怎么做?
    • @RajPawanGumdal 这就是我创建结构而不是类的原因。你可以使用结构吗?因此,如果您强制执行结构,则无法修改值,每次如果有人想要更新值,他/她需要创建一个新结构(因为模型已经进行了验证,这应该可以工作)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-05-19
    • 2015-04-10
    • 1970-01-01
    • 1970-01-01
    • 2012-08-22
    • 2011-12-16
    • 1970-01-01
    相关资源
    最近更新 更多