Swift 4 Codable: Cannot exclude a property - ios

I'm developing a simple music sequencer app. This kind of app tends to have a complex data structure which has to be saved/loaded, so the introduction of Codable protocol in Swift4 is totally a good news for me.
My problem is this:
I have to have a non-Codable property. It doesn't have to be coded because it's a temporary variable only kept alive while the app is active.
So I've just tried to exclude by implementing CodingKey, but the compiler still give me the error "Type 'Song' does not conform to protocol 'Decodable'".
Specifically I want to exclude "musicSequence" in the code below.
class Song : Codable { //Type 'Song' does not conform to protocol 'Decodable'
var songName : String = "";
var tempo : Double = 120;
// Musical structure
var tracks : [Track] = [] // "Track" is my custom class, which conforms Codable as well
// Tones
var tones = [Int : ToneSettings] (); // ToneSettings is also my custom Codable class
var musicSequence : MusicSequence? = nil; // I get the error because of this line
private enum CodingKeys: String, CodingKey {
case songName
case tempo
case tracks
case tones
}
func createMIDISequence () {
// Create MIDI sequence based on "tracks" above
// and keep it as instance variable "musicSequence"
}
}
Does anybody have any ideas?

(See below for a strange turn of events.)
Your use of CodingKeys is already taking care of you encoding. You still get that for free. But you'll need to tell the system how to handle decoding by hand:
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
songName = try values.decode(String.self, forKey: .songName)
tempo = try values.decode(Double.self, forKey: .tempo)
tracks = try values.decode([Track].self, forKey: .tracks)
tones = try values.decode([Int: ToneSettings].self, forKey: .tones)
}
It's not quite smart enough to figure out that musicSequence can and should default to nil (and maybe that would be too smart anyway).
It's probably worth opening a defect at bugs.swift.org to ask for this Decodable to be automatic. It should be able to figure it out in cases where you provide CodingKeys and there is a default value.
EDIT: When I first answered this, I exactly duplicated your error. But when I tried to do it again, copying your code fresh, the error doesn't show up. The following code compiles and runs in a playground:
import Foundation
struct Track: Codable {}
struct ToneSettings: Codable {}
struct MusicSequence {}
class Song : Codable { //Type 'Song' does not conform to protocol 'Decodable'
var songName : String = "";
var tempo : Double = 120;
// Musical structure
var tracks : [Track] = [] // "Track" is my custom class, which conforms Codable as well
// Tones
var tones = [Int : ToneSettings] (); // ToneSettings is also my custom Codable class
var musicSequence : MusicSequence? = nil; // I get the error because of this line
private enum CodingKeys: String, CodingKey {
case songName
case tempo
case tracks
case tones
}
func createMIDISequence () {
// Create MIDI sequence based on "tracks" above
// and keep it as instance variable "musicSequence"
}
}
let song = Song()
let data = try JSONEncoder().encode(song)
String(data: data, encoding: .utf8)
let song2 = try JSONDecoder().decode(Song.self, from: data)
I'm wondering if there's a compiler bug here; make sure to test this with the new beta.

Related

Use switch cases based on condition

