Property Wrapped Value if accessed from other files give non optional type - swift5

struct HarkAppPreference {
#UserPreferenceField(key: .userSelectedTags)
static var userSelectedTags: [MLQTag]
#UserPreferenceField(key: .userLastActivePlalist)
static var userLastActivePlaylist: [PlayableEntity]
#UserPreferenceField(key: .userLastActivePlayableEntityMeta)
static var userLastActivePlayableEntityMeta: LastPlayableEntityMeta
#UserPreferenceField(key: .currentUSer)
static var user: User
}
enum UserPreferenceFieldKey : String {
case userSelectedTags = "MLQUserSelectedTags"
case userLastActivePlalist = "MLQUserLastActivePlalist"
case userLastActivePlayableEntityMeta = "MLQUserLastActivePlayableEntityMeta"
case currentUSer
}
struct User : Codable {
let name : String
let phone: String
}
#propertyWrapper
struct UserPreferenceField<T : Codable> {
let key: UserPreferenceFieldKey
var wrappedValue : T? {
get {
guard let decodedData = UserDefaults.standard.value(forKey: key.rawValue) as? Data else { return nil }
return try? JSONDecoder().decode(T.self, from: decodedData)
}
set {
do {
let encodedValue = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encodedValue, forKey: key.rawValue)
} catch {
print("Error in saving codable value - \(error.localizedDescription)")
}
}
}
init(key:UserPreferenceFieldKey) {
self.key = key
}
}
This is my PropertyWrapper which I created to use for saving data to UserDefault, this worked to my expectations to use the value with optional chaining if used from within the file where I have written the PropertyWrapper code.
But this given an error Initializer for conditional binding must have Optional type, not 'User' when used in another file like below
class ABC {
func sss(){
if let _ = HarkAppPreference.user { // here is gives error
}
}
}
But if the same is used in the same file it works fine with optional chaining.

As per the reply from Apple Developers Team on Dev forum, It's not happening in Xcode 11.3 and worked fine when added optional to declared property.

Related

How to seamlessly wrap #Published variables with a UserDefaults propertyWrapper

