Storing Structs in a Generic Array - swift

I’m trying to create a struct which acts as a storage for the results being returned from a web API. This API returns different JSON results which are modeled as a set of structs.
These results need to be stored in an array inside of a storage class which will need to be generic as it should be able to store arrays of any of the types returned. I’m struggling, however, with adding generic data to an array… and this is where you guys might come in.
This is the storage class:
class FooStorage<F: Fooable> {
private var storage: [F] = []
func add<F: Fooable>(_ someFoo: F) {
storage.append(someFoo)
}
}
These are two sample structs modeling what the API mentioned will return:
struct FooA: Fooable, Decodable {
var foo: String
}
struct FooB: Fooable, Decodable {
var foo: String
var bar: String
}
And finally, this is the protocol I created in order to specify that all of these structs are results of the same API:
protocol Fooable {}
The compiler error I get is this:
No exact matches in call to instance method append
And it is thrown on the storage.append(_:) method of the FooStorage class. Tried to add Equatable and Hashable conformance to the FooX protocols but to no avail. Seems I need some enlightenment here… thanks in advance!

In your add function, remove the generic part & let it use the class generic:
class FooStorage<F: Fooable> {
private var storage: [F] = []
func add(_ someFoo: F) {
storage.append(someFoo)
}
}

Related

How to store properties in generic struct?

I have a Codable struct that is part of my app, RemoteData. I’m building a reusable package that will fetch the data and store it in UserDefaults. The data fetching, DataFetcher class has a Codable generic parameter. I am subclassing DataFetcher to pass in RemoteData as the generic param.
// in my app
struct RemoteData: Codable {
var experimentOne: [Variant<[Page]>]
var experimentTwo: [Variant<Bool>]
var experimentThree: [Variant<String>]
}
All of the properties in RemoteData will be arrays of type Variant<T> where T is Codable:
// in my package
public struct Variant<T: Codable>: Codable, VariantProtocol {
public var experimentName: String
public var variantName: String
public var percent: Int
public var value: T
}
I’d like to be able to save this data in UserDefaults. I’d like to perform some filtering on the Variant array to see if this user should see that configuration. I’d like to save the data so that each experiment name is the key and the single variant the user should see is the value rather than the whole array. Although if the whole array is the only option, I’d be ok with that too.
However, since my DataFetcher doesn’t know what the properties are since it is just taking in a generic I don’t think I can do that. My first thought was to create a protocol that RemoteConfig confirms to and that the DataFetcher generic also conforms to.
// in my package, but subclassing in my app to provide url
open class DataFetcher<T: Decodable> {
var remoteConfig: T?
var url: URL
public init(url: String) {
self.url = url
}
func fetchAndSaveData() { ... }
}
That doesn’t work because I then need to specify T in Variant and I will only be able to have Variant arrays of one type.
I’m stuck here and not sure how to move forward.

Swift: Array of objects that conforms to same protocol as the object

