Below is my method in which there is fetch I make on a Managed object Class Appointment. I need to use same function for other similar managed object Classes. How do I pass different "Class" as parameter every time as I need. And also use it to fetch as I have currently "Appointment" Class. I might need to use Generics may be. Dont know how though.
func getAppointmentArray(aPredicate : String , aModel : Any) -> [Any]
{
var apptArr = [Any]()
let fetchRequest = NSFetchRequest<Appointment>(entityName: "Appointment")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = NSPredicate(format: aPredicate)
do{
let records = try managedObjectContext.fetch(fetchRequest)
if let records = records as? [NSManagedObject]{
if !records.isEmpty{
print("coreData apptmnt result : \(records)")
var appointment : Appointment?
for obj in records
{
}
}else{
print("No records found")
apptArr = []
}
}
}catch{
print("Error")
apptArr = []
}
return apptArr
}
The good folks at Objc.io provide a really good approach for this. First declare a protocol which inherits 'NSFetchRequestResult' protocol as below.
protocol Managed: class, NSFetchRequestResult {
static var entityName: String { get }
}
Now we can provide a very convenient protocol extension for our protocol 'Managed'.
We do the check 'Self: NSManagedObject' as we want the static method entity() of the NSManagedObject class to get the 'NSEntityDescription' describing the entity associated with our class. Particularly this way we get the entity name dynamically(and conveniently too) for all our ManagedObjects that conform to our protocol.
extension Managed where Self: NSManagedObject {
static var entityName: String { return entity().name! }
}
We now improve the protocol extension by providing a method which conveniently creates a fetch request and then calls a configuration block which might be used to configure the created fetch request by whoever calls it. At the end of this method we do a fetch using the created request.
extension Managed where Self: NSManagedObject {
static var entityName: String { return entity().name! }
//Continued
static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest<Self>) -> ()) -> [Self] {
let request = NSFetchRequest<Self>(entityName: Self.entityName)
configurationBlock(request)
return try! context.fetch(request)
}
}
As you can see we do the following things here:
We make good use of protocols and protocol extensions for making our life easy.
We get the entity name without needing to write a method for each concrete managed object class that we might create. This is reusable for every managed object class that will conform to 'Managed'
The fetch method that we wrote makes use of the dynamic and convenient entityName.
The fetch method again makes use of Self which is implementation independent here. This way we make FetchRequests which are generic in itself.
We provide a convenient way to configure the request to whoever calls this method.
And at atlast we return result after fetching which is also dynamic [Self]
To see our protocol in action we can do this for your case:
class Appointment: NSManagedObject, Managed{
//properties for attributes
//etc...
//Will I get free implementation for entity name and a fetch method
//without writing extra code ?
//Yes why not
}
Testing our hard earned knowledge:
let aPredicate = "......
let context: NSManagedObjectContext.....
let appointments = Appointment.fetch(in: context) { fetchRequest in
//Configuration code like adding predicates and sort descriptors
fetchRequest.predicate = NSPredicate(format: aPredicate)
}
You can use the same pattern for any other ManagedObjects if they conform to the protocol. Eg a Doctor ManagedObject subclass conforming to our Managed protocol:
let aPredicate = "......
let context: NSManagedObjectContext.....
let doctors = Doctor.fetch(in: context) { fetchRequest in
//Configuration code like adding predicates and sort descriptors
fetchRequest.predicate = NSPredicate(format: aPredicate)
}
for the generic you can do something like this:
class FetchingDataHandler<T>{
func getAppointmentArray<T>(forClass : T, aPredicate : String , aModel : Any) -> [Any]
{
}
}
Related
I want to state that I am fairly new to Swift and I was exploring CoreData concepts. I tried to test with a ToDo list app which on welcome screen shows user created task categories and upon clicking on any category the user will see all the tasks in that group. I tried to create a generic class something like ToDoListViewController<T: NSManagedObject> and to implement functionality available for both view controllers(CategoryViewController, TaskViewController). In that class I created a function loadItems which takes a predicate as an argument and populates the page with items from a fetch request. So the code roughly looks like this:
class ToDoListViewController<T: NSManagedObject>: UITableViewController {
func loadItems(predicate: NSPredicate? = nil) {
let request: NSFetchRequest<T> = T.fetchRequest()
if predicate != nil {
request.predicate = predicate!
}
do {
let items = try context.fetch(request)
// Do something
} catch {
print("Error fetching items from context \(error)")
}
}
}
The issue is that when I try to compile I get error:
Cannot assign value of type NSFetchRequest<NSFetchRequestResult> to type NSFetchRequest<T>
when assigning request variable. But if I force cast NSFetchRequest<NSFetchRequestResult> to NSFetchRequest<T> like this:
let request: NSFetchRequest<T> = T.fetchRequest() as! NSFetchRequest<T>
everything works fine. Since The NSManagedObject documentation clearly states that it conforms to NSFetchRequestResult protocol, why do I have to force cast NSFetchRequest<NSFetchRequestResult> to NSFetchRequest<T>?
You have to cast the type because the generic type of NSFetchRequest – which is constrained to NSFetchRequestResult – can also be NSDictionary or NSNumber or NSManagedObjectID.
Rather than making a generic type more generic I recommend to use a protocol with associated types like described here
OK, first, I know that there is no such thing as AnyRealmObject.
But I have a need to have something the behaves just like a Realm List, with the exception that any kind of Realm Object can be added to the list -- they don't all have to be the same type.
Currently, I have something like this:
enter code here
class Family: Object {
var pets: List<Pet>
}
class Pet: Object {
var dog: Dog?
var cat: Cat?
var rabbit: Rabbit?
}
Currently, if I wanted to add in, say, Bird, I'd have to modify the Pet object. I don't want to keep modifying that class.
What I really want to do is this:
class Family: Object {
var pets: List<Object>
}
Or, maybe, define a Pet protocol, that must be an Object, and have var pets: List<Pet>
The point is, I want a databag that can contain any Realm Object that I pass into it. The only requirement for the databag is that the objects must be Realm Objects.
Now, since Realm doesn't allow for this, how could I do this, anyway? I was thinking of creating something like a Realm ObjectReference class:
class ObjectReference: Object {
var className: String
var primaryKeyValue: String
public init(with object: Object) {
className = ???
primaryKeyValue = ???
}
public func object() -> Object? {
guard let realm = realm else { return nil }
var type = ???
var primaryKey: AnyObject = ???
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
}
The stuff with the ??? is what I'm asking about. If there's a better way of doing this I'm all ears. I think my approach is ok, I just don't know how to fill in the blanks, here.
(I'm assuming that you are writing an application, and that the context of the code samples and problem you provided is in terms of application code, not creating a library.)
Your approach seems to be a decent one given Realm's current limitations; I can't think of anything better off the top of my head. You can use NSClassFromString() to turn your className string into a Swift metaclass object you can use with the object(ofType:...) API:
public func object() -> Object? {
let applicationName = // (application name goes here)
guard let realm = realm else { return nil }
guard let type = NSClassFromString("\(applicationName).\(className)") as? Object.Type else {
print("Error: \(className) isn't the name of a Realm class.")
return nil
}
var primaryKey: String = primaryKeyValue
return realm.object(ofType: type, forPrimaryKey: primaryKey)(
}
My recommendation is that you keep things simple and use strings exclusively as primary keys. If you really need to be able to use arbitrary types as primary keys you can take a look at our dynamic API for ideas as to how to extract the primary key value for a given object. (Note that although this API is technically a public API we don't generally offer support for it nor do we encourage its use except when the typed APIs are inadequate.)
In the future, we hope to offer enhanced support for subclassing and polymorphism. Depending on how this feature is designed, it might allow us to introduce APIs to allow subclasses of a parent object type to be inserted into a list (although that poses its own problems).
This may not be a complete answer but could provide some direction. If I am reading the question correctly (with comments) the objective is to have a more generic object that can be the base class for other objects.
While that's not directly doable - i.e. An NSObject is the base for NSView, NSString etc, how about this...
Let's define some Realm objects
class BookClass: Object {
#objc dynamic var author = ""
}
class CardClass: Object {
#objc dynamic var team = ""
}
class MugClass: Object {
#objc dynamic var liters = ""
}
and then a base realm object called Inventory Item Class that will represent them
class InvItemClass: Object {
#objc dynamic var name = ""
#objc dynamic var image = ""
#objc dynamic var itemType = ""
#objc dynamic var book: BookClass?
#objc dynamic var mug: MugClass?
#objc dynamic var card: CardClass?
}
then assume we want to store some books along with our mugs and cards (from the comments)
let book2001 = BookClass()
book2001.author = "Clarke"
let bookIRobot = BookClass()
bookIRobot.author = "Asimov"
let item0 = InvItemClass()
item0.name = "2001: A Space Odyssey"
item0.image = "Pic of Hal"
item0.itemType = "Book"
item0.book = book2001
let item1 = InvItemClass()
item1.name = "I, Robot"
item1.image = "Robot image"
item1.itemType = "Book"
item1.book = bookIRobot
do {
let realm = try Realm()
try! realm.write {
realm.add(item0)
realm.add(item1)
}
} catch let error as NSError {
print(error.localizedDescription)
}
From here, we can load all of the Inventory Item Objects as one set of objects (per the question) and take action depending on their type; for example, if want to load all items and print out just the ones that are books.
do {
let realm = try Realm()
let items = realm.objects(InvItemClass.self)
for item in items {
switch item.itemType {
case "Book":
let book = item.book
print(book?.author as! String)
case "Mug":
return
default:
return
}
}
} catch let error as NSError {
print(error.localizedDescription)
}
As it stands there isn't a generic 'one realm object fits all' solution, but this answer provides some level of generic-ness where a lot of different object types could be accessed via one main base object.
I have a Models class defined like this:
class BaseModel: Object {
var data: JSON = JSON.null
convenience init(_ data: JSON) {
self.init()
self.data = data
}
override static func ignoredProperties() -> [String] {
return ["data"]
}
}
class RecipeModel: BaseModel {
dynamic var title: String {
get { return data["fields"]["title"].stringValue }
set { self.title = newValue }
}
... more vars ...
var ingredients: List<IngredientsModel> {
get {
let ingredients = List<IngredientsModel>()
for item in data["fields"]["ingredients"] {
ingredients.append(IngredientsModel(item.1))
}
return ingredients
}
set { self.ingredients = newValue }
}
}
class IngredientsModel: BaseModel {
dynamic var text: String {
get { return data["text"].stringValue }
set { self.text = newValue }
}
... more vars ...
}
And I would like to use it something like this:
Api.shared.fetchAllEntries().call(onSuccess: {response in
print(response.json)
let realm = try! Realm()
try! realm.write {
realm.deleteAll()
}
for item in response.json["items"].arrayValue {
let recipe = RecipeModel(item)
try! realm.write {
realm.add(recipe)
}
}
}, onError: {
print("error")
})
So basically the idea is to just pass the whole JSON to the initial RecipeModel class, and it should parse it out and create the objects I need in the Realm database. It works quite well except for the nested list of IngredientsModel. They do not get added to the realm database.
What I see as a potential problem is that I call self.init() before I call self.data in the convenience init, but I do not see any way to work around this. Do you guys please know how I could achieve that also the IngredientsModel would have its contents set up properly and I would have a list of ingredients in the RecipeModel?
Your current implementation doesn't work, because you are not calling the getter/setter of ingredients in the init method of RecipeModel and hence the IngredientsModel instances are never persisted in Realm.
Moreover, using a computed property as a one-to-many relationship (Realm List) is a really bad idea, especially if you are parsing the results inside the getter for this property. Every time you call the getter of ingredients, you create new model objects instead of just accessing the existing ones that are already stored in Realm, but you are never deleting the old ones. If you were actually saving the IngredientsModel instances to Realm (which you don't do at the moment as mentioned above) you would see that your database is full of duplicate entries.
Your whole approach seems really suboptimal. You shouldn't store the unparsed data object in your model class and use computed properties to parse it. You should parse it when initializing your models and shouldn't store the unparsed data at all. You can use the ObjectMapper library for creating Realm objects straight away from the JSON response.
I'm finding the documentation on the new codegen feature in the Core Data Editor in Xcode 8 a bit sparse.
This is a "in Objective-C, I would...." kind of question.
I'm trying to declare a protocol that has 2 methods:
#property (strong, readonly) NSNumber *serverId;
+ (instancetype)objectWithServerId:(NSNumber*)serverId inContext:(NSManagedObjectContext*)moc;
In Objective-C, I would use mogenerator to declare that the baseclass generated should be "MyBaseClass".
and in that baseclass I can implement that class method once. In the Core Data editor, I just have to make sure my entity has that attribute. In the 'human readable' file, I would declare that it conforms to that protocol, and because it inherits from that baseclass (which is basically abstract), it can call that class method listed above.
I think with strong typing, this may not be possible. I have made it work, but each subclass I create (which uses the Xcode generated Extensions) has to implement this method, whereas I would prefer to write the method once as a generic.
In Swift, I added that attribute to the entity (no parent, therefore it is a subclass from NSManagedObject), and did this:
protocol RemoteObjectProtocol {
var serverId: Int64 {get}
static func object<T: NSManagedObject>(WithServerId serverId: Int64, context: NSManagedObjectContext!) -> T?
}
import CoreData
#objc(TestObject)
class TestObject: NSManagedObject {
}
extension TestObject: RemoteObjectProtocol {
// by nature of it being in the generated core data model, we just need to declare its conformance.
static func object<T: NSManagedObject>(WithServerId serverId: Int64, context: NSManagedObjectContext!) -> T? {
// IF YOU FIND A BETTER WAY TO SOLVE THIS, PLEASE DO!
// I would have loved to use a baseclass RemoteObject: NSManagedObject, where you can just implement this
// Method. But there was no way for it to play nicely with the CoreData Editor
// in addition, the convenience method generated by Xcode .fetchRequest() only seems to work outside of this extension.
// Extensions can't make use of other extensions
// So we create the fetch request 'by hand'
let fetchRequest = NSFetchRequest<TestObject>(entityName: "TestObject")
fetchRequest.predicate = NSPredicate(format: "serverId == %i", serverId)
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "serverId", ascending: true)]
fetchRequest.fetchLimit = 1
do {
let fetchedObjects = try context.fetch(fetchRequest)
return fetchedObjects.first as! T?
} catch {
log.warning("Failed to fetch object with serverId:\(serverId) error:\(error)")
}
return nil
}
}
Codegen classes can conform to protocols. And, Swift protocols can provide a default implementation of any functions.
So, in theory at least, it should be simpler to achieve what you're doing in Swift than in Objective-C. But, getting the syntax right can be a bit tricky.
The main issues are the following:
Preface the protocol declaration with #objc so that it can play with CoreData and NSManagedObject
The function's implementation is not included in the protocol itself, but rather in an extension
Constrain the extension, using a where clause, to apply only to subclasses of NSManageObject (as it should). And, by doing so, the extension receives NSManageObject's functionality
Finally, as always, so as not to modify the Xcode codegen (that's in the Derived Data folder), conform to the protocol in an extension for each of the NSManagedObject subclasses.
So, for the protocol, the following code should do it:
import CoreData
#objc protocol RemoteObject {
var serverId: Int64 { get }
}
extension RemoteObject where Self: NSManagedObject {
static func objectWithServerId(_ serverId: Int64, context: NSManagedObjectContext) -> Self? {
let fetchRequest: NSFetchRequest<Self> = NSFetchRequest(entityName: Self.entity().name!)
let predicate = NSPredicate(format: "serverId == %d", serverId)
let descriptor = NSSortDescriptor(key: #keyPath(RemoteObject.serverId), ascending: true)
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [descriptor]
fetchRequest.fetchLimit = 1
do {
let results = try context.fetch(fetchRequest)
return results.first
} catch {
print("Failed to fetch object with serverId:\(serverId) error:\(error)")
}
return nil
}
}
And, as you noted, every entity already has the serverId attribute. So, you need only declare that it conforms to the protocol:
extension MyCoreDataEntity: RemoteObject {
}
ASIDE
Note that for some reason the compiler rejects the somewhat simpler line:
let fetchRequest: NSFetchRequest<Self> = Self.fetchRequest()
That simpler line generates the following error: Cannot convert value of type 'NSFetchRequest<NSFetchRequestResult>' to specified type 'NSFetchRequest<Self>'. But, for some reason, the above workaround doesn't.
Any ideas on why this occurs are very welcome.
Is it possible for a generic method to infer its type based on the class in which it is being executed? I use CoreData NSManagedObject models to store and retrieve local data, and have managed to make everything generic in an easy to read and usable way, except for in one place. If a user wishes to query the local database to fetch a list of objects, he would write the following line:
let posts: [Post] = Post.all()
This will properly return "all" Post objects in the database, but the syntax requires that the type be defined ([Post]) on top of calling the method from the Post class itself (Post.all()), which feels unnecessarily redundant. Is there any way to define the generic type simply by calling the all() method from the Post class? I imagine I could just create global functions for fetching data, like so:
let posts: [Post] = all()
This doesn't feel nearly as readable as it would be if the syntax was as follows:
let posts = Post.all()
The point of trying to improve this is so that any developers who pick up this project can quickly learn the structure and style without much effort. Also, this will hopefully increase general code readability in the future, regardless of if someone is working on it or just reading it for some other reason.
For more insight, here is a bit more information about the current structure:
//Model.swift - The model base class. All models extend this class.
class Model: NSManagedObject {
/**
Some other stuff here
**/
//MARK: Fetch
internal class func fetch<T: Model>(predicate: NSPredicate? = nil) -> [T]? {
do {
if let request = NSFetchRequest.FromEntityName(self.entityName) { //Get entity with the name defined in the current class
request.predicate = predicate
if let result = try self.context?.executeFetchRequest(request) as? [T] {
return result
}
}
}
catch let error as NSError {
Log.Error("\(error)")
}
return nil
}
//MARK: Fetch general
class func all<T: Model>() -> [T]? {
if let result: [T] = self.fetch() {
return result
}
Log.warning("No \(self.entityName) found")
return nil
}
}
//Post.swift - An example model class. Extends Model.swift
class Post: Model {
//some fields
}
//Example view controller
class ViewController: UIViewController {
override func viewDidLoad() {
let posts: [Post] = Post.all()
//do stuff
}
}
If anyone has an idea about then please let me know. All help is appreciated!
In the general case, the typical way for a class method to return "type of the class" even for subclasses is to use protocol extensions and the Self type. Here's an example that boils your approach down to the bare minimum to make the type checking work the way you want:
// define a protocol
protocol ModelType {}
// create a static method on the protocol that returns [Self]
extension ModelType where Self: NSManagedObject {
static func all() -> [Self]? {
return [Self]() // do your fetch here
}
}
// conform to the protocol in your class hierarchy
class Model: NSManagedObject, ModelType {}
class Post: Model {}
let posts = Post.all()
// implicit type of `posts` is `[Post]?`
Note that all() should be provided by the protocol extension, but not a requirement of the protocol. If you declare all() inside protocol ModelType, then you can't make it use dynamic dispatch, which is necessary if it's to use a dynamic type.
Also, note that in Swift 3 (and macOS 10.12 / iOS 10 / tvOS 10 / watchOS 3), Core Data itself defines some Swift API shortcuts that replace some of the ones you've defined for yourself. Note this example from What's New in Core Data:
func findAnimals() {
context.performAndWait({
let request = Animal.fetchRequest // implicitly NSFetchRequest<Animal>
do {
let searchResults = try request.execute()
// use searchResults ...
} catch {
print("Error with request: \(error)")
}
})
}
Finally, some commentary on your choice of style...
fyi I capitalize the first letter in all static/class methods just as a convention
The point of trying to improve this is so that any developers who pick up this project can quickly learn the structure and style without much effort. Also, this will hopefully increase general code readability in the future
I'm not sure that breaking from language-standard conventions (like the lowercase method names recommended in the Swift 3 API Guidelines) is very compatible with your goal of making it easy for other developers new to your codebase to read and participate.