Swift 4.1 array protocol conformance - ios

We just switched to swift 4.1 and we are having some difficulties with type conformance for Arrays. Here is the old way:
public typealias XDRCodable = XDREncodable & XDRDecodable
public protocol XDREncodable: Encodable {
func xdrEncode(to encoder: XDREncoder) throws
}
public protocol XDRDecodable: Decodable {
init(fromBinary decoder: XDRDecoder) throws
init(fromBinary decoder: XDRDecoder, count: Int) throws
}
extension Array: XDRCodable {
public func xdrEncode(to encoder: XDREncoder) throws {
try encoder.encode(UInt32(self.count))
for element in self {
try (element as! Encodable).encode(to: encoder)
}
}
public init(fromBinary decoder: XDRDecoder) throws {
guard let binaryElement = Element.self as? Decodable.Type else {
throw XDRDecoder.Error.typeNotConformingToDecodable(Element.self)
}
let count = try decoder.decode(UInt32.self)
self.init()
self.reserveCapacity(Int(count))
for _ in 0 ..< count {
let decoded = try binaryElement.init(from: decoder)
self.append(decoded as! Element)
}
}
}
This gives the following error in swift 4.1:
'XDRDecodable' requires that 'Element' conform to 'Decodable'
So we tried changing the declaration to:
extension Array: XDRCodable where Element : XDRCodable
While this compiles it still fails to encode the array and the following warning is generated:
warning: Swift runtime does not yet support dynamically querying conditional conformance ('Swift.Array': 'stellarsdk.XDREncodable')
I saw that this is a work in progress but does anyone have a workaround for this until type conformance is properly implemented. I'd like it to work as it was in swift 4.0 for now.

I have similar problem with BinaryCoder and created workaround. But you must have access to encoder a decoder implementation.
protocol XDRCodableArray {
func binaryEncode(to encoder: XDREncoder) throws
init(fromBinary decoder: XDRDecoder) throws
}
extension Array: XDRCodableArray {
//copy implementation of XDRCodable from Array: XDRCodable
}
//delete extension Array: XDRCodable
In decode append special implementation for arrays:
...
case let binaryT as BinaryDecodable.Type:
return try binaryT.init(fromBinary: self) as! T
//new case
case let array as BinaryCodableArray.Type:
return try array.init(fromBinary: self) as! T
...
And also in encode:
...
case let binary as BinaryEncodable:
try binary.binaryEncode(to: self)
//new case
case let array as BinaryCodableArray:
try array.binaryEncode(to: self)
...
Generaly there is problem with conditional protocol conformance. You can't cast variable to that protocol (in some cases).
protocol Test: Codable {
func test() -> String
}
extension Array: Test where Element: Codable {
func test() -> String {
return "Success"
}
}
func doSomething(x: Codable) {
let test = x as! Test
test.test()
}
let array = ["value"]
let t = (array as! Test).test //success
doSomething(x: array) //fail on first row inside function
I hope that Swift 4.2 with dynamically querying conditional conformance support will solve this.

Related

Protocol type cannot conform to protocol because only concrete types can conform to protocols

