I'm currently working on using Realm together with the Swift framework Oatmeal. Now, both of those require model classes (Oatmeal can do automatic serialisation). My structure for a given Oatmeal model and Realm model are as follows:
class OatmealModel: SerializableObject {
var id: Int = 0
var someOtherProperty: String?
var anotherUnknownProperty: SomeType?
var store: RealmModel?
}
class RealmModel: Object {
dynamic var id: Int = 0
dynamic var someOtherProperty: String?
dynamic var anotherUnknownProperty: SomeType?
override static func primaryKey() -> String? {
return "id"
}
}
As should be clear, they both hold the same properties, but the OatmealModel is always the one I interact with. Aside from this, the OatmealModel also implements a protocol to retrieve data:
protocol PersistentStore {
func save()
func findStore()
func all() -> [PersistentStore]
func filter(predicate: NSPredicate) -> [PersistentStore]
}
These methods allow us to fetch from the Realm database and then hydrate the oatmeal model, and get only oatmeal models back. And then calling save will write back to realm.
So essentially, what I want to do is:
When retrieving entities from the Realm DB and hydrating Oatmeal models, this should be done automatically. Currently, every oatmeal model has to set the all and filter (and save) methods separately just to replace the class name, and the properties currently have to be set manually. My plan is to somehow use reflection on both the oatmeal model and the realm model to see which properties have the same name and type, and then write them from one to the other.
Currently, I have no idea how to go about this, so any hints on how I could use reflection for this would be helpful. I assume somehow use the Mirror to extract the properties and then loop through them and see if one of the same name exists in the other?
Also, to achieve this in the end, since it'll be the models doing this to return instances of itself, is it somehow possible for a model to create an array of its own type, without explicitly writing the type? E.g [Self] (which doesn't work) rather than [OatmealModel]?
I'm fairly new to Swift, but am familiar with a few other languages and concepts, any help is greatly appreciated!
Related
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.
I am building an app that communicates with a python flask / postgreSQL backend. As such, it depends on an internet connection so that users can create, read, update, and delete model entities. However, we also need a local cache/datastore that allows the user to perform a subset of activities without an internet connection - preferably in a seamless manner. I am trying to build an abstract interface that abstracts away from the view & viewmodel code the specifics of performing these CRUD operations over the network or locally with the cache. The API would look something like this:
protocol EnvoyPersistenceService {
func createEntity<entity: Codable>(entityType: EntityType, entity: entity) async throws -> entity
func readEntities<entity: Codable & Identifiable>(entityType: EntityType, queryParams: [BaseQueryItem]) async throws -> [entity]
func updateEntity<entity>(entityType: EntityType, entity: entity) async throws -> entity where entity: Codable, entity: Identifiable
func deleteEntity<entity>(entityType: EntityType, entity: entity) async throws -> Void where entity: Codable, entity: Identifiable
}
enum EntityType: Codable {
case entityTypeA
case entityTypeB
case entityTypeC
}
struct BaseQueryItem: Codable {
var key: String
var value: String
}
It is easy to implement this protocol on the networking side of the house. I include the queryItems in the URL I build, generate URLRequests from those, and execute the requests. I cannot figure out a good way to take that same input and perform an equivalent filter action locally. The cache construct is a simple wrapper around some json files. What I want to be able to do is something like this:
func readEntities<entity: Codable & Identifiable>(entityType: EntityType, queryParams: [BaseQueryItem]) async throws -> [entity] {
let endpoint = resolveEndpoint(entityType: entityType)
do {
let cachedEntities: [entity] = try dataStoreAgent.readDataset(endpoint: endpoint)
// need to filter these entities, but cannot figure out how to do so generically
let filteredEntities = cachedEntities.filter { queryParams }
return filteredEntities
}
}
The above obviously does not work because queryParams isn't a closure. I then did some research into generic ways to perform filtering like this, during which I found approaches like swift: find-function in generic style, https://www.andyibanez.com/posts/understanding-keypaths-swift/, and https://www.swiftbysundell.com/articles/predicates-in-swift/. The problem I run into there is that if I change the API from the simple key/value array, I can now do the local cache filtering but no longer have a model that supports URLRequest generation.
I feel like there has to be a way to extend one of the approaches (keypath-based or predicate-based) linked above to enable extraction of simple key/value pairs but I am struggling to find out how. If there is no good answer, I will make a go at doing some of this dynamically with the Mirror API, but I chose to stay away from that initially because it seems out of sync with how the app typically works. Any ideas on how I should go about this?
In my Quiz app I initialize quizzes, and the providing class does not know the format of the questions before being provided them (although they are constrained by a QuestionProtocol):
public protocol QuestionProtocol {
init?(fields: [String] )
var description: String {get}
var question: String {get}
var solution: String {get}
var explainAnswer: String {get}
var answered: Int {get set}
var qa: String {get}
var qb: String {get}
var qc: String {get}
var qd: String {get}
}
And I can initialize the quizzes and return them easily enough through a method with the signature
public func initializeQuizzes<T: QuestionProtocol>(with type: T.Type, withCompletionHandler completion: ((Result<[Quiz<T>], Error>) -> Void)?)
However to provide these quizzes is expensive (an API call or a SQL retrieval) so I want to store these quizzes and retrieve them separately from a suitable function with signature
public func getNextQFromSet<T: QuestionProtocol>(with type: T.Type) -> (question: T, answers: [String])?
The problem I have is storing these questions which are of type T.
They are linked to a Quiz object:
public class Quiz<T> {
private let questions : [T]
private let name : String
init(name: String, questions: [T]) {
self.name = name
self.questions = questions
}
public func getQuestions() -> [T] {
return questions
}
func getName() -> String {
return name
}
}
So I'm able to store them as quizzes that conform to the QuestionProtocol
private var quizzes = [Quiz<QuestionProtocol>]()
But then I lose the extra information I want to store in the question.
I can store Any, but I believe that is bad practice
private var anyquizzes = [Quiz<Any>]()
Ideally I would like to store T i.e.
Quiz<T>
but that seems to be impossible in Swift.
Because these classes are in a pod they have no way of knowing about the internal workings of a Question, and are provided these at runtime hence the use of generics and the difficulties in storing these questions.
I can't think of a way to improve the design of the App (more specifically the Pod) - I want to initialize the quizzes once and once only and then run functions like getNextQFromSet() to retrieve a relevant question - which obviously depends on me knowing the type of the question (which I do not know before runtime).
For clarity here is a link to the Pod: https://github.com/stevencurtis/QuizManager
How can I store an array containing these questions without knowing the type?
To be short, I think it makes sense to remove QuestionProtocol and replace it with plain data structure struct Question.
Before I explain my point of view, I want to note that even though I looked at the pod, I still do not know all the requirements, so I might be wrong.
Let's try to have a look at the problem from design perspective instead of programming language perspective.
What is the reason of having QuestionProtocol? Could it be replaced with, let's say, object instead? Why do those properties should be polymorphic? Of course implementation details should be hidden, but hiding data is not about protocols or additional function layers, is about abstractions.
Let's convert QuestionProtocol to Question object for now and think about an abstraction. If there is a real abstraction, then there should an object that hides the data (details) and expose functions that manipulate that data. But there is no functions in Question object and it means that there is no real abstraction behind.
Finally, It means that Question entity most likely is a plain data structure with public properties and could be defined as struct Question.
Having this Question struct now, you can define quizzes as Quiz<Question> and use it to save and retrieve the data.
In addition, I think it worth to point out two things which could simplify and potentially improve design and implementation:
Why does SQLiteManager knows something about concrete question (depends on QuestionProtocol)? I think it makes sense to introduce some generic DBObject or at least plain dictionary [String: Any] which SQLiteManager would know how process and then insert. Then Repository could transform Question data structure into DBObject on some level of composition and pass it to SQLiteManager.
When using generics, in most cases there is no need to define additional type: T.Type parameter. Once generic is defined you can use it as [T], T.init, etc. If you still need a metatype (T.Type) you can get by T.self.
Hope this helps!
Update:
There is great example of Quiz app created with TDD and modular design: Quiz App. There is also a video series explaining design and creation process step by step.
How can I store an array containing these questions without knowing
the type?
To my knowledge you can't. As rraphael pointed out in his comment generics aren't resolved at runtime. Furthermore Arrays in swift are designed to hold a single type:
Specifically, you use the Array type to hold elements of a single type, the array’s Element type.
So whatever you do you'll have either an array of Any or maybe QuestionProtocol but nothing more dynamic than that : the type will be resolved at compilation time
You may be able to redesign your QuestionProtocol to suit your needs but without any information on the different types of question it's a bit difficult to help you more since it is an architecture matter.
You can use enum with associated values for describing types. For example:
struct QuestionTypeA { }
struct QuestionTypeB { }
struct QuestionTypeC { }
enum Question {
case typeA(question: QuestionTypeA)
case typeB(question: QuestionTypeB)
case typeC(question: QuestionTypeC)
}
And then:
public class Quiz {
private let questions : Question
private let name : String
...
And store an array of Quiz without generic
private var anyquizzes = [Quiz]()
You wouldn't be able to store a Quiz<T> and a Quiz<U> in the same array using either of those types. They're just not the same type.
If you have an Array<QuizProtocol>, you can just match against your known types in a switch-case statement:
var quizzes: [QuizProtocol] = ...
for quiz in quizzes {
switch quiz {
case let someQuiz as SomeQuiz:
...
case let someOtherQuiz as SomeOtherQuiz:
...
default:
... // couldn't cast to any known type; do some fallback logic
....
}
}
where SomeQuiz and SomeOtherQuiz conform to QuizProtocol (though strictly speaking, you could match against any type).
TLDR: Using many Swift protocols in a large project is great for testing and SOLID coding, but I’m getting function clutter and invalid redeclaration clashes. What’s the best practice to avoid these problems in Swift while making heavy use of protocols?
Concretely, I want to use protocols to separate responsibilities from view classes such that they don’t need to know anything about the data models used to “decorate” them. But this is creating a lot of functions for my data model classes that are exposed throughout the app, and that are starting to clash with other protocols.
As an example, let’s say I want to set up my custom tableview cell from a certain data model in my project. Let’s call it MyDataModel. I create a decorating protocol like so:
protocol MyCellDecorator {
var headingText: String?
var descriptionText: String?
}
And then my cell is like
class MyCell: UITableViewCell {
#IBOutlet weak var headingLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
func setup(fromDecorator decorator: MyCellDecorator) {
headingLabel.text = decorator.headingText
descriptionLabel.text = decorator.descriptionText
}
}
Now all I need to do is provide an extension from my data model class implementing MyCellDecorator, providing headingText and descriptionText, and I can plug my data model object into setup(fromDecorator:).
extension MyDataClass: MyCellDecorator {
var headingText: String {
return “Some Heading“
}
var descriptionText: String {
return “Some Description“
}
}
This makes the cell easy to test; it clearly separates responsibilities, MyCell and the UIViewController driving it now need to know nothing about MyDataModel..
BUT now MyDataModel has two extra properties, headingText, and descriptionText - available everywhere. But MyDataModel already extends 10 other decorator protocols for specific UI throughout my project, and it so happens that another protocol already defines headingText, so I get the compilation error “invalid redeclaration of ‘headingText’”.
With all of this headache, I decide to quit, go ahead and just pass MyDataModel into MyCell, it all compiles but I lose all the aforementioned advantages.
What are good ways, in such a big project as this, to score those sweet sweet protocol wins, without cluttering up my class’s function tables and having redeclaration clashes between different extensions?
I agree with where Andrey is going, but I believe it's even simpler. You just need decorator types, and the way you've described them, they should be able to be simple structs, with no inherent need for protocols.
struct MyCellDecorator {
let headingText: String
let descriptionText: String
}
(I've made these non-optional, and I strongly recommend that unless you have a UI distinction between "empty string" and "none.")
Extensions work almost exactly as you've done before:
extension MyDataClass {
func makeMyCellDecorator() -> MyCellDecorator {
return MyCellDecorator(headingText: "Some Heading",
description: "Some Description")
}
}
In some cases, you may find that model objects have very consistent ways that they generate a decorator. That's a place where protocols will allow you to extract code such as:
protocol MyCellDecoratorConvertible {
var headingText: String { get }
var descriptionText: String { get }
}
extension MyCellDecoratorConvertible {
func makeMyCellDecorator() -> MyCellDecorator {
return MyCellDecorator(headingText: headingText,
description: description)
}
}
This example captures the case where the cell happens to have exactly the right names already. Then you just have to add MyCellDecoratorConvertible and the property comes for free.
The key point to all of this is that rather than have model.headingText you'll have model.makeMyCellDecorator().headingText, which will address your explosion of properties.
Note this will generate a new Decorator every time you access it, which is why I'm using a make (factory) naming convention. There are other approaches you might consider, such as an AnyMyCellDecorator type eraser (but I'd start simple; these are likely very small types and copying them is not expensive).
You can split the UI into modules and use internal extensions. Those will not appear in other modules, which will prevent myCellDecorator from showing up everywhere. If more convenient, you can put the myCellDecorator extensions in the same file with MyCell and mark them private.
Since this is a large, existing code-base, I highly recommend allowing any existing code duplication to drive your design. There is no one pattern that is ideal for all systems. It's not even necessary to have every decorator follow the exact same pattern (in some cases it may make more sense to use a protocol; in others a struct; in others you might want both). You can create a pattern "language" without boxing yourself into a world where you're creating extra protocols just because "that's the pattern."
But MyDataModel already extends 10 other decorator protocols for specific UI throughout my project, and it so happens that another protocol already defines headingText, so I get the compilation error “invalid redeclaration of ‘headingText’”.
I think this is the main pitfall here, that you use single model to provide data for different parts of the application. If we are talking about the MVC pattern, then the single model should only provide data for corresponding controller. I think in this case there will be much less protocol adoptions in the model.
On other hand you can try to split functionality inside of the model:
For instance, if we have
protocol CellDecorator {
var headingText: String?
var descriptionText: String?
init(withSomeData data: ...) {}
}
we could create something like this
class MyCellDecorator: CellDecorator {
var headingText: String?
var descriptionText: String?
}
class MyDataClass {
lazy var cellDecorator: CellDecorator = {
return CellDecorator(withSomeData: ...)
}
}
One struct-based way I've played with is this:
Instead of extending MyDataClass, I create a simple struct (which can be fileprivate to the view controller class, or not) that looks like:
struct MyDataClassCellDecorator: MyCellDecorator {
var headingText: String? {
return "Some heading with \(data.someText)"
}
var descriptionText: String? {
return data.someOtherText
}
let data: MyDataClass
}
This way MyCell can still use the protocol to decorate itself, MyDataClass doesn't need any extension at all, and in whatever access scope I want it I get a struct that does the decorating logic for MyDataClass + MyCellDecorator.
I'm building a generic API for my Swift applications. I use CoreData for local storage and CloudKit for cloud synchronization.
in order to be able to work with my data objects in generic functions I have organized them as follows (brief summary):
Objects that go in the CoreData Database are NSManagedObject instances that conform to a protocol called ManagedObjectProtocol, which enables conversion to DataObject instances
NSManagedObjects that need to be cloud synced conform to a protocol called CloudObject which allows populating objects from records and vice-versa
Objects I use in the graphic layer of my apps are NSObject classes that conform to the DataObject protocol which allows for conversion to NSManagedObject instances
an object of a specific class. What I would like this code to look like is this:
for record in records {
let context = self.persistentContainer.newBackgroundContext()
//classForEntityName is a function in a custom extension that returns an NSManagedObject for the entityName provided.
//I assume here that recordType == entityName
if let managed = self.persistentContainer.classForEntityName(record!.recordType) {
if let cloud = managed as? CloudObject {
cloud.populateManagedObject(from: record!, in: context)
}
}
}
However, this gives me several errors:
Protocol 'CloudObject' can only be used as a generic constraint because it has Self or associated type requirements
Member 'populateManagedObject' cannot be used on value of protocol type 'CloudObject'; use a generic constraint instead
The CloudObject protocol looks as follows:
protocol CloudObject {
associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
var recordID: CKRecordID? { get }
var recordType: String { get }
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudManagedObject>
func populateCKRecord() -> CKRecord
}
Somehow I need to find a way that allows me to get the specific class conforming to CloudObject based on the recordType I receive. How would I Best go about this?
Any help would be much appreciated!
As the data formats of CoreData and CloudKit are not related you need a way to efficiently identify CoreData objects from a CloudKit record and vice versa.
My suggestion is to use the same name for CloudKit record type and CoreData entity and to use a custom record name (string) with format <Entity>.<identifer>. Entity is the record type / class name and identifier is a CoreData attribute with unique values. For example if there are two entities named Person and Event the record name is "Person.JohnDoe" or "Event.E71F87E3-E381-409E-9732-7E670D2DC11C". If there are CoreData relationships add more dot separated components to identify those
For convenience you could use a helper enum Entity to create the appropriate entity from a record
enum Entity : String {
case person = "Person"
case event = "Event"
init?(record : CKRecord) {
let components = record.recordID.recordName.components(separatedBy: ".")
self.init(rawValue: components.first!)
}
}
and an extension of CKRecord to create a record for specific record type from a Entity (in my example CloudManager is a singleton to manage the CloudKit stuff e.g. the zones)
extension CKRecord {
convenience init(entity : Entity) {
self.init(recordType: entity.rawValue, zoneID: CloudManager.shared.zoneID)
}
convenience init(entity : Entity, recordID : CKRecordID) {
self.init(recordType: entity.rawValue, recordID: recordID)
}
}
When you receive Cloud records extract the entity and the unique identifier. Then try to fetch the corresponding CoreData object. If the object exists update it, if not create a new one. On the other hand create a new record from a CoreData object with the unique record name. Your CloudObject protocol widely fits this pattern, the associated type is not needed (by the way deleting it gets rid of the error) but add a requirement recordName
var recordName : String { get set }
and an extension to get the recordID from the record name
extension CloudObject where Self : NSManagedObject {
var recordID : CKRecordID {
return CKRecordID(recordName: self.recordName, zoneID: CloudManager.shared.zoneID)
}
}
Swift is not Java, Swift is like C++, associatedType is a way of writing a generic protocol, and generics in Swift means C++ template.
In Java, ArrayList<String> is the same type as ArrayList<Integer>!!
In Swift (and C++) , Array<String> is NOT the same type as Array<Int>
So, you can't take an array of Arrays for example, you MUST make it an array of Array<SpecificType>
What did Apple do to make you able to make a "type-erased" array for example?
They made Array<T> extend Array<Any>.
If you want to immitate this in your code, how?
protocol CloudObject {
// Omitted the associatedtype (like you already done as in the replies)
//associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
var recordID: CKRecordID? { get }
var recordType: String { get }
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<NSManagedObject & ManagedObjectProtocol>
func populateCKRecord() -> CKRecord
}
Then make the "generic protocol", this would be useful in safely and performance programming when the resolving of protocol is known at compile time
protocol CloudObjectGeneric: CloudObject {
// Generify it
associatedtype CloudManagedObject: NSManagedObject, ManagedObjectProtocol
// You don't need to redefine those, those are not changed in generic form
//var recordID: CKRecordID? { get }
//var recordType: String { get }
//func populateCKRecord() -> CKRecord
// You need a new function, which is the generic one
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject>
}
Then make the generic protocol conform to the non-generic one, not to need writing 2 populateManagedObject functions in each implementation
extension CloudObjectGeneric {
// Function like this if the generic was a parameter, would be
// straightforward, just pass it with a cast to indicate you
// are NOT CALLING THE SAME FUNCTION, you are calling it from
// the generic one, but here the generic is in the return, so
// you will need a cast in the result.
func populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext) -> Promise<CloudObject> {
let generic = populateManagedObject(from record: CKRecord, in context: NSManagedObjectContext)
return generic as! Promise<CloudObject> // In Promises I think this
// will NOT work, and you need .map({$0 as! CloudObject})
}
}