Convert request Function to Generic type - ios

I am trying to convert my below fetch request code from core data to generic type.
let request = NSPredicate(format: "name == %# AND password == %# AND type == %#", "admin", "admin", "admin")
let fetchReq : NSFetchRequest = UserRegistration.fetchRequest()
fetchReq.predicate = request
let adminDetail :[UserRegistration] = DatabaseEngine.fetch(fetchRequest: fetchReq)!
Converted so far:
extension UIViewController{
class func getData<T: NSManagedObject>(req: NSPredicate) -> T{
let fetchReq : NSFetchRequest = T.fetchRequest()
fetchReq.predicate = req
return DatabaseEngine.fetch(fetchRequest: fetchReq as! NSFetchRequest<NSManagedObject>)! as! T
}
}
DatabaseEngine.fetch function.
static func fetch (fetchRequest: NSFetchRequest<T> = NSFetchRequest(), context:NSManagedObjectContext = kApplicationDelegate.managedObjectContext) -> [T]? {
let entity = NSEntityDescription.entity(forEntityName: typeName(some:T.self)
, in:context)
// Configure Fetch Request
fetchRequest.entity = entity
do {
return try context.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [T]
} catch {
//let fetchError = error as NSError
// return nil
}
return nil
}
But no results any more. Anybody help me to convert this code with few explaining lines. Ans will be appreciated sure.

According to my comment I recommend to use a protocol with extension for example
protocol Fetchable
{
associatedtype FetchableType: NSManagedObject = Self
static var entityName : String { get }
static var managedObjectContext : NSManagedObjectContext { get }
static func objects(for predicate: NSPredicate?) throws -> [FetchableType]
}
extension Fetchable where Self : NSManagedObject
{
static var entityName : String {
return String(describing: self)
}
static var managedObjectContext : NSManagedObjectContext {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
static func objects(for predicate: NSPredicate?) throws -> [FetchableType]
{
let request = NSFetchRequest<FetchableType>(entityName: entityName)
request.predicate = predicate
return try managedObjectContext.fetch(request)
}
}
Change (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext to the reference to your managed object context.
Make all NSManagedObject subclasses adopt Fetchable. There is no extra code needed in the subclasses.
Now you can get the data with
do {
let predicate = NSPredicate(format: ...
let objects = try MyEntity.objects(for: predicate)
} catch {
print(error)
}
That's all, objects are [MyEntity] without any type casting and always non-optional on success.
The protocol is easily extendable by default sorting descriptors, sorting directions etc.

Related

Swift generic functions issues. Decrease duplicating code

I want to understand how can decrease amount of duplicated code. I have two almost the same functions. The differences are next:
firs function returns array of [ExerciseEntity] and second function returns array of [WorkoutEntity]
func replaceExercisesIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [ExerciseEntity] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<ExerciseEntity>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<ExerciseEntity>(),
sourceArray: jsonObjects)
return managedObjects
}
func replaceWorkoutsIdentifiers(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [WorkoutEntity] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<WorkoutEntity>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<WorkoutEntity>(),
sourceArray: jsonObjects)
return managedObjects
}
This is a similar question related to how to use generic function I asked before.
I implemented this in my code but:
func importArray<T: ImportableUniqueObject>(from exercisesDict: [[String: Any]], transaction: BaseDataTransaction) -> [T] where T.ImportSource == [String: Any] {
let managedObjects = try? transaction.importUniqueObjects(Into<T>(), sourceArray: jsonObjects)
}
But here is few things, with T type
First - I can't add this code: editObject?.id = identifier
as there is no id in T type.
Second when I debug these generic functions debugger every time crashes:
Message from debugger: The LLDB RPC server has crashed. The crash log is located in ~/Library/Logs/DiagnosticReports and has a prefix 'lldb-rpc-server'. Please file a bug and attach the most recent crash log.
If interesting here is a file with log. I have not submitted it yet.
For sure I can add a lot of prints to track behavior, though it's a but annoying) But main task is to get rid of duplication.
Try this (I have not tested):
protocol MyProtocol {
var id: Int { get set }
}
struct ExerciseEntity {
var id: Int
}
struct WorkoutEntity {
var id: Int
}
func replaceWorkoutsIdentifiers<T: MyProtocol>(from jsonObjects: [[String: Any]], transaction: BaseDataTransaction) -> [T] {
for jsonObject in jsonObjects {
if let mobileLocalId = jsonObject["mobileLocalId"] as? String {
if mobileLocalId.contains("<x-coredata://") {
if let managedObject = try? transaction.fetchOne(From<T>()
.where(
format: "%K == %#",
#keyPath(BaseMO.id),
mobileLocalId)
) {
let editObject = transaction.edit(managedObject)
if let identifier = jsonObject["id"] as? String {
editObject?.id = identifier
}
}
}
}
}
let managedObjects = try! transaction.importUniqueObjects(
Into<T>(),
sourceArray: jsonObjects)
return managedObjects as! T
}
Using:
let array: [ExerciseEntity] = replaceWorkoutsIdentifiers(from jsonObjects: ..., transaction: ...)