I've been looking for the perfect UserDefaults wrapper that
seamlessly encodes and decodes Data objects from storage
works with #Published
My starting point was this StackOverflow answer, but it uses object:forKey which doesn't work with custom objects (encoding URLs is always non-trivial for me).
My idea is to be able to use it like so:
struct Server: Identifiable, Codable, Equatable, Hashable { /* vars */ }
class ServerPickerViewModel: ObservableObject {
#Published(wrappedValue: Server.defaultServers.first!,
type: Server.self,
key: "currentServer")
var currentServer: Server?
}
To achieve this, I modified the code from #VictorKushnerov's answer:
import Combine
private var cancellables = [String: AnyCancellable]()
extension Published {
init<T: Encodable & Decodable>(wrappedValue defaultValue: T, type: T.Type, key: String) {
let decoder = JSONDecoder()
var value: T
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(T.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value) // <-- Error
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
let encodedVal = encoder.encode(val) // <-- Error
UserDefaults.standard.set(encodedVal, forKey: key)
}
}
}
There are currently two errors I can't get through, which are the following:
Cannot convert value of type 'T' to expected argument type 'Value' it still relying on the underlying #Published's Value generic type, I wish I could override that with my type T.
Instance method 'encode' requires that 'Published<Value>.Publisher.Output' (aka 'Value') conform to 'Encodable'
You can fix your compilation errors by using where Value : Codable to restrict your extension. Then, you can get rid of your T generic altogether (& you don't have to use the type argument either):
extension Published where Value : Codable {
init(wrappedValue defaultValue: Value, key: String) {
let decoder = JSONDecoder()
var value: Value
if
let data = UserDefaults.standard.data(forKey: key),
let decodedVal = try? decoder.decode(Value.self, from: data) {
value = decodedVal
} else {
value = defaultValue
}
self.init(initialValue: value)
cancellables[key] = projectedValue.sink { val in
let encoder = JSONEncoder()
do {
let encodedVal = try encoder.encode(val)
UserDefaults.standard.set(encodedVal, forKey: key)
} catch {
print(error)
assertionFailure(error.localizedDescription)
}
}
}
}
This being said, I'd probably take the path instead of creating a custom property wrapper instead that wraps #AppStorage instead of extending #Published

Custom intialiser on Primitive types for JSONDecoder

How do I customise the behaviour of JSONDecoder for primitive types like Int, Bool?
Here is the problem:
Backend cannot be relied upon for types. Eg: Bool can come as true/false or "true"/"false"(bool can come wrapped in double quotes)
We have at least 300 Codable structs having average 15 properties in them and writing decoding logic is cumbersome. Also the logic remains more or less same hence the code has become repetitive
Hence I am looking for a solution such that if there is a Type mismatch primitive types should be able to handle it and if not then it should be set to nil provided that type is Optional.
I tried multiple approaches for this
1. Having Wrapper on all the primitive types and handling the decoding logic. Below is an example of wrapper on Bool
struct Bool1: Codable{
private var bool: Bool?
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let b = try? container.decode(Bool.self) {
self.bool = b
} else if let string = try? container.decode(String.self) {
if string.lowercased() == "true" {
self.bool = true
} else if string.lowercased() == "false" {
self.bool = false
} else {
throw Error()
}
}
}
}
but this created unnecessary confusion among fellow developers as Wrapped types do not come out as naturally as the Native ones. Also the value cannot be accessed directly (it always need xyz.bool) to extract the raw value
2. Create a new protocol inherited from Decodable and subclass JSONDecoder
protocol KKDecodable: Decodable {
init(decoder1: Decoder)
}
extension Bool: KKDecodable {
init(decoder1: Decoder) {
// Logic for creating Bool from different types
}
}
class JSONDecoder1: JSONDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : KKDecodable {
// Some code that would invoke `init(decoder1: Decoder)`
// which is defined in `KKDecodable`
}
}
I was not able to write working code using this method
Property Wrapper
You can use property wrapper. Imagine this as an example:
#propertyWrapper
struct SomeKindOfBool: Decodable {
var wrappedValue: Bool?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let stringifiedValue = try? container.decode(String.self) {
switch stringifiedValue.lowercased() {
case "false": wrappedValue = false
case "true": wrappedValue = true
default: wrappedValue = nil
}
} else {
wrappedValue = try? container.decode(Bool.self)
}
}
}
Usage
struct MyType: Decodable {
#SomeKindOfBool var someKey: Bool?
}
You can use someKey value like a normal Bool now:
Test:
let jsonData = """
[
{ "someKey": "something else" },
{ "someKey": "true" },
{ "someKey": true }
]
""".data(using: .utf8)!
let decodedJSON = try! JSONDecoder().decode([MyType].self, from: jsonData)
for decodedType in decodedJSON {
print(decodedType.someKey)
}
Result:
nil
Optional(true)
Optional(true)
You can do similar for other situations and also any other type you need. Also note that I have changed the code to match your needs, but you can use the more compatible version that I posted as a gist here in GitHub

How to get the parameter name in the enum?