Within the app, we have two types of Stickers, String and Bitmap. Each sticker pack could contain both types. This is how I declare the models:
// Mark: - Models
protocol Sticker: Codable {
}
public struct StickerString: Sticker, Codable, Equatable {
let fontName: String
let character: String
}
public struct StickerBitmap: Sticker, Codable, Equatable {
let imageName: String
}
After the user chooses some stickers and used them, we want to save the stickers into UserDefaults so we can show him the "Recently Used" Sticker tab. I'm trying to Decode the saved [Sticker] array:
let recentStickers = try? JSONDecoder().decode([Sticker].self, from: data)
But I get the following compile error:
Protocol type 'Sticker' cannot conform to 'Decodable' because only concrete types can conform to protocols
I can't understand why as I declared Sticker as Codable which also implement Decodable. Any help would be highly appreciated!
Rather than protocols use generics.
Declare a simple function
func decodeStickers<T : Decodable>(from data : Data) throws -> T
{
return try JSONDecoder().decode(T.self, from: data)
}
T can be a single object as well as an array.
In your structs drop the Sticker protocol. You can also delete Equatable because it's getting synthesized in structs.
public struct StickerString : Codable {
let fontName: String
let character: String
}
public struct StickerBitmap : Codable {
let imageName: String
}
To decode one of the sticker types annotate the type
let imageStickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}]
"""
let stickerData = Data(imageStickers.utf8)
let recentStickers : [StickerBitmap] = try! decodeStickers(from: stickerData)
print(recentStickers.first?.imageName)
and
let stringSticker = """
{"fontName":"Times","character":"πŸ˜ƒ"}
"""
let stickerData = Data(stringSticker.utf8)
let sticker : StickerString = try! decodeStickers(from: stickerData)
print(sticker.character)
To decode an array of StickerString and StickerBitmap types declare a wrapper enum with associated values
enum Sticker: Codable {
case string(StickerString)
case image(StickerBitmap)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let stringData = try container.decode(StickerString.self)
self = .string(stringData)
} catch DecodingError.keyNotFound {
let imageData = try container.decode(StickerBitmap.self)
self = .image(imageData)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let string) : try container.encode(string)
case .image(let image) : try container.encode(image)
}
}
}
Then you can decode
let stickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}, {"fontName":"Times","character":"πŸ˜ƒ"}]
"""
let stickerData = Data(stickers.utf8)
let recentStickers = try! JSONDecoder().decode([Sticker].self, from: stickerData)
print(recentStickers)
In a table view just switch on the enum
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let sticker = stickers[indexPath.row]
switch sticker {
case .string(let stringSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "StringStickerCell", for: indexPath) as! StringStickerCell
// update UI
return cell
case .image(let imageSticker):
let cell = tableView.dequeueReusableCell(withCellIdentifier: "ImageStickerCell", for: indexPath) as! ImageStickerCell
// update UI
return cell
}
}
what happens here is kind of self explanatory
JSONDecoder().decode(/* swift here is expecting class or struct that conforms to Codable */.self, from: data)
but let us assume that you can pass a protocol
in your protocol
protocol Sticker: Codable {
}
where is the properties that you are expecting swift to decode from data ?
you added the properties in
public struct StickerString: Sticker, Codable, Equatable { // it should have redundendant conformance as well as you are conforming to Coddle again
let fontName: String // here is the properties you are expected to be decoded with the coding keys
let character: String // here is the properties you are expected to be decoded with the coding keys
}
Here is what I suggest you do as long you want the type you want to decode to be dynamic
class GenericService< /* here you can pass your class or struct that conforms to Codable */ GenericResponseModel: Codable> {
func buildObjectFromResponse(data: Data?) -> GenericResponseModel? {
var object : GenericResponseModel?
do {
object = try JSONDecoder().decode(GenericResponseModel.self , from: data!)
} catch (let error){
print(error)
}
return object
}
}
through this class you can pass any type or even list that conforms to Codable
then you will decouple the type checking from the decoding process using the method below
private func handleDecodingTypes (stickers: [Sticker]){
for sticker in stickers {
if sticker is StickerString {
/* do the decoding here */
}
if sticker is StickerBitmap {
/* do the decoding here */
}
}
}
The Sticker protocol is not confirming/implementing the Codable, it is actually inheriting from the Codable. As the error message suggests, Protocols do not conform to other protocols only concrete types do.
protocol Sticker: Codable //This is Protocol inheritance
By stating
public struct StickerString: Sticker
Means that Sticker string conforms to Sticker and the Sticker is child of Codable, so StickerString eventually conforms to Codable. There is no need to state conformance again i.e:
public struct StickerString: Sticker, Codable //Conformance to Codable is redundant
Now coming to the decoding part.
let recentStickers = try? JSONDecoder().decode([Sticker].self, from: data)
The decode method needs a concrete type. It doesn't have any information about the underlying type or its attributes because Sticker is just a protocol itself inheriting from Codable that has no properties/attributes.
Compiler would not have any problem combining both the StrickerString and StickerBitmap after they've decoded e.g.
let stickerString = try JSONDecoder().decode(StickerString.self, from: data)
let stickerBitmap = try JSONDecoder().decode(StickerBitmap.self, from: data)
let stickers : [Sticker] = [stickerString, stickerBitmap]

Updating object properties with codable extension in Swift