swift mirror return class name in class function

I'm having a class function and in that class function I want the name of the class thats extending this abstract entity class.
I found the Mirror type could do this so I have something like this:
class AbstractEntity {
class func findOrCreateEntity() -> AnyObject? {
print("Name: \(NSStringFromClass(Mirror(reflecting: self)))")
}
}
The only problem is that for example a class Car extends this AbstractEntity and it calls the method this prints Car.Type, this is correct but I don't want the .Type extension. Is there any way to just get the class name?
I really want to do it the swift way if it's possible. I know of the exnteions of NSObject and then the NSStringFromClass thing..
For clarity:
class AbstractEntity: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
class func findOrCreateEntityWithUUID(entityName: String, uuid: String, context: NSManagedObjectContext) -> AnyObject? {
let request = NSFetchRequest(entityName: entityName)
request.returnsObjectsAsFaults = false;
let predicate = NSPredicate(format: "uuid = %#", uuid)
request.predicate = predicate
do {
var results = try context.executeFetchRequest(request)
if results.count > 0 {
return results[0]
} else {
let entity = NSEntityDescription.entityForName(entityName, inManagedObjectContext: context)
let abstractEntity = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: context)
abstractEntity.setValue(uuid, forKey: "uuid")
return abstractEntity
}
} catch let error as NSError {
DDLogError("Fetch failed: \(error.localizedDescription)")
}
return nil
}
}
That's my AbstractEntity and every entity in my model extends that. So I have one place where I want my logic.
Now for example I have a Car entity and I want to do Car.find... and it returns me a Car. That why I (think) I need the class name so that I can use it to fetch it with the NSFetchRequest(entityName: entityName). I now pass in the entityName I want to fetch but I think this is ugly because when you do Car.find.. you know you want a car.
Really i don't get your question, but according to my requirement you need following functionality.
class AbstractEntity {
class func findOrCreateEntity() {
let str = __FILE__
let fullNameArr = str.componentsSeparatedByString("/")
print("Name: \(fullNameArr.last!)")
}
}

Value of type 'Self.ManageableType' has no member 'uid'

This is my code, which builds in XCode 7.2.1.
When I try to build the project in XCode 7.3 beta 2, I got the error "Value of type 'Self.ManageableType' has no member 'uid'"
protocol Manageable {
typealias ManageableType : NSManagedObject
var uid: String { get set }
}
extension Manageable {
static func className() -> String {
return String(self)
}
static func fetchObjects(predicate: NSPredicate?,
completion:(fetchedObjects: [String: ManageableType]) -> ()) {
let entityDescription = NSEntityDescription.entityForName(className(),
inManagedObjectContext: CoreDataStack.sharedInstance.context)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = entityDescription
if let p = predicate {
fetchRequest.predicate = p
}
var fetchedObjectsDict: [String: ManageableType] = [:]
do {
let result = try CoreDataStack.sharedInstance.context.executeFetchRequest(fetchRequest) as! [ManageableType]
if result.count > 0 {
for object in result {
fetchedObjectsDict[object.uid] = object
}
}
} catch {
print("ERROR FETCH MANAGEABLE OBJECTS: \(error)")
}
completion(fetchedObjects: fetchedObjectsDict)
}
}
When I try to change loop code block into:
for object in result {
let uid = object.valueForKey("uid") as! String
fetchedObjectsDict[uid] = object
}
I got the error "Ambiguous use of 'valueForKey'"
Why these errors happen here in new XCode version, help please?
Your protocol extension needs a type constraint
extension Manageable where Self : NSManagedObject, ManageableType == Self { ... }

