How to store properties in generic struct? - swift

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.

Related

Autogeneration of readonly Swift protocol exposing #Published properties

I'm currently working on a refactor where write access to model objects is restricted. Model objects are reference types with #Published properties:
public final class Model {
public enum State { /* Some cases here. */ }
#Published public var state: State
}
Let's say that I want a view model that returns a string to be observed by the UI layer. I'm using a protocol to expose readonly access to the view model, and a concrete implementation to support it:
public protocol ViewModelAPI {
var stringPublisher: AnyPublisher<String, Never> { get }
}
private struct ViewModel: ViewModelAPI {
public let stringPublisher: AnyPublisher<String, Never>
init(model: Model) {
stringPublisher =
model.$state
.map { /* Convert Model.State to string to display. */ }
.eraseToAnyPublisher()
}
}
public final class StateStringView: UIView {
init(viewModel: ViewModelAPI) {
/* Subscribe to `viewModel` to observe new String to display. */
}
}
Generally, this approach works pretty well, in that it prevents the UI layer from having direct Model dependencies. However, the ViewModel implementation has access to the settable Model.state property even though it only ever reads the property and never sets it.
Ideally, I'd like an easy way to create a protocol from an existing model object, such that we convert any #Published property into an AnyPublisher allowing readonly access. I think creating a protocol by hand is appropriate for ViewModel APIs since it's tailored to specifically the data used by a single UI feature. However, creating explicit observable protocols for all Model objects seems like a lot of overhead since it would require updating the protocol whenever the Model is updated.
Does anyone have any ideas on how to generate a protocol such that we could inspect a class and extract all of the #Published properties? Would dynamic member lookup be a possible solution here? Or would I just need to use a script to generate protocols that accomplish this?

Storing Structs in a Generic Array

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)
}
}

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 { }

Is there a way to reuse model within itself?

I have an app that stores User (UserModel) Friend list. if a friend clicks one user, its type is the same type (UserModel). In Swift it wouldnt allow using the model recursively, giving me this error:
"Value type 'OwnerModel' cannot have a stored property that recursively contains it"
import Foundation
struct OwnerModel: Codable {
var ownerId: Int
var ownerEmail: String
var ownerUserName: String
var ownerCommonName: String
var ownerBirthDate: String
var ownerCountry: String
var ownerBdayReminderId: Int
var ownerIsVerified: Bool
var ownerIsOnline: Bool
var ownerIsEventGreeted: Bool
var ownerIsBirthdayGreeted: Bool
var ownerAllowGreeting: Bool
var ownerFriends: OwnerModel
}
Is there a way I can reuse the OwnerModel under ownerFriends?
This can't work because structs are value types. So each OwnerModel would have to have a OwnerModel inside it, which would have to have an OwnerModel inside it, which would have to have an OwnerModel inside it.... This can never resolve. Since you've marked this Codable, try to write the JSON you expect to encode this to.
That said, ownerFriends seems plural, which would suggest [OwnerModel], and that's not a problem, since you could have zero of them:
struct OwnerModel: Codable {
...
var ownerFriends: [OwnerModel]
}
Remember again, however, that structs are value types. So each OwnerModel is just a value. It's not a reference to any other object. If you want to refer to other owners, you may want to store IDs rather than the actual object (or use classes in order to create references).

How to convert String to Struct (in Swift)

I have a struct like,
struct LoginConstants {
struct Selectors {
let testa = "test1234"
}
}
and a class like,
class Login: XCTestCase {
override class func setUp () {
// below constant will have a value like "LoginConstants"
let localConstants = "\(String(describing: self))Constants"
}
}
... so here I have a struct-name as a string format in localConstants.
My Question is how I can access the LoginConstants properties from the localConstants string?
NOTE:
I know I can access the LoginConstants() directly.
But I am planning to create a parent class where I can access this ***Constants struct dynamically.
Thanks for the help!
Objective-C has the ability to do this, but Swift does not. If you give a class an Objective-C name via the #objc attribute, you can use the Objective-C runtime functions to access it by name. However, this is not possible with a struct.
It's probably not the best way to go anyway. A better solution is to rethink what you are trying to do, and access the struct type directly rather than by name.