let me start by saying that I have already implemented Decodable which decodes JSON into several objects with these two Integer values:
public class ARBufferData: DecoderUpdatable {
private var previousStation: Int
private var numberOfElements: Int
func update(from decoder: Decoder) throws {
//Still needs work
}
}
What I am now trying to achieve is making the created objects updatable so that when a value in the JSON changes (e.g. numberOfElements) only the value is changed in the corresponding object. I believe this guide can enable me to do it, but I am having trouble implementing it: Understanding and Extending Swift 4’s Codable
This is the extension of KeyedDecodingContainer:
extension KeyedDecodingContainer {
func update<T: DecoderUpdatable>(_ value: inout T, forKey key: Key, userInfo: Any) throws {
let nestedDecoder = NestedDecoder(from: self, key: key)
try value.update(from: nestedDecoder)
}
}
The reason this would be helpful is that I can then set a property observer on that value and trigger a redraw of the visualisation.
I would be very grateful, if anyone can help or point me in the right direction.
Thank you!
Cheers
There are two ways to update the class. One, you decode each int by itself and compare. Two, you implement DecoderUpdatable for Int and call container.update with them as argument.
public class ARBufferData: NSObject, Decodable, DecoderUpdatable {
init(previousStation: Int, numberOfElements: Int) {
self.previousStation = previousStation
self.numberOfElements = numberOfElements
}
#objc dynamic var previousStation: Int
#objc dynamic var numberOfElements: Int
private enum CodingKeys: String, CodingKey {
case previousStation, numberOfElements
}
public func update(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try container.update(&previousStation, forKey: .previousStation)
try container.update(&numberOfElements, forKey: .numberOfElements)
}
}
extension Int: DecoderUpdatable {
public mutating func update(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let result = try container.decode(Int.self)
guard result != self else { return }
self = result
}
}
I do not know whether the blogpost-writer intended it this way though. If he did, then generating the DecoderUpdatable conformances for the basic types could be a use case for Sourcery, but that's off topic here.
In Swift4 there is an interesting way to observe which you may also be interested in:
let token = buffer.observe(\.numberOfElements, options: [.new, .old]) {
object, change in
if change.oldValue != change.newValue {
// Act on change of buffer.numberOfElements.
}
}
Source

How to create a wrapper function for "ObjectMapper" library in swift

I am using ObjectMapper library to map my JSON object to Swift object. The traditional method of library is working fine for me like below code.
tmpArray1 = Mapper<UserModel>().mapArray(JSONArray: result1)
tmpArray2 = Mapper<CompanyModel>().mapArray(JSONArray: result2)
Now, I want to create a generic method to return dynamic object according to argument which i pass in that function. I want somewhat like below.
tmpArray1 = WrapperClass.shared.getModelObject(objType: UserModel, data: Any)
tmpArray2 = WrapperClass.shared.getModelObject(objType: CompanyModel, data: Any)
Here is my WrapperClass.swift class code:
open class WrapperClass: NSObject {
static let shared = WrapperClass()
open func getModelObject(objType: Mappable, data: Any) -> Any? {
// Need Help Here
return <dynamic object>
}
}
I don't know my approach is 100% right but i want somewhat like whatever object type i pass in the argument of the function i want same object type in return with mapped with ObjectMapper object. I am using Swift 4.0 version.
You can find the ObjectMapper
here.
Update:
I have tried below thing but it will not work, show an error
func getModelObject<T: Mappable>(modelType: T.Type, data: Any) -> [T]? {
if data is Array<Any> {
return Mapper<modelType>().mapArray(JSONArray: data as! [[String: Any]])
//ERROR: Use of undeclared type 'modelType'
}
return nil
}
You can achieve that by combination of generics and Type. It allows you to instantiate the mappable object with generic T (no Mapper<...> here):
func getModelObject<T: Mappable>(objType: T.Type, data: Any) -> T? {
if let data = data as? [String: Any] {
return T(JSON: data)
} else if let data = data as? String {
return T(JSONString: data)
}
return nil
}
Example of usage:
class User: Mappable {
var name: String!
var age: Int!
required init?(map: Map) {}
func mapping(map: Map) {
name <- map["name"]
age <- map["age"]
}
}
let json = "{\"name\":\"Bob\",\"age\":100}"
if let user = WrapperClass.shared.getModelObject(objType: User.self, data: json) {
print(user.name, user.age)
}
Answer with Mapper<...>:
func getModelObject<T: Mappable>(data: Any) -> [T]? {
if let data = data as? [[String: Any]] {
return Mapper<T>().mapArray(JSONArray: data)
}
return nil
}

Enum of structs in Swift 3.0

