Is it possible to save Swift 4 smart keypaths to UserDefaults - swift4

I am trying to save a configuration containing keypaths to UserDefaults. I would like to avoid writing a static serializing mechanism.
I am been thinking to use the Codable protocol but KeyPath does not conform to it.
I tried using NSCoding/Data mechanism, but no success.
Is there a way to achieve this?
Thanks!

Related

How to turn Any into Data

I want to save Any? in Swift (to FileManager).
It could be everything, also types that don't conform to Codable (like SCNNodes).
I would like to wrap it into Data. I know, I can't do it with a PropertyListEncoder.
Could you make it easy for me to understand how it works?
Could you make it easy for me to understand how it works?
It doesn't. The whole meaning of Any is that it could be anything. But not just anything can be turned into a Data. That is why protocols like Codable and NSCoding (to which SCNNode conforms) exist — and why Any cannot conform to either of them. These protocols pick out the types that can be turned into a Data.

Does conforming to Codable give me Core Data value transformation for free like conforming to NSCoding did?

I am not very experienced with recent Core Data so please bear with me if I am missing important points here.
When conforming to NSCoding an NSObject didn't need to have it's own implementation of NSValueTransformer / ValueTransformer. You would describe it as Transformable in the Xcode model editor without defining a Value Transformer and the object would still persist as you would expect with the help of a NSValueTransfomer.
Now, Codable entered stage to replace NSCoding. The question is: does it already or will it give me the same convenience NSCoding gave me by not needing to implement a custom ValueTransformer?
Core Data doesn't know anything about Codable, so conforming to Codable makes no difference to how Core Data works. It would be really nice if what you described worked, and I suggest you file an enhancement request with Apple about it. I've already done so (rdar://37708071 in case anyone from Apple reads this), and the more people who ask, the more likely they'll add this.
For now, your options are:
Keep using NSCoding and a transformable attribute.
Use Codable but then convert to/from Data yourself outside of Core Data, and use a binary attribute.

Swift: How to conform to protocol with associated types if I have two types to associate?

OK weird question perhaps, and it's only because I don't really know what I'm asking for so I'll try to describe it best I can. Please direct me appropriately if this has been asked before.
So I'm using the awesome Codable protocol with pretty much all my models and I'm making a class that handles some storing, let's call it Storage<Model: Codable> which has a generic type conforming to Codable because one instance of this class will handle storage for one type of model.
Now I need to be notified when things change in the Storage instance, like stuff getting written to disk and deletions. So I make a new protocol StorageListener that declares functions like func storage(_ storage: Storage<CodableType>, didRemoveModelForKey key: String). Now since this uses the Storage type which requires use of a generic Model an associated type must be declared: associatedtype CodableType: Codable.
So I now use type erasure to make an AnyStorageListener<AnyCodableType: Codable>: StorageListener, that I can store in an array in my Storage class.
Now this is fine, I can just conform my ViewModel or whatever to StorageListener and declare the typealias CodableType = MyModel but what if I need my ViewModel to listen to two Storages of different types?
What I've come up with is using listener container objects that I can initialize with closures to the protocol functions and thus work around the problem. That should work but I was wondering if there's a cleaner solution? Perhaps some way to type erasure away the generic requirement altogether?

Using a Swift struct in an NSOutlineView

I'm trying to populate an NSOutlineView with Swift structs as its items. This works, except for the row(forItem:) method, which always returns -1.
I've made sure my struct conforms to Equatable and I implemented an isEqual function. Still the NSOutlineView can not find the row for any item.
If I convert my struct to a class (not derived from NSObject), it all works. Even without conforming to Equatable or implementing any isEqual functions.
I thought classes and structs in Swift are basically the same except one being a reference type and the other a value type.
What do I need to implement to use Swift structs as items in an NSOutlineView?
Release notes for macOS 10.14 point that if a Swift value type conforms to both Equatable and Hashable protocols, the NSOutlineView will successfully work with that value type.
But if you build against an older SDK, the value types won't work with the NSOutlineView indeed.
Someone posted a similar question on the Apple Developer Forum.
NSOutlineView doesn't use Equatable when identifying an item, it uses the reference pointer to the item. In Swift, structs are always passed by value (and not passed by reference) so the outline view will never recognize that the item you're passing is the same. Switching your struct to a class fixes the issue because Swift uses pass by reference for classes.
Unfortunately, there's no way to use Structs as items to work with NSOutlineView and you will need to use a Class.
Read this answer for more details about how Swift uses pass by reference vs. pass by value.
You may override the method row(forItem item: Any?) in your subclass by simply looking all rows and returning the index of the equal ones.

Swift enum and NSCoding

I have a 'Thing' object with a String property and an NSImage property; the Thing class has encodeWithCoder: and decodeWithCoder: methods, and I can archive and unarchive a [Thing] array using NSKeyedArchiver/Unarchiver.
So far, so good. Now I want to expand my Thing class by an array of directions, where 'Direction' is the following enum:
enum Direction{
case North(direction:String)
case East(direction:String)
case South(direction:String)
case West(direction:String)
}
In other words, the data I wish to store is
thing1.directions: Direction = [.North("thing2"), .South("thing3")]
(In a more perfect world, I'd be using direct references to my Things rather than just their names, but I realise that this will easily create reference cycles - can't set a reference to another Thing until that Thing has been created - so I'll refrain. I'm looking for a quick and dirty method to save my app data and move on.)
Since I will be needing directions elsewhere, this is a separate entity, not just an enum inside the Thing class. (Not sure whether that makes a difference.)
What is the best way to make my Direction enum conform to NSCoding?
The best workaround I can come up with involves creating a [String: String] dictionary with "North" and "South" as keys and "thing2" and "thing3" as values, and reconstruct my enum property from that, but is there a better way?
And for that matter, is there a way to make tuples conform to NSCoding because right now (String, String) gets me a 'not compatible to protocol "AnyObject"' error.
Many thanks.
What I do is give the enum a type and encode and decode its raw value, or else implement description for the enum and encode and decode that string. Either way (or if you use some other way), you obviously need a way to convert in both directions between an enumerator and an archivable type.
Yes you need to access the enum from the RAW value. Full example and discussion here:
How do I encode enum using NSCoder in swift?
Note this change in Xcode 6.1 " move code from the old-style “fromRaw()/toRaw()” enum APIs to the new style-initializer and “rawValue” property"
https://developer.apple.com/library/ios/releasenotes/DeveloperTools/RN-Xcode/Chapters/Introduction.html