I have made a file called Constants.swift. Within this, I have made a class like so...
public class WebServices {
static let getMyPlants : String = "plant/getPlants"
static let getMyOrganizations: String = "organization/getOrganizations"
}
Now whenever, I use an api anywhere in my project, I do Webservices.getMyPlants.
Now I also have a base-url for each of the API's. That is mentioned below public class WebServices.... like so..
struct envDev {
var BASEURL : String = "http://api-proj-dev.ii.the-co.com/api/"
}
Now, the base-url for Webservices.getMyOrganizations is different. I want to use a condition within struct envDev that if I have selected Webservices.getMyOrganizations, then I can give a different BASEURL. Something like...
//The below code isn't right. I just wrote it to represent the kind of solution I wish to have.
struct envDev {
var BASEURL : String = "http://api-proj-dev.ii.the-co.com/api/"
if Webservices.getMyOrganizations {
var BASEURL : String = "http://my second base-url.."
}
}
EDIT 1 Giving below the signature of APIHelper
class APIHelper: NSObject {
var API: NSString
var json: NSString
var receivedJSON: NSString?
var arrResult: NSMutableArray = []
let esmatBaseUrl = AppDelegate().currentUser //This is given in AppDelegate as `var currentUser = envDev()`
()
EDIT 2 Inclusion of baseUrl computed property in APIHelper & the error.
class APIHelper: NSObject {
var API: NSString
var json: NSString
var receivedJSON: NSString?
var arrResult: NSMutableArray = []
let esmatBaseUrl = AppDelegate().currentUser //This is given in AppDelegate as `var currentUser = envDev()`
()
var baseUrl: String {
esmatBaseUrl.baseUrl(forApi: API as String) // Here I'm getting the error as `Value of type 'envDev' has no member 'baseUrl'`
}
envDev has no way of knowing what happens in APIHelper, so you need a way to pass in the API from APIHelper to envDev. This means that BASEURL should not be a property, but a method:
func baseUrl(forApi api: String) -> String {
switch api {
case WebServices.getMyPlants: return "some url"
case WebServices.getMyOrganizations: return "some other url"
default: fatalError()
}
}
Then in APIHelper, you can add a baseUrl computed property that calls the above method:
var baseUrl: String {
esmatBaseUrl.baseUrl(forApi: API as String)
}
This would mean that you need to change all occurrences of esmatBaseUrl.BASEURL in your existing code to just baseUrl.
Also, I would suggest not using NSString, NSArray, etc in Swift. You should their Swift counterparts: String and [T].
I understood your query. You want to create an ENUM for your server-environment's, instead of hard-coding baseUrl's you probably want to use ENUMS to select different environments, right.
So accordingly, I've created an ENUM for you to add different server-environments so it will be feasible for you to use it frequently every-where.
private enum ServerEnv: String {
case stage, prod, test, my_plants, my_organization
var domainValue: String {
switch self {
case .test, .my_plants: return "http://api-testing-proj-dev.ii.the-co.com/api/"
case .stage: return "http://api-staging-proj-dev.ii.the-co.com/api/"
case .prod: return "http://api-production-proj-dev.ii.the-co.com/api/"
case .my_organization: return "http://api-my_organization-proj-dev.ii.the-co.com/api/"
}
}
}
Example :
let baseUrl = ServerEnv.my_organization.domainValue
Output => baseURL = "http://api-my_organization-proj-dev.ii.the-co.com/api/"
let baseUrl = ServerEnv.my_plants.domainValue
Output => baseURL = "http://api-testing-proj-dev.ii.the-co.com/api/"
I hope, I've solved your query here.
Happy Coding :-)

Issue in making a struct Codable

I have Application protocol with 2 variables. And I have component struct that has a variable, which confirms to Application protocol. I need to save this struct in disk . So I'm confirming it to Codable protocol. While doing so I'm getting an error like this ,
"Protocol type 'Application' cannot conform to 'Decodable' because only concrete types can conform to protocols"
Here is my code,
public protocol Application {
var name : String {get}
var ownerName : String {get}
}
public struct component : Codable {
let application : Application
private enum CodingKeys: String, CodingKey {
case application
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
application = try values.decode(Application.self, forKey: .application)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(application, forKey: .application)
}
}
I'm new to swift so sorry if I'm missing something very obvious. Im not able to fix this and I need some help in right direction. Thank you in advance.
How you address this strongly depends on the problem you're solving.
If you want to store and load exactly these two keys in JSON, then Application should be a struct (as jawadAli notes).
If you mean to store more information, but a given Component is tied to exactly one type of Application, then you want Component to be generic:
public struct Component<App: Application & Codable> : Codable {
let application : App
...
}
Note the addition of & Codable. If all things that conform to Application should be Codable, then you can make that a requirement of Application:
public protocol Application: Codable {
var name : String {get}
var ownerName : String {get}
}
It is important to understand that this does not make Application conform to Codable. It means that in order to conform to Application, a type must also conform to Codable. It is never possible to decode an abstract type (i.e. a protocol).
If you mean to store more information, but a given Component doesn't actually know what kind of Application it holds, then this is a more complicated problem (and often over-complicated and should be rethought; if you find you're using a lot of as? tests, then you should almost certainly redesign). If that's your problem, you should explain a bit more what problem you're solving, and we can discuss how to solve it. (It generally requires some kind of dynamic type registration system, and a JSON format that supports metadata about types. Or you can switch to NSCoder and not use JSON.)
Use Struct that is confirming to codable instead of protocol will solve the issue
public struct Application : Codable {
var name : String
var ownerName : String
}

Set capacity to array of model in Decodable protocol

I want to set a model array max capacity if the user has not logged in even JSON response contains more records.
public class AlertsInbox: Codable {
public var messages: [Messages]?
public init(messages: [Messages]) {
self.messages = messages
if !GlobalVar.isLoggedIn,
let capacity = GlobalVar.messageCapacity {
self.messages?.reserveCapacity(capacity)
}
}
}
Decode json response:
let responseMessage = try JSONDecoder().decode(AlertsInbox.self, from: data)
Using reserveCapacity in init of model but it gets overridden with the number of records received in response.
How to restrict while decoding?
Thanks
You should not conditionally strip/drop out elements from an array in the Type's initializer. This consideration is against the responsibility role of Model object. Instead it's a job for the controller object.
But you can have an access point in the model type. Let's say, you have a property messages in your AlertsInbox object. You can have another access point with another property, let's say, limitedMessages that will be a computed property.
Look at the below code. I intentionally changed the type's semantics from Class to Struct. But you are free to use as your need.
struct AlertsInbox: Codable {
let messages: [Message]
var limitedMessages: [Message] {
// here you will use the value of your limit
// if the limit exceeds the size of the array, whole array will be returned
return Array(messages.prefix(5))
}
struct Message: Codable {
let title: String
}
}
Now you will use this, just like any other property, when you need the stripped out version of your actual messages. Like alertInbox.limitedMessages instead of the messages property.
But if you are not satisfied with the above and wanna stick to your plan, you can do that as well. See:
struct AlertsInbox: Codable {
let messages: [Message]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let messages = try container.decode([Message].self, forKey: .messages)
// the idea of limiting the element numbers is same as the above
self.messages = Array(messages.prefix(5))
}
struct Message: Codable {
let title: String
}
}