I am trying to create an enum of a struct that I would like to initialize:
struct CustomStruct {
var variable1: String
var variable2: AnyClass
var variable3: Int
init (variable1: String, variable2: AnyClass, variable3: Int) {
self.variable1 = variable1
self.variable2 = variable2
self.variable3 = variable3
}
}
enum AllStructs: CustomStruct {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case getData:
return CustomStruct(variable1:"data1", variable2: SomeObject.class, variable3: POST)
case addNewData:
// Same to same
default:
return nil
}
}
}
I get the following errors:
Type AllStructs does not conform to protocol 'RawRepresentable'
I am assuming that enums cannot be used this way. We must use primitives.
It should be:
struct CustomStruct {
var apiUrl: String
var responseType: AnyObject
var httpType: Int
init (variable1: String, variable2: AnyObject, variable3: Int) {
self.apiUrl = variable1
self.responseType = variable2
self.httpType = variable3
}
}
enum MyEnum {
case getData
case addNewData
func getAPI() -> CustomStruct {
switch self {
case .getData:
return CustomStruct(variable1: "URL_TO_GET_DATA", variable2: 11 as AnyObject, variable3: 101)
case .addNewData:
return CustomStruct(variable1: "URL_TO_ADD_NEW_DATA", variable2: 12 as AnyObject, variable3: 102)
}
}
}
Usage:
let data = MyEnum.getData
let myObject = data.getAPI()
// this should logs: "URL_TO_GET_DATA 11 101"
print(myObject.apiUrl, myObject.responseType, myObject.httpType)
Note that upon Naming Conventions, struct should named as CustomStruct and enum named as MyEnum.
In fact, I'm not pretty sure of the need of letting CustomStruct to be the parent of MyEnum to achieve what are you trying to; As mentioned above in the snippets, you can return an instance of the struct based on what is the value of the referred enum.
I'm not commenting on the choice to use an enum here, but just explaining why you got that error and how to declare an enum that has a custom object as parent.
The error shows you the problem, CustomStruct must implement RawRepresentable to be used as base class of that enum.
Here is a simplified example that shows you what you need to do:
struct CustomStruct : ExpressibleByIntegerLiteral, Equatable {
var rawValue: Int = 0
init(integerLiteral value: Int){
self.rawValue = value
}
static func == (lhs: CustomStruct, rhs: CustomStruct) -> Bool {
return
lhs.rawValue == rhs.rawValue
}
}
enum AllStructs: CustomStruct {
case ONE = 1
case TWO = 2
}
A few important things that we can see in this snippet:
The cases like ONE and TWO must be representable with a Swift literal, check this Swift 2 post for a list of available literals (int,string,array,dictionary,etc...). But please note that in Swift 3, the LiteralConvertible protocols are now called ExpressibleByXLiteral after the Big Swift Rename.
The requirement to implement RawRepresentable is covered implementing one of the Expressible protocols (init?(rawValue:) will leverage the initializer we wrote to support literals).
Enums must also be Equatable , so you'll have to implement the equality operator for your CustomStruct base type.
Did you try conforming to RawRepresentable like the error is asking?
Using JSON representation should work for variable1 and variable3. Some extra work may be required for variable2.
struct CustomStruct: RawRepresentable {
var variable1: String
var variable2: AnyClass
var variable3: Int
init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8) else {
return nil
}
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return nil
}
self.variable1 = (json["variable1"] as? String) ?? ""
self.variable2 = (json["variable2"] as? AnyClass) ?? AnyClass()
self.variable3 = (json["variable3"] as? Int) ?? 0
}
var rawValue: String {
let json = ["variable1": self.variable1,
"variable2": self.variable2,
"variable3": self.variable3
]
guard let data = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return ""
}
return String(data: data, encoding: .utf8) ?? ""
}
}
According to the documentation:
If a value (known as a β€œraw” value) is provided for each enumeration case, the value can be a string, a character, or a value of any integer or floating-point type.
So yes, you cannot set a struct type to be enum's raw value.
In your case I would suggest using string as the enum raw value and some dictionary mapping these strings to CUSTOM_STRUCT type.
A bit late on the party but maybe useful for someone else. I would consider to simply use computed variables instead of a struct.
enum MyEnum {
case getData
case addNewData
var variable1: String {
switch self {
case .getData: return "data1"
case .addNewData: return "data2"
}
}
var variable2: Int {
switch self {
case .getData: return 1
case .addNewData: return 2
}
}
// ....
}
Usage:
let data = MyEnum.getData
print (data.variable1) // "data1"

Swift generic class which could take Array of elements or single element