My code is like this:
enum API {
case login(phone:String, password:String, deviceID:String)
}
extension API:TargetType {
var task: Task {
switch self {
case let .login(phone, password, deviceID):
///How to get the parameter name here?
///For example:"phone", "password", "deviceID"
///Can this be generated automatically?
let parameters =
["phone":phone,
"password:":password,
"deviceID":deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
}
}
How to get the parameter name in Switch case?
For example:"phone", "password", "deviceID"
Can this be generated automatically?
How to avoid writing "phone" and the other dictionary keys literally, and make the compiler generate them from the associated value labels.
Maybe after the completion is like this
func parameters(_ api:API) -> [String, Any] {
}
switch self {
case .login:
return .requestParameters(parameters(self), encoding: JSONEncoding.default);
}
It seems that it is impossible to complete temporarily.
Who is the hero?
You can assign all associated values of the enum case to a single variable and then access the separate values using their labels.
enum API {
case login(phone:String, password:String, deviceID:String)
}
extension API:TargetType {
var task: Task {
switch self {
case let .token(params)
let parameters =
["phone":params.phone,
"password:":params.password,
"deviceID":params.deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
}
}
Btw shouldn't that .token be .login? There's no .token case in your API enum defined.
If you want to generate the Dictionary keys to match the String representation of the associated value labels, that cannot be done automatically, but as a workaround, you can define another enum with a String raw value and use that for the Dictionary keys.
enum API {
case login(phone:String, password:String, deviceID:String)
enum ParameterNames: String {
case phone, password, deviceID
}
}
extension API:TargetType {
var task: Task {
switch self {
case let .token(params)
let parameters =
["\(API.ParameterNames.phone)" : params.phone,
"\(API.ParameterNames.phone)" : params.password,
"\(API.ParameterNames.deviceID)" : params.deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
}
}
There is code where you could get the label plus all the value(s) of an enum.
public extension Enum {
public var associated: (label: String, value: Any?, values: Dictionary<String,Any>?) {
get {
let mirror = Mirror(reflecting: self)
if mirror.displayStyle == .enum {
if let associated = mirror.children.first {
let values = Mirror(reflecting: associated.value).children
var dict = Dictionary<String,Any>()
for i in values {
dict[i.label ?? ""] = i.value
}
return (associated.label!, associated.value, dict)
}
print("WARNING: Enum option of \(self) does not have an associated value")
return ("\(self)", nil, nil)
}
print("WARNING: You can only extend an enum with the EnumExtension")
return ("\(self)", nil, nil)
}
}
}
You will then be able to get the .associated.label and .associated.value of your enum. In your case your .value will be a tupple. Then you would need to use the .associated.values. Unfortunately you won't get the field names for these values. Because it's a tupple you will get field names like .0, .1 and .2. As far as I know there is no way to get the actual field names.
So in your case your code will be something like this:
enum API {
case login(phone:String, password:String, deviceID:String)
}
extension API:TargetType {
var task: Task {
return .requestParameters(self.associated.values, encoding: JSONEncoding.default);
}
}
But then you still need some functionality for going from the self.associated.values where the keys are .0, .1 and .2 to the names you like. I think the only option is for you to do this mapping yourself. You could extend your enum with a function for that.
If you want to see some more enum helpers, then have a look at Stuff/Enum
Your switch should look like this:
switch self {
case .login(let phone, let password, let deviceID)
let parameters =
["phone":phone,
"password:":password,
"deviceID":deviceID]
return .requestParameters(parameters, encoding: JSONEncoding.default);
}
Swift automatically generates the declared variables for you
You can take a look about Reflection in Swift
And you can make it automatic generate parameter like this:
class ParameterAble {
func getParameters() -> [String: Any] {
var param = [String: Any]()
let childen = Mirror(reflecting: self).children
for item in childen {
guard let key = item.label else {
continue
}
param[key] = item.value
}
return param
}
}
class LoginData: ParameterAble {
var phone: String
var password: String
var deviceID: String
init(phone: String, password: String, deviceID: String) {
self.phone = phone
self.password = password
self.deviceID = deviceID
}
}
enum API {
case login(data: LoginData)
}
extension API {
var task: [String: Any] {
switch self {
case let .login(data):
return data.getParameters()
}
}
}
let loginData = LoginData(phone: "fooPhone", password: "fooPass", deviceID:
"fooId")
let login = API.login(data: loginData)
print(login.task)
This is output: ["phone": "fooPhone", "deviceID": "fooId", "password": "fooPass"]
You can try it in Playground

Converting a rawValue into a string in Swift

Apple has made changes from Swift 3 to 4. When I run the following code:
let metadata = [ PDFDocumentAttribute.titleAttribute,
PDFDocumentAttribute.authorAttribute,
PDFDocumentAttribute.subjectAttribute,
PDFDocumentAttribute.creatorAttribute,
PDFDocumentAttribute.producerAttribute,
PDFDocumentAttribute.creationDateAttribute,
PDFDocumentAttribute.modificationDateAttribute,
PDFDocumentAttribute.keywordsAttribute ]
if var attributes = pdfDoc.documentAttributes {
for (index, value) in metadata.enumerated() {
if attributes[value] != nil {
print("\(metadata[index])): \(String(describing: attributes[value]!))")
} else {
print("\(metadata[index]): nil")
}
}
I now get: PDFDocumentAttribute(_rawValue: Title) instead of "Title", which I got before as the value of metadata[index].
How do I get rid of the rawValue stuff?
The PDFDocumentAttribute type has a property called rawValue that contains the old string value. So you can say
print("\(metadata[index].rawValue): \(String(describing: attributes[value]!))")
As an aside, instead of force-unwrapping the attribute you can use an if let, as in
if let attr = attributes[value] {
print("\(metadata[index].rawValue): \(attr)")
} else {
print("\(metadata[index].rawValue): nil")
}
If you add this extension:
extension PDFDocumentAttribute: CustomStringConvertible {
public var description: String {
return self.rawValue
}
}
Now you can just do:
// Forcing the downcast has little risk here
// but you may want to use `as?` and test for the optional instead
let attributes = pdfDoc.documentAttributes as! [PDFDocumentAttribute:Any]
for meta in metadata {
print("\(meta): \(attributes[meta] ?? "nil")")
}
Note that you can also do:
for attribute in attributes {
print("\(attribute.key): \(attribute.value)")
}
Which will just print out the attributes that exist on the document.

Using reflection to set object properties without using setValue forKey

In Swift it's not possible use .setValue(..., forKey: ...)
nullable type fields like Int?
properties that have an enum as it's type
an Array of nullable objects like [MyObject?]
There is one workaround for this and that is by overriding the setValue forUndefinedKey method in the object itself.
Since I'm writing a general object mapper based on reflection. See EVReflection I would like to minimize this kind of manual mapping as much as possible.
Is there an other way to set those properties automatically?
The workaround can be found in a unit test in my library here
This is the code:
class WorkaroundsTests: XCTestCase {
func testWorkarounds() {
let json:String = "{\"nullableType\": 1,\"status\": 0, \"list\": [ {\"nullableType\": 2}, {\"nullableType\": 3}] }"
let status = Testobject(json: json)
XCTAssertTrue(status.nullableType == 1, "the nullableType should be 1")
XCTAssertTrue(status.status == .NotOK, "the status should be NotOK")
XCTAssertTrue(status.list.count == 2, "the list should have 2 items")
if status.list.count == 2 {
XCTAssertTrue(status.list[0]?.nullableType == 2, "the first item in the list should have nullableType 2")
XCTAssertTrue(status.list[1]?.nullableType == 3, "the second item in the list should have nullableType 3")
}
}
}
class Testobject: EVObject {
enum StatusType: Int {
case NotOK = 0
case OK
}
var nullableType: Int?
var status: StatusType = .OK
var list: [Testobject?] = []
override func setValue(value: AnyObject!, forUndefinedKey key: String) {
switch key {
case "nullableType":
nullableType = value as? Int
case "status":
if let rawValue = value as? Int {
status = StatusType(rawValue: rawValue)!
}
case "list":
if let list = value as? NSArray {
self.list = []
for item in list {
self.list.append(item as? Testobject)
}
}
default:
NSLog("---> setValue for key '\(key)' should be handled.")
}
}
}
I found a way around this when I was looking to solve a similar problem - that KVO can't set the value of a pure Swift protocol field. The protocol has to be marked #objc, which caused too much pain in my code base.
The workaround is to look up the Ivar using the objective C runtime, get the field offset, and set the value using a pointer.
This code works in a playground in Swift 2.2:
import Foundation
class MyClass
{
var myInt: Int?
}
let instance = MyClass()
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = 42
assert(instance.myInt == 42)
Notes:
This is probably pretty fragile, you really shouldn't use this.
But maybe it could live in a thoroughly tested and updated reflection library until Swift gets a proper reflection API.
It's not that far away from what Mirror does internally, see the code in Reflection.mm, around here: https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
The same technique applies to the other types that KVO rejects, but you need to be careful to use the right UnsafeMutablePointer type. Particularly with protocol vars, which are 40 or 16 bytes, unlike a simple class optional which is 8 bytes (64 bit). See Mike Ash on the topic of Swift memory layout: https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html
Edit: There is now a framework called Runtime at https://github.com/wickwirew/Runtime which provides a pure Swift model of the Swift 4+ memory layout, allowing it to safely calculate the equivalent of ivar_getOffset without invoking the Obj C runtime. This allows setting properties like this:
let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)
This is probably a good way forward until the equivalent capability becomes part of Swift itself.
Swift 5
To set and get properties values with pure swift types you can use internal ReflectionMirror.swift approach with shared functions:
swift_reflectionMirror_recursiveCount
swift_reflectionMirror_recursiveChildMetadata
swift_reflectionMirror_recursiveChildOffset
The idea is to gain info about an each property of an object and then set a value to a needed one by its pointer offset.
There is example code with KeyValueCoding protocol for Swift that implements setValue(_ value: Any?, forKey key: String) method:
typealias NameFreeFunc = #convention(c) (UnsafePointer<CChar>?) -> Void
struct FieldReflectionMetadata {
let name: UnsafePointer<CChar>? = nil
let freeFunc: NameFreeFunc? = nil
let isStrong: Bool = false
let isVar: Bool = false
}
#_silgen_name("swift_reflectionMirror_recursiveCount")
fileprivate func swift_reflectionMirror_recursiveCount(_: Any.Type) -> Int
#_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
fileprivate func swift_reflectionMirror_recursiveChildMetadata(
_: Any.Type
, index: Int
, fieldMetadata: UnsafeMutablePointer<FieldReflectionMetadata>
) -> Any.Type
#_silgen_name("swift_reflectionMirror_recursiveChildOffset")
fileprivate func swift_reflectionMirror_recursiveChildOffset(_: Any.Type, index: Int) -> Int
protocol Accessors {}
extension Accessors {
static func set(value: Any?, pointer: UnsafeMutableRawPointer) {
if let value = value as? Self {
pointer.assumingMemoryBound(to: self).pointee = value
}
}
}
struct ProtocolTypeContainer {
let type: Any.Type
let witnessTable = 0
var accessors: Accessors.Type {
unsafeBitCast(self, to: Accessors.Type.self)
}
}
protocol KeyValueCoding {
}
extension KeyValueCoding {
private mutating func withPointer<Result>(displayStyle: Mirror.DisplayStyle, _ body: (UnsafeMutableRawPointer) throws -> Result) throws -> Result {
switch displayStyle {
case .struct:
return try withUnsafePointer(to: &self) {
let pointer = UnsafeMutableRawPointer(mutating: $0)
return try body(pointer)
}
case .class:
return try withUnsafePointer(to: &self) {
try $0.withMemoryRebound(to: UnsafeMutableRawPointer.self, capacity: 1) {
try body($0.pointee)
}
}
default:
fatalError("Unsupported type")
}
}
public mutating func setValue(_ value: Any?, forKey key: String) {
let mirror = Mirror(reflecting: self)
guard let displayStyle = mirror.displayStyle
, displayStyle == .class || displayStyle == .struct
else {
return
}
let type = type(of: self)
let count = swift_reflectionMirror_recursiveCount(type)
for i in 0..<count {
var field = FieldReflectionMetadata()
let childType = swift_reflectionMirror_recursiveChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
guard let name = field.name.flatMap({ String(validatingUTF8: $0) }),
name == key
else {
continue
}
let clildOffset = swift_reflectionMirror_recursiveChildOffset(type, index: i)
try? withPointer(displayStyle: displayStyle) { pointer in
let valuePointer = pointer.advanced(by: clildOffset)
let container = ProtocolTypeContainer(type: childType)
container.accessors.set(value: value, pointer: valuePointer)
}
break
}
}
}
This approach works with both class and struct and supports optional, enum and inherited(for classes) properties:
// Class
enum UserType {
case admin
case guest
case none
}
class User: KeyValueCoding {
let id = 0
let name = "John"
let birthday: Date? = nil
let type: UserType = .none
}
var user = User()
user.setValue(12345, forKey: "id")
user.setValue("Bob", forKey: "name")
user.setValue(Date(), forKey: "birthday")
user.setValue(UserType.admin, forKey: "type")
print(user.id, user.name, user.birthday!, user.type)
// Outputs: 12345 Bob 2022-04-22 10:41:10 +0000 admin
// Struct
struct Book: KeyValueCoding {
let id = 0
let title = "Swift"
let info: String? = nil
}
var book = Book()
book.setValue(56789, forKey: "id")
book.setValue("ObjC", forKey: "title")
book.setValue("Development", forKey: "info")
print(book.id, book.title, book.info!)
// Outputs: 56789 ObjC Development
if you are afraid to use #_silgen_name for shared functions you can access to it dynamically with dlsym e.g.: dlsym(RTLD_DEFAULT, "swift_reflectionMirror_recursiveCount") etc.
UPDATE
There is a swift package (https://github.com/ikhvorost/KeyValueCoding) with full implementation of KeyValueCoding protocol for pure Swift and it supports: get/set values to any property by a key, subscript, get a metadata type, list of properties and more.
Unfortunately, this is impossible to do in Swift.
KVC is an Objective-C thing. Pure Swift optionals (combination of Int and Optional) do not work with KVC. The best thing to do with Int? would be to replace with NSNumber? and KVC will work. This is because NSNumber is still an Objective-C class. This is a sad limitation of the type system.
For your enums though, there is still hope. This will not, however, reduce the amount of coding that you would have to do, but it is much cleaner and at its best, mimics the KVC.
Create a protocol called Settable
protocol Settable {
mutating func setValue(value:String)
}
Have your enum confirm to the protocol
enum Types : Settable {
case FirstType, SecondType, ThirdType
mutating func setValue(value: String) {
if value == ".FirstType" {
self = .FirstType
} else if value == ".SecondType" {
self = .SecondType
} else if value == ".ThirdType" {
self = .ThirdType
} else {
fatalError("The value \(value) is not settable to this enum")
}
}
}
Create a method: setEnumValue(value:value, forKey key:Any)
setEnumValue(value:String forKey key:Any) {
if key == "types" {
self.types.setValue(value)
} else {
fatalError("No variable found with name \(key)")
}
}
You can now call self.setEnumValue(".FirstType",forKey:"types")