Issue in making a struct Codable - ios

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
}

Related

Reference to generic type 'Resource' requires arguments in <…>

I have a struct as below which is confirming a protocol as below
struct ResourceItems<T: Decodable>: MyProtocol {
}
protocol MyProtocol: Decodable {
}
Now I am creating another protocol & want to access ‘ResourceItems’ in that protocol as below
protocol ObjectConverterProtocol{
var response : ResourceItems {get set}
}
But getting the error as ‘
Reference to generic type 'ResourceItems' requires arguments in <…>
’
Please guide me what I have to pass as a generic parameter. I tried with AnyObject & Any but both of them does not confirm Codable.
I can’t get how to fix this issue. Please guide.
Since ResourceItems is a generic type you need to use it like that also in your protocol. For this you can use an associated type
protocol ObjectConverterProtocol{
associatedtype ResourceType: Decodable
var response : ResourceItems<ResourceType> {get set}
}
and then you can use your protocol
struct Converter: ObjectConverterProtocol {
typealias ResourceType = String
var response: ResourceItems<ResourceType>
//or var response: ResourceItems<String>
}
or even make Converter generic
struct Converter<C: Decodable>: ObjectConverterProtocol {
typealias ResourceType = C
var response: ResourceItems<ResourceType>
}
let converter = Converter(response: ResourceItems<String>())
Since ResourceItems is a generic type, you need to specify the type of generic parameter explicitly.
Now, since the generic parameter must be Decodable, you must define the Decodable type with ResourceItems in angular brackets <...>.
Let's assume, struct DecodableObject is of a Decodable type, i.e.
struct DecodableObject: Decodable {
//....
}
So, you can now define response of type ResourceItems like so,
protocol ObjectConverterProtocol {
var response : ResourceItems<DecodableObject> {get set}
}

MultipeerConnectivity MCPeerID does not conform to Codable

I'm writing a simple P2P chat program and thus far I've been storing MCPeerID's displayName property as a string in my model to determine who to send a particular message to. On each send operation, I search through the connectedPeers array and copy the MCPeerID into a receiver list whenever the displayName matches the string I have in my model.
This can be problematic in the case where two peers have the same name. And I'm also not satisfied with having perform a search for each send. So I'm trying to use MCPeerIDs directly in my model. However, Xcode complains that MCPeerID does not conform to Encodable nor Decodable, and I'm not sure how to fix this.
My model represents a topic that maintains a list of participants as well as a log of who said what. Thus, I can sync a new participant when they join and update existing participants when new messages are added. My model looks something like the following:
import Foundation
import MultipeerConnectivity
class Task : Codable {
var uuidStr: String = UUID().uuidString
var topic : String
var history : [String] = []
var users : [MCPeerID] = []
...
private enum CodingKeys: String, CodingKey {
case uuidStr
case topic
case history
case users
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(uuidStr, forKey: .uuidStr)
try container.encode(topic, forKey: .topic)
try container.encode(history, forKey: .history)
try container.encode(users, forKey: .users)
}
...
}
(I haven't shown the standard init() as this is not where the problem lies.)
After looking at the documentation, I see a method with the signature MCPeerID.init?(coder: NSCoder) and func encode(with: NSCoder) but I'm not sure how to specify the NSCoder parameter. Any help on how to serialise the users property would be much appreciated.
There seem to be a few issues here (p2p is a complex subject):
1. identifying users (business level),
The peer name MCPeerID(displayName: ) is not the peer identifier, it's just aimed at the human viewer (e.g. chatroom users). Point 3) below shows a way how a peer can include other user info when advertising their presence.
2. identifying peers (network level),
The framework (MC) creates a unique peer id for you and recommends the app re-use (ie. persist & reload) across app launches (MCPeerID - official docs - example objetive-c using NSUserDefaults storage api).
3. sharing peer session info (session level)
In your model, you're serialising the Task, fine (though no mention of what format, .xml, .plist, .json etc), but I don't see why you're wanting to serialise these MCPeerIDs?
Each peer on connecting is supposed to re-use their own peerID, and, optionally, if they wish to advertise additional info to potential peers they could provide a dictionary with info about the business-user behind the peer object e.g:
myAdvertiser = MCNearbyServiceAdvertiser(peer: reusableLocalPeerID, discoveryInfo: ["uuidStr" : "my-user-id-here"], serviceType: "my-tasks-app")
(MCNearbyServiceAdvertiser docs).
4. separating concerns (data level)
I would separate the framework (MC) from business model (Task, User ...) , to something like this:
class Task: Codable { var users: [User] ... }
class User: Codable { uid:String ...}
struct PeerRecord: { var peer:MCPeerID, var user_id:String, var name:String ... }
var foundPeers:[MCPeerID]
var connectedPeers:[String:PeerRecord] // e.g. ["a-user-id": PeerRecord(peer: ...)]
5. serialising
Apple example at point 2) above shows how to serialise MCPeerID using UserDefaults api.
Generally, when one extends Codable, one also expects to use an encoder (JSON, XML etc) somewhere in the app,
passing to this encoder's .encode(...) method the object in question e.g:
func storeTasks(tasksCodable:[Task]) -> Bool {
if let encoded_in_data_format = try? JSONEncoder().encode(tasksCodable) {
UserDefaults.standard.set(encoded_in_data_format, forKey: "tasks")
return true
}
return false
}

