Can I use Swift's map() on Protocols? - swift

I have some model code where I have some Thoughts that i want to read and write to plists. I have the following code:
protocol Note {
var body: String { get }
var author: String { get }
var favorite: Bool { get set }
var creationDate: Date { get }
var id: UUID { get }
var plistRepresentation: [String: Any] { get }
init(plist: [String: Any])
}
struct Thought: Note {
let body: String
let author: String
var favorite: Bool
let creationDate: Date
let id: UUID
}
extension Thought {
var plistRepresentation: [String: Any] {
return [
"body": body as Any,
"author": author as Any,
"favorite": favorite as Any,
"creationDate": creationDate as Any,
"id": id.uuidString as Any
]
}
init(plist: [String: Any]) {
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = UUID(uuidString: plist["id"] as! String)!
}
}
for my data model, then down in my data write controller I have this method:
func fetchNotes() -> [Note] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: Any]] else {
return []
}
return notePlists.map(Note.init(plist:))
}
For some reason the line return notePlists.map(Note.init(plist:)) gives the error 'map' produces '[T]', not the expected contextual result type '[Note]'
However, If I replace the line with return notePlists.map(Thought.init(plist:)) I have no issues. Clearly I can't map the initializer of a protocol? Why not and what's an alternate solution?

If you expect to have multiple types conforming to Note and would like to know which type of note it is stored in your dictionary you need to add an enumeration to your protocol with all your note types.
enum NoteType {
case thought
}
add it to your protocol.
protocol Note {
var noteType: NoteType { get }
// ...
}
and add it to your Note objects:
struct Thought: Note {
let noteType: NoteType = .thought
// ...
}
This way you can read this property from your dictionary and map it accordingly.

Related

Reflection from structure type in Swift

Recently I'm developing API parts using GraphQL.
When I call API, I need to generate a query from structure like this.
// from this model
struct ModelA {
let id: String
let title: String
....
}
// to this query
query {
id
title
}
If I have an instance of ModelA, I can reflect properties from instance using Mirror.
But I don't want to make the instance in this case and I don't want to make the properties to variables because I need to use this model for response.
Additionally class_copyPropertyList is a good solution if the model is NSObject class. However in this case this is a structure in swift.
Is this possible? I appreciate your help in advance.
try this
extension Encodable {
func query() -> String? {
guard let encodeData: Data = try? JSONEncoder().encode(self) else { return nil }
guard let jsonRepresentation: [String: Any] = try? JSONSerialization.jsonObject(with: encodeData, options: []) as? [String: Any] else { return nil }
let keys: String = jsonRepresentation.map{ $0.key }.joined(separator: " ")
return "query { " + keys + " }"
}
}
struct ModelA: Encodable {
let id: String
let title: String
}
let model = ModelA(id: "abc123", title: "Model title")
if let query = model.query() {
print(query)
}
EDIT
// query function in Encodable extension stays the same
protocol Querable: Encodable {
static var dummy: Encodable { get }
}
extension Querable {
static var query: String? {
return self.dummy.query()
}
}
struct ModelA: Querable {
let id: String
let title: String
static var dummy: Encodable {
return ModelA(id: "", title: "")
}
}
if let query = ModelA.query {
print(query)
}
Unfortunately you're not going to be able to do that with structs. You can, however, do something like this.
protocol Queryable {
static var queryableProperties: [String] { get }
}
extension Queryable {
static func makeQuery() -> String {
return "query {\n"
+ queryableProperties
.map { " \($0)" }
.joined(separator: "\n")
+ "\n}"
}
}
struct Dog {
let name: String
let age: Int
}
extension Dog: Queryable {
static var queryableProperties: [String] {
return ["name", "age"]
}
}
struct Person {
let firstName: String
let lastName: String
}
extension Person: Queryable {
static var queryableProperties: [String] {
return ["firstName", "lastName"]
}
}
print(Person.makeQuery())
print(Dog.makeQuery())
Which prints:
query {
firstName
lastName
}
query {
name
age
}

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

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