I have a question about generics in swift.
First of all, In JSONDecoder we have the below function.
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
And assuming we have a struct called Book: Codable defined before, we can call this function as;
decoder.decode(Book.self, from: bookData)
and also as;
decoder.decode([Book].self, from: bookListData)
We were able to feed the function with the array of Book objects, however as far I know [Book].self does not conform to Decodable.
My question how can we apply a similar solution, when we try to feed a generic struct or class instead of a function. For example,
protocol Publishable { /* literally empty */ }
class Manager<T: Publishable> {
var data: T?
// managing stuff
}
we have the above Manager class and an extesion as, extension Book: Publishable. we can create an instance from it like,
let bookManager = Manager<Book>()
But we cannot directly say,
let bookListManager = Manager<[Book]>()
Let me tell you, I am not sure how the first examples even works. I didn't realize it until I have faced this problem. Anyway, I am looking for a solution or an advice that may enable me to create an instance as above. Or any other way.
Appreciated.
EDIT:
I am aware of a solution where I could create another struct as,
struct BookList: Publishable {
var books: [Book]
}
and then,
let bookListManager = Manager<BookList>()
will work. However, It would require me to create ton of middle structures which would be inconvenient.
as far I know [Book].self does not conform to Decodable
It does. (Actually, that's [Book], not [Book].Type, but [Book].self isn't what you meant.)
extension Array: Decodable where Element: Decodable { …
If you want similar functionality, extend similarly.
extension Array: Publishable where Element: Publishable { }

Working with generic constraints

I know this question has been asked before but I have no idea how to solve this current problem. I have defined a protocol MultipleChoiceQuestionable with an associatedtype property:
protocol Questionable {
var text: String {get set}
var givenAnswer: String? {get set}
}
protocol MultipleChoiceQuestionable: Questionable {
associatedtype Value
var answers: Value { get }
}
struct OpenQuestion: Questionable {
var text: String
var givenAnswer: String?
}
struct MultipleChoiceQuestion: MultipleChoiceQuestionable {
typealias Value = [String]
var text: String
var givenAnswer: String?
var answers: Value
}
struct NestedMultipleChoiceQuestion: MultipleChoiceQuestionable {
typealias Value = [MultipleChoiceQuestion]
var text: String
var answers: Value
var givenAnswer: String?
}
Types which conform to this protocol are saved in an array as Questionable like so:
// This array contains OpenQuestion, MultipleChoiceQuestion and NestedMultipleChoiceQuestion
private var questions: [Questionable] = QuestionBuilder.createQuestions()
Somewhere in my code I want to do something like:
let question = questions[index]
if let question = question as? MultipleChoiceQuestionable {
// Do something with the answers
question.answers = .....
}
This is not possible because Xcode warns me: Protocol MultipleChoiceQuestionable can only be used as a generic constraint. I've been searching around on how to solve this issue since generics are quite new for me. Apparently Swift doesn't know the type of the associatedtype during compile time which is the reason this error is thrown. I've read about using type erasure but I don't know if that solves my problem. Maybe I should use generic properties instead or perhaps my protocols are defined wrong?
If the action you want to apply to your sub-protocol objects does not rely on the associated type (i.e. neither has the a generic parameter nor returns the generic type) you can introduce an auxiliary protocol which just exposes the properties/methods you need, let your type conform to that protocol, and declare the question in terms of that protocol.
For example, if you just want to know some info about the question:
protocol MultipleChoiceInfo {
var numberOfAnswers: Int { get }
}
extension MultipleChoiceQuestion: MultipleChoiceInfo {
var numberOfAnswers: Int { return answers.count }
}
// do the same for the other multiple-choice types
Then you can access the questions through the new protocol like this:
let question = questions[index]
if let info = question as? MultipleChoiceInfo {
print(info.numberOfAnswers)
}
As I said, if you cannot provide an abstract (non-generic) interface then this won't work.
EDIT
If you need to process the generic data inside your questions you can extract the logic depending on the concrete generic type into another "processing" type which provides an interface to your questions. Each question type then dispatches its data to the processor interface:
protocol MultipleChoiceProcessor {
func process(stringAnswers: [String])
func process(nestedAnswers: [MultipleChoiceQuestion])
}
protocol MultipleChoiceProxy {
func apply(processor: MultipleChoiceProcessor)
}
extension MultipleChoiceQuestion: MultipleChoiceProxy {
func apply(processor: MultipleChoiceProcessor) {
processor.process(stringAnswers: answers)
}
}
Just create a type conforming to MultipleChoiceProcessor and do the type-check dance again:
if let proxy = question as? MultipleChoiceProxy {
proxy.apply(processor:myProcessor)
}
As an aside, if you don't have more protocols and structs in your real application, you might also just ditch the protocol stuff altogether... for this kind of problem it seems a bit over-engineered.

Changing a struct with one type to another type

I have two structs with the same fields. What is the best way to merge them.
struct Type1{
var variable1:String?
var variable2:Double?
var variable3:String?
var notImporant:String?
}
struct Type2{
var variable1A:String?
var variable2A:String?
var variable3A:String!
}
What is the best way to convert type2 to type1? I am getting a return from an API and parsing it using codable but there are two different structs and I need to get one struct. The data is the same, it is just mapped differently in terms of types. Some of the structs have more info and others have less.
Just make a copy constructor in both structs like so:
struct Type2 {
var variable1A:String?
var variable2A:String?
var variable3A:String!
init(_ otherType: Type1) {
variable1A = otherType.variable1
variable2A = otherType.variable2
variable3A = otherType.variable3
}
}
You cannot cast two unrelated structs. What you can do is define a common Protocol for the two of them, and use them in places where you don't care which underlying object it is.
protocol CommonProtocol {
var variable1: String? { get }
var variable3: String? { get }
}
struct Type1: CommonProtocol {
var variable1:String?
var variable2:Double?
var variable3:String?
var notImporant:String?
}
struct Type2: CommonProtocol {
var variable1A:String?
var variable2A:String?
var variable3A:String!
}
Then, in whichever place you're currently stuck with a type1 instead of a type2, have that function just accept a CommonProtocol instead, and you can use either.
Note that, while both of your types have a variable2, one of them is a Double? while the other is a String?. There are a few different ways to approach that, which I leave to you. I just left it out of the protocol.
On another note, it's Swift standard to capitalize the names of structs (Type1, Type2). In certain instances, you can run into problems if you don't, so I suggest you do.

Swift Collections With Class Conforming to Protocol

Is it possible to construct a collection (array, dictionary, set) type checks values against both a class and a protocol? Given:
class Piece {
var name: String?
}
protocol Jump {
func jump() { ... }
}
protocol Move {
func move() { ... }
}
Allows:
var pieces: [Piece]?
Or:
var moves: [Move]?
var jumps: [Jump]?
Or:
var movenjump: [protocol <Move,Jump>]
But, I'm not sure how to restrict a collection to instances of Piece that Move and Jump.
The short answer is no. To date, Swift only allow collections for either a single Class type or protocol types (single protocol or multiple protocols). But there are ways you can workaround this.
The best approach is if Piece is your code, try to make it a protocol. Then you can declare array for example: let anArray: [protocol<Piece, Move, Jump>].
If you don't have access to the source code of Piece, try to generify the surrounding class which needs to declare the collection. This way, you can use where clause to constrain the generic type to anything you like:
class MyClass<T where T: Piece, T: Move, T: Jump> {
var myCollection: [T]?
}