Top-level Bool encoded as number property list fragment. PropertyListEncoder - swift

I have this generic function to save in NSUserDefaults, in generally works but now I want to save a boolean value and I get an error. I could not find anything and I do not understand why it is not working.
extension UserDefaults {
func saveUserDefaults<T: Codable>(withKey key: String, myType: T) throws{
do {
let data = try PropertyListEncoder().encode(myType)
UserDefaults.standard.set(data, forKey: key)
print("Saved for Key:", key)
} catch let error {
print("Save Failed")
throw error
}
}
I am calling it like this:
try! UserDefaults().saveUserDefaults(withKey: "String", myType: false)
This is the error I get. I know there is an other way to save boolean values, but I am wondering why it is not working like this?
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.EncodingError.invalidValue(false,
Swift.EncodingError.Context(codingPath: [], debugDescription:
"Top-level Bool encoded as number property list fragment.",
underlyingError: nil))
Thanks!

A PropertyListEncoder encodes to a “property list,” and that is always an
array or a dictionary, compare PropertyListSerialization.
Therefore
let data = try PropertyListEncoder().encode(myType)
fails if myType is a Bool (or anything which is not an array
or a dictionary).
The possible objects in a property list are also restricted, they can only be instances of
NSData, NSString, NSArray, NSDictionary, NSDate, or NSNumber – or of Swift types
which are bridged to one of those Foundation types.

As #Martin said a PropertyListEncoder supports only property lists on top level, but not a single fragment of property list like NSNumber.
A very simple (though not very elegant) workaround is to wrap any object into array:
let data = try PropertyListEncoder().encode([myType])
UserDefaults.standard.set(data, forKey: key)
And decode it as:
let arr = try PropertyListDecoder().decode([T].self, from: data)
return arr.first
see https://www.marisibrothers.com/2018/07/workaround-for-serializing-codable-fragments.html

You do not need to encode a Boolean value to save into UserDefaults. You can directly save the boolean into the UserDefaults by calling
let myValue: Bool = false
UserDefaults.standard.set(myValue, forKey: "key")
See Apple docs: UserDefaults.set(_:forKey:).
In fact, Float, Double, Integer, Bool and URL types do not need to be encoded and can directly be saved to UserDefaults.
I see that you have a function that takes a Codable type, encodes it and then saves it to UserDefaults. As Martin R pointed out, you will have to modify that function and check whether passed object can be directly saved to UserDefaults without a need for encoding. It is not necessarily pretty but something like this could work:
switch objectToSave {
case let aFloatType as Float:
UserDefaults.standard.set(aFloatType, forKey: "key")
case let aDoubleType as Double:
UserDefaults.standard.set(aDoubleType, forKey: "key")
case let anIntegerType as Int:
UserDefaults.standard.set(anIntegerType, forKey: "key")
case let aBoolType as Bool:
UserDefaults.standard.set(aBoolType, forKey: "key")
case let aURLType as URL:
UserDefaults.standard.set(aURLType, forKey: "key")
default:
//encode the object as a PropertyList and then save it to UserDefaults
do {
let data = try PropertyListEncoder().encode(objectToSave)
UserDefaults.standard.set(data, forKey: key)
} catch let error {
//the object you passed cannot be encoded as a PropertyList
//possibly because the object cannot be represented as
//NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary - or equivalent Swift types
//also contents of NSArray and NSDictionary have to be one of the types above
print("Save Failed: \(error)")
//perform additional error handling
}
}

Related

Swift Userdefaults converting String to __NSCFString

I have code that save a dictionary of [String: Any] in UserDefaults. On retrieval String are changed to __NSCFString. I am using Mixpanel to track events and sends this dictionary as events properties. Now the problem is __NSCFString is not a valid MixpanelType so Mixpanel is discarding my dictionary.
Questions:
Is there a way to get same datatypes that are saved using dictionary in UserDefaults?
Is there a way Mixpanel accepts converted datatypes?
Here is a code I am using
var mixpanelProperties: [String: Any] {
get { defaults.dictionary(forKey: "\(#function)") ?? [:] }
set { defaults.set(newValue, forKey: "\(#function)") }
}
mixpanelProperties = ["a-key": "value for the key"]
let prop = mixpanelProperties
print("Type of: \(String(describing: prop["a-key"]))")
asdad
MacOS and iOS use a variety of different classes to represent strings, which are all compatible. x as? String should just work, no matter what the concrete class of x is. Unless you use code that explicitely checks the class which you shouldn't do.

Use of undeclared type 'valueMirror' when using Mirror

I am trying to map a struct to other class that have same properties. but it keep showing this error
Use of undeclared type 'valueMirror'
My code
extension Mapper {
func map<T:Object>(to type: T.Type){
let object = T()
let m = Mirror(reflecting: self)
for property in m.children {
guard let key = property.label else { continue }
let value = property.value
let valueMirror = Mirror(reflecting: value)
if valueMirror.displayStyle == .collection {
let array = value as! valueMirror.subjectType // <-- error
object.setValue(array.asRealMList, forKey: key)
} else {
object.setValue(value, forKey: key)
}
}
}
}
valueMirror.subjectType is not a type as far as the compiler is concerned. There must be a compile time type after as!.
Since the only place you are using array is in array.asRealMList, you probably just need to cast value to a type that has the property asRealMList. As you said in the comments, this is an extension on Array.
Luckily Array is covariant, so even without knowing which type of array it is, you'll be able to cast any array to [Any]:
let array = value as! [Any]
valueMirror.subjectType is of type Any.Type.
You probably want to cast value to Any.

Check if variable is computed or stored

In my app I translate objects from custom classes into dictionaries so that they can be saved locally in a plist as well as on a server. I use the following to turn the properties of a class into a dictionary:
func dictionary() -> [String : Any] {
var count: UInt32 = 0;
let myClass: AnyClass = self.classForCoder;
let properties = class_copyPropertyList(myClass, &count);
var dictionaryRepresentation: [String:Any] = [:]
for i in 0..<count {
let property = properties![Int(i)]
let cStringKey = property_getName(property);
let key = String(cString: cStringKey!)
dictionaryRepresentation[key] = self.value(forKey: key) as Any
}
return dictionaryRepresentation
}
I have a problem, however, with computed properties. It seems that those are computed and the returned value gets put into the dictionary as well, which I would like to avoid. So here is my question:
Is it possible to check whether is a property computed programatically using only its name?
I am assuming this could be possible by trying to assign a value to it which would give me an error or some similar approach.
Here is what seems to be a working solution, based on suggestion by dasblinkenlight.
Rather than using the Objective-C method outlined above, create a Mirror of the class which has a children made up of all settable properties, therefore excluding computables.
Used like this:
let mirror = Mirror(reflecting: MyObject)
for case let (label?, value) in mirror.children {
print (label, value)
}
Here label is the name of the variable and value is obviously the value.
EDIT: In case anyone wants to convert objects into dictionary, I am posting the full code here as well. Do however remember that if values are custom objects as well, those will need to be converted too.
func dictionary() -> [String:Any] {
let mirror = Mirror(reflecting: self)
var dictionaryRepresentation = [String:Any]()
for case let (label, value) in mirror.children {
guard let key = label else { continue }
dictionaryRepresentation[key] = value
}
return dictionaryRepresentation
}
You can try property_copyAttributeList(_:_:) function, it may contain a read-only marker for swift's computed properties. Although I guess let properties also will have that marker, so you must find a way to differ them.

Swift 3: Safe way to decode values with NSCoder?

Before Swift 3, you decode boolean values with NSCoder like this:
if let value = aDecoder.decodeObjectForKey(TestKey) as? Bool {
test = value
}
The suggested approach in Swift 3 is to use this instead:
aDecoder.decodeBool(forKey: TestKey)
But the class reference for decodeBool doesn't explain how to handle the situation if the value you're decoding isn't actually a boolean. You can't embed decodeBool in a let statement because the return value isn't an optional.
How do you safely decode values in Swift 3?
Took me a long time to figure out but you can still decode values like this.
The problem I had with swift3 is the renaming of the encoding methods:
// swift2:
coder.encodeObject(Any?, forKey:String)
coder.encodeBool(Bool, forKey:String)
// swift3:
coder.encode(Any?, forKey: String)
coder.encode(Bool, forKey: String)
So when you encode a boolean with coder.encode(boolenValue, forKey: "myBool") you have to decode it with decodeBool but when you encode it like this:
let booleanValue = true
coder.encode(booleanValue as Any, forKey: "myBool")
you can still decode it like this:
if let value = coder.decodeObject(forKey: "myBool") as? Bool {
test = value
}
This is safe (for shorter code using nil-coalescing op.) when wanted to use suggested decodeBool.
let value = aDecoder.decodeObject(forKey: TestKey) as? Bool ?? aDecoder.decodeBool(forKey: TestKey)
Using decodeBool is possible in situations when sure that it's Bool, IMO.

how should I use "?" in Swift

for example
if let name = jsonDict["name"] as AnyObject? as? String {
println("name is \(name)")
} else {
println("property was nil")
}
I have the follow question:
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as? AnyObject as? String yes or no?
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as AnyObject? as String? yes or no?
I don't know the difference between as? String and as String?
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as? AnyObject as? String yes or no? - No, the latter makes no sense, you are trying to making a double cast from AnyObject to String. Furthermore, jsonDict["name"] would be enough for the compiler to recognize what type the return is, you shouldn't need any casting to a string.
jsonDict["name"] as AnyObject? as? String is the same as jsonDict["name"] as AnyObject? as String? yes or no?. Same as the first case, making a double cast makes little sense. Furthermore, the difference between as? and as is that as? will only execute in the case that the object can be successfully converted to the desired type, if this is not the case, the object will not be casted, therefore avoiding crashes.
there is huge difference between as and as?.
as
the as downcasts the object and force-unwrap the result in every time, and it does not really care about the real type of the object or any fail-safe procedure. the responsibility is yours, therefore that can cause the following code crashes in runtime:
let jsonDict: Dictionary<String, AnyObject> = ["name": 32]
if let downcasted: String = jsonDict["name"] as String? { ... } else { ... }
the reason: it forcibly downcasts the Int32 value to an optional String (=String?), and that cause a simple crash – if force-unwrapping fails, it will cause crash in every occasion in runtime.
you can use this solution only when you are 100% certain the downcast won't fail.
therefore, if your dictionary looks like this:
let jsonDict: Dictionary<String, String?> = ["name": nil]
if let downcasted: String = jsonDict["name"] as String? { ... } else { ... }
the else-branch will be executed, because the String? is nil, when you replace the nil with any string (e.g. "My Name") in your dictionary, the if-branch will be executed in runtime as expected – if the value is any other type than optional String, a crash will happen as I highlighted above in every case when the value is not nil.
as?
the as? optionally downcast the object, and if the downcasting procedure fails then it returns nil. that solution helps you to check the success of the donwcasting in runtime without suffering any crash:
let jsonDict: Dictionary<String, AnyObject> = ["name": 32]
if let downcasted: String = jsonDict["name"] as? String { ... } else { ... }
in that situation the else-branch will executed and your application can carry on, because the actual Int32 value fails to be downcasted to String – if your value is a String (e.g. "My Name") then the if-branch will be executed in runtime.
you have to use this solution when you are not certain about which the actual object's type is in runtime and there is a chance the downcasting procedure could fail.
NOTE: I would recommend to read the official docs about type-casting and optional-chaining for better understanding the entire procedure.