UTF-8 encoding issue of JSONSerialization

I was trying convert struct to Dictionary in Swift. This was my code:
extension Encodable {
var dictionary: [String: Any]? {
if let data = try? JSONEncoder().encode(self) {
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
return dict
}
return nil
}
return nil
}
}
This works in most situation. But when I try to convert a nested structure which contains unicode characters such as Chinese, this happened:
struct PersonModel: Codable {
var job: String?
var contacts: [ContactSimpleModel]
var manager: ManagerSimpleModel?
}
struct ContactSimpleModel: Codable {
var relation: String
var name: String
}
struct ManagerSimpleModel: Codable {
var name: String
var age: Int
}
let contact1 = ContactSimpleModel(relation: "朋友", name: "宙斯")
let contact2 = ContactSimpleModel(relation: "同学", name: "奥丁")
let manager = ManagerSimpleModel(name: "拉斐尔", age: 31)
let job = "火枪手"
let person = PersonModel(job: job, contacts: [contact1, contact2], manager: manager)
if let dict = person.dictionary {
print(dict)
}
The result of this code is this:
["contacts": <__NSArrayI 0x600002471980>(
{
name = "\U5b99\U65af";
relation = "\U670b\U53cb";
},
{
name = "\U5965\U4e01";
relation = "\U540c\U5b66";
}
)
, "manager": {
age = 31;
name = "\U62c9\U6590\U5c14";
}, "job": 火枪手]
You can see the result. The Chinese characters in those nested structures were become a utf-8 encoding string. The top-level property "job": 火枪手 is right. But the values in those nested structures were not the original string.
Is this a bug of JSONSerialization? Or how to make it right?
More information. I used the result like this:
var sortedQuery = ""
if let dict = person.dictionary {
sortedQuery = dict.sorted(by: {$0.0 < $1.0})
.map({ "\($0)\($1)" })
.joined(separator: "")
}
It was used to check whether the query was legal. The result is not the same as Java or other platform.
The result is perfectly fine. That's the internal string representation – a pre-Unicode legacy – of an array or dictionary when you print it.
Assign the values to a label or text view and you will see the expected characters.

Make Dictionary conform to value type protocol

This is simplified code that doesn't compile. Is there anyway to make this work in Swift? Thanks.
protocol Person {
var name:String { get }
var age:Int { get }
}
extension Dictionary : Person {
var name: String {
return self["name"] as String
}
var age: Int {
return self["Int"] as Int
}
}
Let me give some context to why I would want to do this.
Lets say I have some person data coming in over the wire as json. As I pass it through JSONSerialization I get a [String:AnyObject] Dictionary back.
So I would like to declare the JSON data interfaces in protocols, make the dictionary objects conform to the protocols and then extract the values from the dictionaries via typed properties, rather then via magic strings and casts. This way the client code would only know about protocol types even though they are implemented as dictionaries behind the curtain.
Not sure it's doable or a good idea, just wanted to try it. But compiler is giving me all sorts of trouble.
I understand you want to encapsulate the logic to link a json to it's model representation.
The suggested solution
First of all I am suggesting another way to achieve what you are looking for instead
Look at this Struct.
struct Person {
let name: String
let age: Int
init?(json: [String:Any]) {
guard let name = json["name"] as? String, age = json["age"] as? Int else { return nil }
self.name = name
self.age = age
}
}
The logic to extract data from the json is encapsulated into its initializer. And if the provided json is not valid the initialization fails. It's safe because it will never crash and it's easy to use.
The direct answer (don't do this at home!)
protocol Person {
var name: String? { get }
var age: Int? { get }
}
extension Dictionary : Person {
private var dictWithStringKeys: [String:Any] {
return reduce([String:Any]()) { (dict, elm) -> [String:Any] in
var dict = dict
if let key = elm.0 as? String {
dict[key] = elm.1
}
return dict
}
}
var name: String? {
return dictWithStringKeys["name"] as? String
}
var age: Int? {
return dictWithStringKeys["age"] as? Int
}
}