I understand that RLMObjects cannot store NSDecimalNumber. To get around this, I tried the following, but failed:
private dynamic var _amount: String = ""
public var amount: NSDecimalNumber {
get { return NSDecimalNumber(string: _amount) }
set { _amount = newValue.stringValue }
}
I am getting a failure stating the RLMObjects cannot store NSDecimalNumbers. I was under the impression that non-dynamic properties would not be stored in Realm
Any property of RLMObjects must be dynamic. So amount: NSDecimalNumber property should be defined as dynamic
Like below:
private dynamic var _amount: String = ""
public dynamic var amount: NSDecimalNumber {
get { return NSDecimalNumber(string: _amount) }
set { _amount = newValue.stringValue }
}
And computed property should not be persisted. (Of course, amount property is NSDecimalNumber, so it can not be persisted in Realm. If amount property will be persisted, exception occured)
To prevent it, you can override ignoredProperties() method and return "amount" as string array.
override public class func ignoredProperties() -> [AnyObject]! {
return ["amount"]
}
Based on the above, complete class definition is as follows:
public class Product: RLMObject {
private dynamic var _amount: String = ""
public dynamic var amount: NSDecimalNumber {
get { return NSDecimalNumber(string: _amount) }
set { _amount = newValue.stringValue }
}
public override class func ignoredProperties() -> [String]! {
return ["amount"]
}
}
Related
I have a phone number model which looks like this:
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
}
}
As you can see above the phone model can transition between different states. The SMS validation is available for few countries and for few it is not applicable. So, I plan on setting smsValidationOTPTriggered state when SMS validation is applicable for a country and while the validation is in progress.
What I need here is, while the states smsValidationOTPTriggered or smsValidationSuccessful are set I would not want any module of the application to modify the values(phoneType, phone, code, countryCode) of the model. In other words, I would like the model to switch to a read-only mode while these 2 states are set in model and would like the module to be informed with an error or exception when a modification is attempted.
Is there a best practice already available for what I am trying to achieve here? I have searched before raising this question but did not find any. How can I achieve this?
Thanks,
Raj Pawan Gumdal
How about something like this, I think its better to use property wrappers for your case! The below is not an exact solution but can modify/change to accommodate your need
import 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()
You can use a technique with the name "popsicle immutability". An object is initially mutable, but can be "frozen". Modifications for frozen objects are forbidden. In your case PhonesViewModel become frozen when isValid property have value smsValidationOTPTriggered or smsValidationSuccessful.
Let's add Freezable protocol for requirements to objects that can become immutable and conforming for PhonesViewModel:
protocol Freezable: class {
var isFrozen: Bool { get }
}
extension PhonesViewModel: Freezable {
var isFrozen: Bool {
isValid == .smsValidationOTPTriggered || isValid == .smsValidationSuccessful
}
}
Now we must add validation for isFrozen value when a property is assigned. It can be added in property observers like:
...
public var phone: String? {
didSet {
validate()
}
}
...
private func validate() {
assert(!isFrozen)
}
Or using property wrapper:
#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
}
}
}
So your class will look like:
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
}
}
I have a couple of private properties defined like so..
private var tableConfig : TableViewConfig<Company, CompanyCell>?
private var tabledata = [Company]() {
didSet {
tableConfig?.items = tabledata
}
}
Now there are 2 other properties defined like so...
var model: Model?
var companyCell: TableviewCell?
Now, if get the value in model as Industry and the value in companyCell as IndustryCell, how can I update these values to private var tableConfig : TableViewConfig.... and private var tabledata = [Company]().... so that they will ultimately have the values like so..
private var tableConfig : TableViewConfig<Industry, IndustryCell>?
private var tabledata = [Industry]() {
didSet {
tableConfig?.items = tabledata
}
}
you can use a get set variabile to achieve this result
var _tabledata: [Industry] {
get {
return tabledata
}
set {
tabledata = newValue
}
}
As the attribute private implies properties and functions declared as private can only be accessed within the scope they are declared.
You have two options:
Change the access control to fileprivate (if the caller is in the same file) or internal or lower restriction.
Add an internal or lower restriction computed property or method in the scope of the private property to set it.
You can expose an init() or a method to set the value of private properties, i.e.
class SampleClass {
private var tableConfig : TableViewConfig<Company, CompanyCell>?
private var tabledata = [Company]() {
didSet {
tableConfig?.items = tabledata
}
}
init(tableData: [Company]) {
self.tabledata = tabledata
}
func add(item: Company) {
self.tabledata.append(item)
}
}
You can create an init in case you want to initialise your private variable and use a method in case you want to update the private variables.
I'm using the HandyJSOn framework to serialize and deserialize objects in Swift3. Now I've the problem that I want to exclude some properties from this process. I tried to follow the steps given on the GithHub page but I can't get to work:
class MyClass : HandyJSON {
private var excludeThisProperty : String
public func mapping(mapper: HelpingMapper) {
mapper >>> self.excludeThisProperty
}
}
The compiler fials with the error:
binary operator >>> cannot be applied to operands of type HelpingMapper and String
+++ Example +++
class MyClass : HandyJSON {
private let myPropertyDefault : String? = "example"
private var myProperty : String
public required init() {
myProperty = myPropertyDefault!
}
public func reset() {
myProperty = myPropertyDefault!
}
public func mapping(mapper: HelpingMapper) {
mapper >>> self.myPropertyDefault
}
}
Please change your string to optional :
private var excludeThisProperty : String?
Example code :
let jsonString = "{\"excludeThisProperty\":\"sdfsdf\"}"
if let myclass = MyClass.deserialize(from: jsonString) {
print(myclass)
}
class MyClass : HandyJSON {
private var myPropertyDefault : String? = "example" // changed from let to var
private var myProperty : String
public required init() {
myProperty = myPropertyDefault!
}
public func reset() {
myProperty = myPropertyDefault!
}
public func mapping(mapper: HelpingMapper) {
mapper >>> self.myPropertyDefault
}
}
I'm stuck putting all of the above together. I'll appreciate if I can get any input.
Here's my short setup:
typealias RealmObject = Object
/// Extension to ignore undefined keys when mapping
extension RealmObject : EVReflectable {
open override func setValue(_ value: Any?, forUndefinedKey key: String) { }
}
Sample Realm models:
class Product: RealmObject {
dynamic var productId: String = ""
let productLanguages = List<ProductLanguage>()
override static func primaryKey() -> String? {
return "productId"
}
}
class ProductLanguage: RealmObject {
dynamic var productLanguageId: String = ""
dynamic var languageCode: String = ""
dynamic var productName: String = ""
override static func primaryKey() -> String? {
return "productLanguageId"
}
}
To fetch product details I use Moya and RxSwift:
func getProduct(productItemKey: String) -> Observable<Product> {
return provider.request(.product(productId: productItemKey)).map(to: Product.self)
}
I think .map(to: Product.self) does not work with realm Lists out of the box. For each object inside the list I get an error:
ERROR: Could not create an instance for type
dict:{
CreateDate = "2015-10-12T11:11:50.013Z";
IngredientList = "Acao ingredient";
LanguageCode = "en-US";
ProductId = "d6bb0084-6838-11e5-9225-00ac14ef2300";
ProductLanguageId = "f96848d0-df77-4594-99b7-d390bb127891";
ProductName = Acao;
Tagline = "The smart drink - 100% organic, vegan energy booster with guara"
}
Is there any other way to map Moya response into Realm objects?
Thank you very much for any input!
Turns out it was a bug in EVReflection. Fixed in 4.17.0
I've followed the solution at Make a Swift dictionary where the key is "Type"? to create dictionaries that can use a class type as keys.
What I want to do is: I have one dictionary that should store class types with their class type (aka metatype) as keys, too:
class MyScenario {
static var metatype:Metatype<MyScenario> {
return Metatype(self)
}
}
var scenarioClasses:[Metatype<MyScenario>: MyScenario.Type] = [:]
Then I have methods to register and execute scenarios:
public func registerScenario(scenarioID:MyScenario.Type) {
if (scenarioClasses[scenarioID.metatype] == nil) {
scenarioClasses[scenarioID.metatype] = scenarioID
}
}
public func executeScenario(scenarioID:MyScenario.Type) {
if let scenarioClass = scenarioClasses[scenarioID.metatype] {
let scenario = scenarioClass()
}
}
... Problem is in the last line:
Constructing an object of class type 'MyScenario' with a metatype
value must use a 'required' initializer.
It looks like the compiler is confused at that point since I cannot use 'required' at that assignment. Does anyone have an idea how I would have to instantiate the scenarioClass in executeScenario()?
This must do the job.
import Foundation
struct Metatype<T> : Hashable
{
static func ==(lhs: Metatype, rhs: Metatype) -> Bool
{
return lhs.base == rhs.base
}
let base: T.Type
init(_ base: T.Type)
{
self.base = base
}
var hashValue: Int
{
return ObjectIdentifier(base).hashValue
}
}
public class MyScenario
{
var p: String
public required init()
{
self.p = "any"
}
static var metatype:Metatype<MyScenario>
{
return Metatype(self)
}
}
var scenarioClasses:[Metatype<MyScenario>: MyScenario.Type] = [:]
public func registerScenario(scenarioID:MyScenario.Type)
{
if (scenarioClasses[scenarioID.metatype] == nil)
{
scenarioClasses[scenarioID.metatype] = scenarioID
}
}
public func executeScenario(scenarioID:MyScenario.Type)
{
if let scenarioClass = scenarioClasses[scenarioID.metatype]
{
let scenario = scenarioClass.init()
print("\(scenario.p)")
}
}
// Register a new scenario
registerScenario(scenarioID: MyScenario.self)
// Execute
executeScenario(scenarioID: MyScenario.self)
// Should print "any"