Swift - Sorting an array retrieved from Core Data [duplicate]

This question already has an answer here:
Sorting Array received from Core Data in Swift
(1 answer)
Closed 8 years ago.
I want to sort the array with usernames that I retrieve from my core data. I retrieve it like this:
override func viewDidAppear(animated: Bool) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "User")
let en = NSEntityDescription.entityForName("User", inManagedObjectContext: context)
myList = context.executeFetchRequest(freq, error: nil)!
tv.reloadData()
}
And later I set the cells in my tableview like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let CellID:NSString = "cell"
var cell: UITableViewCell = self.tv.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
if let ip = indexPath as Optional {
var data:NSManagedObject = myList[ip.row] as NSManagedObject
cell.textLabel!.text = data.valueForKeyPath("username") as String!
}
I tried to use the sorting function for in the viewDidAppear function like this:
var sortedList = myList.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
But this gives me an error saying: "Could not find member 'localizedCaseInsensitiveCompare'"
Any suggestions on how to proceed would be appreciated.
executeFetchRequest returns [AnyObject]? and you need to convert this as [NSManagedObjet] array and sort on user key
if let myList = myList as? [NSManagedObject] {
var sortedList = myList.sorted { ($0.valueForKeyPath("user") as String).localizedCaseInsensitiveCompare(($1.valueForKeyPath("user") as String)) == NSComparisonResult.OrderedAscending }
}
else {
println("My List not contains the NSManagedObject array")
}
I like to use a separate function to fetch and sort the array. You sort the array by using an NSSortDescriptor. You can add the capitalization check as a selector for the sort descriptor. I got the separate function idea from this tutorial. I used it and it works for me. I got the idea for the selector idea by referencing this Objective-C code.
var objectives: [NSManagedObject]!
func fetchUsernames() {
static let appDel: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "User")
let sortDescriptor = NSSortDescriptor(key: "username", ascending: true, selector: "localizedCaseInsensitiveCompare:")
freq.sortDescriptors = [sortDescriptor]
do {
let fetchedResults: [NSManagedObject] = try managedContext.executeFetchRequest(freq) as! [NSManagedObject]
if let results: [NSManagedObject] = fetchedResults {
objectives = results
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
tableView.reloadData()
}

How do I append an executeFetchRequest result into a array struct

I'm an iOS newbie developer. I'm trying to import the results of an executeFetchRequest into a structure I created for viewing later into a table. I"m getting "Array index out of range" in func getTasks(), I'm pretty sure I"m supposed to append it, but not sure quite how.
I'm sure there's a better way of setting this up in general. Right now I'm just trying to get things to work. But other suggestions would be appreciated.
import UIKit
import CoreData
var taskMgr: TaskManager = TaskManager()
struct task {
var name = "Un-Named"
var desc = "Un-Described"
}
class TaskManager: NSObject {
var tasks = task[]()
init() {
super.init()
self.getTasks()
}
func addTask(name: String, desc: String) {
tasks.append(task(name: name, desc: desc))
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext
let ent = NSEntityDescription.entityForName("Tasks", inManagedObjectContext: context)
var newTask = Tasks(entity: ent, insertIntoManagedObjectContext: context)
newTask.name = name
newTask.desc = desc
println("Object saved")
context.save(nil)
}
func getTasks() {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext
var request = NSFetchRequest(entityName: "Tasks")
request.returnsObjectsAsFaults = false;
var results:NSArray = context.executeFetchRequest(request, error: nil)
if (results.count > 0) {
self.tasks = task[]()
var i = 0
for element in results {
tasks[i].name = element.name // fatal error: Array index out of range
tasks[i].desc = element.desc
i++
}
}
}
}
class Tasks: NSManagedObject {
#NSManaged var name: String
#NSManaged var desc: String
}
You can't use subscripting to add items to an array -- you need to call append() or use the += operator instead. Try this:
self.tasks = task[]()
for element in results {
tasks += task(name: element.name, desc: element.desc)
}

Resources