Swift 4 Codable: Cannot exclude a property

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.

Glossy iOS doesn't conform to protocol

I'm trying to integrate Gloss to do json parsing but I get lots of non sense errors.
I installed it with pod install and I experienced followings:
First when I import it, I got
: Cannot import underlying modules glossy, I don't know how, but after copy pasting the example from GlossExample project in github repo it disappeared.
Second if I use: struct Repo: Glossy I get error that Repo doesn't conform to protocols Decodable and Encodable but the code is pasted from the example and there exist the methods init?(json: JSON) and func toJSON() -> JSON?
Then I tried to use struct Repo: Decodable and having this function for decoder:
init?(json: JSON) {
let repoId: Int = "id" <~~ json
I get following error:
35: Binary operator '<~~' cannot be applied to operands of type 'String' and 'JSON'
Last I said OK, I won't use overloaded operators but the normal Decoder class:
init?(json: JSON) {
self.repoId = Decoder.decodeURL("id")(json)
}
I got:
Cannot convert value of type 'JSON' to expected argument type 'JSON' (aka 'Dictionary<String, AnyObject>')
Your helps and answers are welcome!
First when I import it, I got : Cannot import underlying modules glossy, I don't know how, but after copy pasting the example from GlossExample project in github repo it disappeared.
This is an issue that happens in Xcode with a few frameworks, but simply cleaning the code or directly running it usually gets rid of the warning. Otherwise it may have to do with using an older build of Xcode.
Second if I use: struct Repo: Glossy I get error that Repo doesn't conform to protocols Decodable and Encodable but the code is pasted from the example and there exist the methods init?(json: JSON) and func toJSON() -> JSON?
This means that your struct doesn't conform to both protocols. From the Glossy declaration:
/**
Convenience protocol for objects that can be
translated from and to JSON
*/
public protocol Glossy: Decodable, Encodable { }
So the Glossy protocol inherits both Decodable and Encodable, which means that you need to implement the protocol functions for both protocols, and not just toJSON() -> JSON?.
Then I tried to use struct Repo: Decodable and having this function for decoder: ...
You need to first declare the constant in the struct, and in the init deserialise the JSON and set the value to the constant:
struct Repo: Decodable {
let repoId: Int
init?(json: JSON) {
guard let repoId: Int = "id" <~~ json { else return nil }
self.repoId = repoId
}
}

Error When Using Protocol as a Type in Swift

I'm using Swift and trying to make some collection objects. These collection objects have a backing Dictionary to hold custom objects. For example, an object might be of type Cat and the collection object would be of type Cats. Cats would have a private dictionary containing values of type Cat. I have other types that also need respective collections (each collection type has specific logic for the type it holds).
I created a protocol to ensure each collection has a few common characteristics. These common functions and subscripts are generally passthroughs to the backing dictionary. Here is the protocol:
protocol ObjectDictionaryProtocol {
// These are necessary for generics to work in protocols
typealias Key: Hashable
typealias Value
// MARK: - Properties
var count: Int { get }
var isEmpty: Bool { get }
var keys: LazyMapCollection<Dictionary<Key, Value>, Key> { get }
var values: LazyMapCollection<Dictionary<Key, Value>, Value> { get }
// MARK: - Subscripts
subscript(key: Key) -> Value? { get set }
}
When I go to actually use the protocol as a type, for instance:
var objects: ObjectDictionaryProtocol
or
init(objs: ObjectDictionaryProtocol) {
...
}
I get the error:
Protocol 'ObjectDictionaryProtocol' can only be used as a generic constraint because it has Self or associated type requirements
I've searched around and it looks like the Hashable protocol I'm conforming to for my Key typealias is causing this. What is the best way to get around this? Is there a way to change the protocol such that I don't need the Hashable, or do I need to do something in the class that is using ObjectDictionaryProtocol? Or maybe there's a better way to effectively 'subclass' a Swift Dictionary (quotes because I realize the Dictionary struct cannot be subclassed)?
A protocol with associated types is less a type and more a template for a type. The actual type exists when the protocol's associated types are specified. Thus ObjectDictionaryProtocol 'becomes' a type when you specify:
ObjectDictionaryProtocol<String,Cat>
but the above is not valid Swift...
So you asked... '[is there] a better way to effectively 'subclass' a Swift Dictionary'. You might get by with an extension with something like:
class Cat {}
extension Dictionary where Value : Cat {
func voice (name: Key) {
if let _ = self[name] {
print ("\(name): Meow")
}
}
}
var someCats = Dictionary<String,Cat>()
someCats["Spot"] = Cat()
someCats.voice("Spot")
// Spot: Meow
Or you may need to actually implement the protocol.
class ObjectDiciontary<Key:Hashable, Value> : ObjectDictionaryProtocol {
var backingDictionary = Dictionary<Key,Value>()
// implement protocol
}
var object : ObjectDictionary<String,Cat> = ...
// ...
The reason is because when you're using typealias you're effectivly making your Protocol into a generic protocol as Protocol<T>. Bear with me here, I'll explain why and how to fix it.
The issue here is that Apple has decided to make typealias the keyword for defining associated types. This is fixed in Swift 2.2 (Xcode 7.3)
You can read more about it on https://github.com/apple/swift-evolution/blob/master/proposals/0011-replace-typealias-associated.md
It's being renamed to associatedtype which makes more sense.
This means your protocol has to be adopted, and there define its associated types.
In your case it would look something like
protocol ObjectDictionaryProtocol {
associatedtype Value
}
extension String : ObjectDictionaryProtocol {
associatedtype = Double
}
The init could look like
init<T : ObjectDictionaryProtocol>(objs: ObjectDictionaryProtocol)
or
init<T : ObjectDictionaryProtocol
where T.Value == Double>(objs: ObjectDictionaryProtocol)
Now for typealias Key: Hashable it means that whatever type that is assigned to Key has to conform or be Hashable
This will give you an error that String isn't conforming to ObjectDictionaryProtocol as it can't fulfil the requirements.
protocol ObjectDictionaryProtocol {
associatedtype Value : FloatingPointType
}
extension String : ObjectDictionaryProtocol {
associatedtype = Int
}
Int isn't a FloatingPointType (but Double is)

Resources