问题描述
我的电话号码模型如下:
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
状态。
我这里需要的是,设置状态smsValidationOTPTriggered
或smsValidationSuccessful
时,我不希望应用程序的任何模块修改模型的值(phoneType,phone,代码,countryCode) 。换句话说,我希望在模型中设置这两个状态时模型切换到只读模式,并希望在尝试修改时以错误或异常通知模块。
我在这里想要达到的最佳实践已经存在吗?在提出这个问题之前,我已经进行了搜索,但没有找到任何问题。我该如何实现?
谢谢, 拉吉·帕万·冈达尔
解决方法
这样的事情怎么样,我认为最好为您的情况使用属性包装器!以下不是确切的解决方案,但可以修改/更改以满足您的需求 导入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()
,
您可以使用名称为“冰棍不可变性”的技术。对象最初是可变的,但可以被“冻结”。禁止修改冻结的对象。在您的情况下,当PhonesViewModel
属性的值为isValid
或smsValidationOTPTriggered
时,smsValidationSuccessful
将被冻结。
让我们添加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
}
}