Decoding Data Using 'Codable'

I am trying to decode an array of my model objects(Catalog) from a JSON that looks like this after serialization of corresponding 'Data' object.
{ "id" : 5,
"catalogs" : [ {catalogKeyValue1},{catalogKeyValue2}]
}
My model object looks like this
struct Catalog : Codable{
var id : Int
var name : String
var categoryId : Int
var minProductPrice : Int
var maxProductDiscount : Int?
var shareText : String
var collageImage : String
var collageImageAspectRatio : Double?
var shipping : [String : Int]?
var description : String
}
I need to get an array of Catalogs (which is nested against 'catalogs' key in JSON) after decoding.I fully understand using nested containers and writing custom initialaizer for Catalog struct .How can I achieve this without having to write another Codable struct for outer JSOn that looks like this
struct CatalogArray: Codable {
var catalogs : [Catalog]
}
and then do something like this to get a decoded array of Catalogs
let catalogArray = try decoder.decode(CatalogArray.self, from: validData)
My problem is I dont have any need for this catalogArray struct. Is there a way of getting Catalog model objects decoded without having to create unnecessary nested structs.
You can do this instead of making a new Struct everytime: try container.decode([Catalog].self, forKey: "Catalogs")
Arrays of a type that is Codable are automatically Codable.
As per your comment
The problem with more Codable structs is that I will need to create another struct if the same array of catalogs comes with a different key in another API response.
You can create generic struct that can do the same for you. here is a example
struct GeneralResponse<T:Codable>: Codable {
let code: Int
let catalogs: T?
enum CodingKeys: String, CodingKey {
case code = "id"
case catalogs = "catalogs"
}
public init(from decoder:Decoder) throws {
let contaienr = try decoder.container(keyedBy: CodingKeys.self)
code = try contaienr.decode(Int.self, forKey: .code)
do {
let object = try contaienr.decodeIfPresent(T.self, forKey: .data)
catalogs = object
} catch {
catalogs = nil
}
}
}
Now
You can use this with different catalogs type of struct
like
GeneralResponse<[Catalogs]> or GeneralResponse<[CatalogsAnother]>
Hope it is helpful

Using custom model class with Backendless in Swift

I'm trying to retrieve data from an online data storage using the func that I found online on the official Backendless docs! but when I try to use persona like a Lista(my own class) Object, I get the error: Could not cast value of type '__NSDictionaryM' (0x10c1ccfc0) to 'InLIsta_.Lista' (0x108439790).
I search over this site but the answer aren't specific for the Backendless case, so I hope that anyone can help me
this is my code (obviously I've declared all the var and let necessary to the code to run):
class Lista : NSObject {
var nome: String?
var pr: String?
var pagamento = 0
var entrato: Bool = false
var commenti: String?
var objectId: String?
var created: NSDate?
var updated: NSDate?
}
func findQ() {
Types.tryblock({ () -> Void in
let startTime = NSDate()
let found = self.backendless.persistenceService.of(Lista.ofClass()).find(self.query)
let currentPage = found.getCurrentPage()
print("Loaded \(currentPage.count) name objects")
print("Total name in the Backendless storage - \(found.totalObjects)")
for person in currentPage {
let persona = person as! Lista // here i get error
print("Restaurant <\(Lista.ofClass())> name = \(persona.nome)")
self.nomi.append(persona.nome!)
}
print("Total time (ms) - \(1000*NSDate().timeIntervalSinceDate(startTime))")
},
catchblock: { (exception) -> Void in
print("Server reported an error: \(exception as! Fault)")
}
)
}
The backendless persistence service has a method -(void)mapTableToClass:(NSString *)tableName type:(Class)type; that you need to call for each of your custom classes so they'll be used during the deserialisation.
self.backendless.persistenceService.mapTableToClass("Lista", type: Lista.self)
This needs to be done before any calls are made to use the persistence service.
Note that the classes, if not defined in obj-c, must be exported to obj-c. Note that this also means you can't have any optionals.
Ideally you should use the platform code generation to create your model class definitions to ensure all of the attributes are created with the appropriate types. A failure to map to your custom class could be caused by type mismatches or name mismatches. Optionals will always fail in the current SDK implementation.

Resources