I would like to make generic class which will be able to take Parsable type, or Array of Parsable type. Logic for both are almost the same so i don't want to make two different class for this operation. Is it possible to solve it using Swift generics, or protocol associatedtype types?
protocol Parsable: class {
associatedtype Type
static func objectFromDictionary(dictionary: Dictionary<String, AnyObject>, inContext context: NSManagedObjectContext) -> Type?
func importFromDictionary(dictionary: Dictionary<String, AnyObject>)
}
class ParseOperation<T: Parsable>: NSOperation {
func execute() -> T {
}
}
class ParseOperation<T where T: SequenceType, T.Generator.Element == Parsable>: NSOperation {
func execute() -> T {
// Do parsing
}
}
This i how i would like to work:
class ParseOperation<T where T: SequenceType, T.Generator.Element == Parsable OR T: Parsable>: NSOperation {
func execute() -> T {
// Do parsing
}
}
In my current implementation i am using enum which looks little bit ugly:
class ParseOperation<T where T: NSManagedObject, T:Parsable>: NSOperation {
var responseToParse: AnyObject?
var parseType: ParseType
var parsedObjects: [T]?
init(parseType: ParseType) {}
func execute() {
var objects: [NSManagedObject] = []
if self.parseType == .Single {
if let responseToParse = self.responseToParse as? Dictionary<String, AnyObject>,
let parsedObject = T.objectFromDictionary(responseToParse, inContext: localContext) {
objects.append(parsedObject)
}
} else if self.parseType == .Array {
if let responseToParse = self.responseToParse as? Array<Dictionary<String, AnyObject>> {
for dictionary in responseToParse {
if let parsedObject = T.objectFromDictionary(dictionary, inContext: localContext) {
objects.append(parsedObject)
}
}
}
}
self.parsedObjects = objects
...
}
}
I modified #RonaldMartin 's answer to show how ParsableArray might help you. It don't need to take input of Parsable elements, just implement parse function this way:
protocol Parsable {
associatedtype T
static func parse(input: AnyObject) -> T?
}
struct ParsableArray<TElement where TElement: Parsable>: Parsable {
static func parse(input: AnyObject) -> [TElement.T]? {
guard let arrayInput = input as? [AnyObject] else {
return nil
}
return arrayInput.flatMap(TElement.parse)
}
}
I've renamed objectFromDictionary to parse because it's need to take AnyObject not the Dictionary to be able to parse array. You can add context or whatever you like to parse method, of course.
If Parsable done this way then ParseOperation becomes very simple:
class ParseOperation<T where T: Parsable>: NSOperation {
let input: AnyObject
var result: T.T?
init(input: AnyObject) {
self.input = input
}
override func main() {
result = T.parse(input)
}
}
Then, you can parse arrays this way (note: this is only to demonstrate how to create ParseOperation; S is just some Parsable struct):
let op = ParseOperation<ParsableArray<S>>(input: [["1": 5, "2": 6], ["3": 10]])
op.main()
var r: [S]? = op.result
I hope, this will help.
As far as I know, the type constraint system is not designed to handle OR constraints. However, it should still be possible to do what you're asking.
One approach is to represent both singleton Parsables and collections of Parsables under a single type that you can use to constrain ParseOperation. The neatest way to do this would be to extend Array (or SequenceType, CollectionType, etc.) to conform to the Parsable type as well, but this is not yet possible as of Xcode 7.3. You can use the same workaround from that linked question and add an intermediate class to represent Parsable arrays:
class ParsableArray: Parsable {
let array: [Parsable]
init(array: [Parsable]) {
self.array = array
}
// Parsable conformance...
}
Now, you can just use the original protocol for your type constraint:
class ParseOperation<T: Parsable>: NSOperation {
func execute() -> T {
// Do parsing
}
}
Ideally you should be able to do this:
// This DOES NOT compile as of XCode 7.3
extension Array: Parsable where Element: Parsable {
// Parsable implementation
}
However it currently doesn't work.
Currently you have to rely on a wrapper struct:
struct ParsableArray<Element: Parsable>: Parsable {
let array: [Element]
init(_ array: [Element]) {
self.array = array
}
// Parsable implementation
}
You can also implement a convenience method for [Parsable] arrays:
extension Array where Element: Parsable {
func toParsable() -> ParsableArray<Element> {
return ParsableArray(self)
}
}
So you could execute your method like this:
let array = [Parsable]()
parse(array.toParsable()) // Equivalent to: parse(ParsableArray(array))

Resources