Fetch request on single Transformable attribute (Swift) - swift

I have a Core Data entity (type) called NoteEntity. It has a managed variable called noteDocument, which is of the custom type NoteDocument (my subclass of NSDocument). I changed its auto-generated NoteEntity+Core Data Properties class so it reads
import Foundation
import CoreData
extension NoteEntity {
#NSManaged var noteDocument: NoteDocument? // changed
#NSManaged var belongsTo: NSSet?
}
so that noteDocument is of type NoteDocument instead of NSObject. The NoteDocument class does implement NSCoding, as follows:
required convenience init(coder theDecoder: NSCoder)
{
let retrievedURL = theDecoder.decodeObjectForKey("URLKey") as! NSURL
self.init(receivedURL: retrievedURL)
}
func encodeWithCoder(theCoder: NSCoder)
{
theCoder.encodeObject(fileURL, forKey: "URLKey")
}
What I want to be able to do is find the noteEntity entities in the managed context with a given noteDocument value. So I run this code (passing it a parameter theNote that corresponds to a noteDocument that I know exists in the managed context):
var request = NSFetchRequest(entityName: "NoteEntity")
let notePredicate = NSPredicate(format: "noteDocument == %#", theNote)
request.predicate = notePredicate
print("Text in the NoteEntity with the NoteDocument for "+theNote.filename+":")
do
{
let notesGathered = try context.executeFetchRequest(request) as? [NoteEntity]
for n in notesGathered!
{
print (n.noteDocument!.filename)
print (n.noteDocument!.noteText)
}
}
catch let error as NSError
{
print("Could not run fetch request. \(error), \(error.userInfo)")
}
but it returns no entries. If I comment out the predicate, I get all the NoteEntity values in the database, but with the predicate in there I get nothing. Clearly something is wrong with the search I'm trying to do in the predicate. I think it's because the value is a Transformable, but I'm not sure where to go from there. I know you can't run fetch requests on the members of Transformable arrays, but is it not possible to run fetch requests on single Transformable attributes? If it isn't, what alternatives exist?
EDIT: The NoteDocument class includes a lot more than the NSCoding. As I said, it's an NSDocument subclass. The NSCoding uses a URL as its key because that's the "primary key" for the NoteDocument class - it's what initializes the class. Here is the rest of the class, not including the NSCoding above:
import Cocoa
class NoteDocument: NSDocument, NSCoding
{
var filename: String
var noteText: String
var attributes: NSDictionary?
var dateCreated: NSDate?
var dateString: String?
init (receivedURL: NSURL)
{
self.filename = ""
self.noteText = ""
super.init()
self.fileType = "net.daringfireball.markdown"
self.fileURL = receivedURL
// Try to get attributes, most importantly date-created.
let fileManager = NSFileManager.defaultManager()
do
{
attributes = try fileManager.attributesOfItemAtPath(fileURL!.path!)
}
catch let error as NSError
{
print("The error was: "+String(error))
}
if let dateCreated = attributes?.fileCreationDate()
{
// print("dateCreated is "+String(dateCreated!))
// Format the date-created to an appropriate string.
dateString = String(dateCreated)
}
else
{
print("Did not find the attributes for "+filename)
}
if let name = self.fileURL?.lastPathComponent
{
filename = name
}
else
{
filename = "Unnamed File"
}
noteText = ""
do
{
noteText = try NSString(contentsOfURL: self.fileURL!, encoding: NSUTF8StringEncoding) as String
}
catch let error as NSError
{
print("Error trying to get note file:"+String(error))
}
}
// MARK: - Document functions
override class func autosavesInPlace() -> Bool
{
// print ("autosavesInPlace ran.")
return true
}
override func dataOfType(typeName: String) throws -> NSData
{
var outError: NSError! = NSError(domain: "Migrator", code: 0, userInfo: nil)
// Post: Document is saved to a file specified by the user.
outError = NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
if let value = self.noteText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
// Convert noteText to an NSData object and return that.
return value
}
print("dataOfType ran.")
throw outError
}
override func readFromData(data: NSData, ofType typeName: String) throws
{
// Currently unused; came free with NSDocument.
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
}
}

In the code you show that the only thing you're coding is a URL. In this case it makes much more sense and provides more utility to use a plain string in the entity to store the URL and to add a transient attribute (or create a wrapper class to combine the entity and document) for the document. In this way you can use the URL in the predicate and it's easy to build the predicate from a document. Storing the document as a transformable isn't helping you in any way it seems.

Related

Any clean way for batch inserting coredata objects with relationships?

I have been observing high CPU times in background threads while inserting the coredata objects, and from analyser i could find that it's coming majorly because of some relationships i was creating one by one, and those could be in thousands.
So i thought if i could create them with batch insert. I can do that easily for objects using without relationships using NSBatchInsertRequest, but with relationships, I can't seem to find any clean way.Without relationships, i can easily create dictionaries and insert using the above request.
​
With relationships, i also tried using the object handler method of NSBatchInsertRequest, but even that is giving me an exception
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'run' between objects in different contexts
This is how i am trying to make sure that the trackpoint getting added is using the run object from the same context as the one in which its being created
func addTrackPoints(run: RunModel, objectId: NSManagedObjectID) async throws {
let locations:[CLLocation] = run.getLocations()
let count = run.getLocations().count
var index = 0
let batchInsert = NSBatchInsertRequest(entity: TrackPoint.entity()) { (managedObject: NSManagedObject) -> Bool in
guard index < count else { return true }
if let trackPoint = managedObject as? TrackPoint {
let data = locations[index]
guard let run = try? StorageService.shared.getBackgroundContext().object(with: objectId) as? Run else {
fatalError("failed to get run object")
}
trackPoint.run = run
}
index += 1
return false
}
try await StorageService.shared.batchInsert(entity: TrackPoint.entity(), batchInsertRequest: batchInsert, context: StorageService.shared.getBackgroundContext())
}
I also tried it without accessing the object from same context but instead tried directly using the Run object that i had created. It didn't crash, but it still didn't create the relationship.Also it forced me to remove the concurrencydebug run argument.
func addTrackPoints(run: RunModel, object: Run) async throws {
let locations = run.getLocations()
let count = run.getLocations().count
var index = 0
let batchInsert = NSBatchInsertRequest(entity: TrackPoint.entity()) { (managedObject: NSManagedObject) -> Bool in
guard index < count else { return true }
if let trackPoint = managedObject as? TrackPoint {
let data:CLLocation = locations[index]
trackPoint.run = object
}
index += 1
return false
}
try await StorageService.shared.batchInsert(entity: TrackPoint.entity(), batchInsertRequest: batchInsert, context: StorageService.shared.getBackgroundContext()) }
StorageService
public func batchInsert(entity: NSEntityDescription, batchInsertRequest: NSBatchInsertRequest, context: NSManagedObjectContext? = nil) async throws {
var taskContext:NSManagedObjectContext? = context
if(taskContext == nil) {
taskContext = StorageService.shared.newTaskContext()
// Add name and author to identify source of persistent history changes.
taskContext?.name = "importContext"
taskContext?.transactionAuthor = "import\(entity.name ?? "entity")"
}
/// - Tag: performAndWait
try await taskContext?.perform {
// Execute the batch insert.
do{
let fetchResult = try taskContext?.execute(batchInsertRequest)
if let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
} catch {
self.logger.error("Failed to execute batch insert request. \(error)")
}
throw SSError.batchInsertError
}
logger.info("Successfully inserted data for \(entity.name ?? "entity")")
}
Any help would be deeply appreciated :-)
How app works, I send request to server, get some results and want data to be saved in core data for further usage to send request to server only when needed. so next time I will query data from database.
Here is sample:
I always save data in background context, which is configured like this:
func getBgContext() -> NSManagedObjectContext {
let bgContext = self.persistenceController.container.newBackgroundContext()
bgContext.automaticallyMergesChangesFromParent = true
bgContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return bgContext
}
Next I construct my data models like this so decoder will handle entity creation and data parsing + insertion in dbContext:
public class SomeDataModel: NSManagedObject, Codable {
var entityName: String = "SomeDataModel"
enum CodingKeys: String, CodingKey {
case id = "id"
case someData = "someData"
}
public required convenience init(from decoder: Decoder) throws {
guard
let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "SomeDataModel", in: context)
else {
throw DecoderConfigurationError.missingManagedObjectContext
}
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int32.self, forKey: .id)
someData = try values.decodeIfPresent(String.self, forKey: .someData)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(someData, forKey: .someData)
}
func toExternalModel() -> SomeExternalUsableModel {
return SomeExternalUsableModel(id: id, someData: someData)
}
}
extension SomeDataModel {
#nonobjc public class func fetchRequest() -> NSFetchRequest<SomeDataModel> {
return NSFetchRequest<SomeDataModel>(entityName: "SomeDataModel")
}
#NSManaged public var someData: String?
#NSManaged public var id: Int32
}
extension SomeDataModel: Identifiable {
}
to pass dbcontext to decoder I do next:
extension CodingUserInfoKey {
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")!
}
dbContext - create background context somewhere in API helper class, and use this context for all the parsings below.
and next I do parsing with decoder when respond from server comes:
let model = try self.dbContext.performAndWait {
let jsonDecoder = JSONDecoder()
let jsonEncoder = JSONEncoder()
// pass context to decoder/encoder
jsonDecoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.dbContext
jsonEncoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.dbContext
// parse model, used generic for reuse for other models
let model = try jsonDecoder.decode(T.self, from: result.data)
// after this line - all the data is parsed from response from server, and saved to dbContext, and contained in model as well
if self.dbContext.hasChanges {
do {
try self.dbContext.save()
self.dbContext.refreshAllObjects() // refresh context objects to ELIMINATE all outdated db objects in memory (specially when you will have relations, they could remain in memory until updated)
} catch let error {
// process error
}
}
return model
}
// do with saved and ready to use data in models whatever needed:
return model
and extensions used for performAndWait
extension NSManagedObjectContext {
func performAndWait<T>(_ block: () throws -> T) throws -> T? {
var result: Result<T, Error>?
performAndWait {
result = Result { try block() }
}
return try result?.get()
}
func performAndWait<T>(_ block: () -> T) -> T? {
var result: T?
performAndWait {
result = block()
}
return result
}
}

Generic parameter 'ResultType' could not be inferred [duplicate]

In Swift 2 the following code was working:
let request = NSFetchRequest(entityName: String)
but in Swift 3 it gives error:
Generic parameter "ResultType" could not be inferred
because NSFetchRequest is now a generic type. In their documents they wrote this:
let request: NSFetchRequest<Animal> = Animal.fetchRequest
so if my result class is for example Level how should I request correctly?
Because this not working:
let request: NSFetchRequest<Level> = Level.fetchRequest
let request: NSFetchRequest<NSFetchRequestResult> = Level.fetchRequest()
or
let request: NSFetchRequest<Level> = Level.fetchRequest()
depending which version you want.
You have to specify the generic type because otherwise the method call is ambiguous.
The first version is defined for NSManagedObject, the second version is generated automatically for every object using an extension, e.g:
extension Level {
#nonobjc class func fetchRequest() -> NSFetchRequest<Level> {
return NSFetchRequest<Level>(entityName: "Level");
}
#NSManaged var timeStamp: NSDate?
}
The whole point is to remove the usage of String constants.
I think i got it working by doing this:
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Level")
at least it saves and loads data from DataBase.
But it feels like it is not a proper solution, but it works for now.
The simplest structure I found that works in 3.0 is as follows:
let request = NSFetchRequest<Country>(entityName: "Country")
where the data entity Type is Country.
When trying to create a Core Data BatchDeleteRequest, however, I found that this definition does not work and it seems that you'll need to go with the form:
let request: NSFetchRequest<NSFetchRequestResult> = Country.fetchRequest()
even though the ManagedObject and FetchRequestResult formats are supposed to be equivalent.
Here are some generic CoreData methods that might answer your question:
import Foundation
import Cocoa
func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
let entityName = T.description()
let context = app.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
let record = T(entity: entity!, insertInto: context)
return record
}
func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
let recs = allRecords(T.self)
return recs.count
}
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let predicate = search
{
request.predicate = predicate
}
if let sortDescriptors = multiSort
{
request.sortDescriptors = sortDescriptors
}
else if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func deleteRecord(_ object: NSManagedObject)
{
let context = app.managedObjectContext
context.delete(object)
}
func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
let context = app.managedObjectContext
let results = query(T.self, search: search)
for record in results
{
context.delete(record)
}
}
func saveDatabase()
{
let context = app.managedObjectContext
do
{
try context.save()
}
catch
{
print("Error saving database: \(error)")
}
}
Assuming that there is a NSManagedObject setup for Contact like this:
class Contact: NSManagedObject
{
#NSManaged var contactNo: Int
#NSManaged var contactName: String
}
These methods can be used in the following way:
let name = "John Appleseed"
let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name
let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %#", name))
for contact in contacts
{
print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}
deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %#", name))
recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")
saveDatabase()
This is the simplest way to migrate to Swift 3.0, just add <Country>
(tested and worked)
let request = NSFetchRequest<Country>(entityName: "Country")
Swift 3.0 This should work.
let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
I also had "ResultType" could not be inferred errors. They cleared once I rebuilt the data model setting each entity's Codegen to "Class Definition". I did a brief writeup with step by step instructions here:
Looking for a clear tutorial on the revised NSPersistentContainer in Xcode 8 with Swift 3
By "rebuilt" I mean that I created a new model file with new entries and attributes. A little tedious, but it worked!
What worked best for me so far was:
let request = Level.fetchRequest() as! NSFetchRequest<Level>
I had the same issue and I solved it with the following steps:
Select your xcdatamodeld file and go to the Data Model Inspector
Select your first Entity and go to Section class
Make sure that Codegen "Class Definition" is selected.
Remove all your generated Entity files. You don't need them anymore.
After doing that I had to remove/rewrite all occurences of fetchRequest as XCode seem to somehow mix up with the codegenerated version.
HTH
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func loadItemsCategory() {
let request: NSFetchRequest<Category> = Category.fetchRequest()
do {
categoryArray = try context.fetch(request)
} catch {
print(error)
}
tableView.reloadData()
}

How to apply the type to a NSFetchRequest instance?

In Swift 2 the following code was working:
let request = NSFetchRequest(entityName: String)
but in Swift 3 it gives error:
Generic parameter "ResultType" could not be inferred
because NSFetchRequest is now a generic type. In their documents they wrote this:
let request: NSFetchRequest<Animal> = Animal.fetchRequest
so if my result class is for example Level how should I request correctly?
Because this not working:
let request: NSFetchRequest<Level> = Level.fetchRequest
let request: NSFetchRequest<NSFetchRequestResult> = Level.fetchRequest()
or
let request: NSFetchRequest<Level> = Level.fetchRequest()
depending which version you want.
You have to specify the generic type because otherwise the method call is ambiguous.
The first version is defined for NSManagedObject, the second version is generated automatically for every object using an extension, e.g:
extension Level {
#nonobjc class func fetchRequest() -> NSFetchRequest<Level> {
return NSFetchRequest<Level>(entityName: "Level");
}
#NSManaged var timeStamp: NSDate?
}
The whole point is to remove the usage of String constants.
I think i got it working by doing this:
let request:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Level")
at least it saves and loads data from DataBase.
But it feels like it is not a proper solution, but it works for now.
The simplest structure I found that works in 3.0 is as follows:
let request = NSFetchRequest<Country>(entityName: "Country")
where the data entity Type is Country.
When trying to create a Core Data BatchDeleteRequest, however, I found that this definition does not work and it seems that you'll need to go with the form:
let request: NSFetchRequest<NSFetchRequestResult> = Country.fetchRequest()
even though the ManagedObject and FetchRequestResult formats are supposed to be equivalent.
Here are some generic CoreData methods that might answer your question:
import Foundation
import Cocoa
func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
let entityName = T.description()
let context = app.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
let record = T(entity: entity!, insertInto: context)
return record
}
func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
let recs = allRecords(T.self)
return recs.count
}
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let predicate = search
{
request.predicate = predicate
}
if let sortDescriptors = multiSort
{
request.sortDescriptors = sortDescriptors
}
else if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func deleteRecord(_ object: NSManagedObject)
{
let context = app.managedObjectContext
context.delete(object)
}
func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
let context = app.managedObjectContext
let results = query(T.self, search: search)
for record in results
{
context.delete(record)
}
}
func saveDatabase()
{
let context = app.managedObjectContext
do
{
try context.save()
}
catch
{
print("Error saving database: \(error)")
}
}
Assuming that there is a NSManagedObject setup for Contact like this:
class Contact: NSManagedObject
{
#NSManaged var contactNo: Int
#NSManaged var contactName: String
}
These methods can be used in the following way:
let name = "John Appleseed"
let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name
let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %#", name))
for contact in contacts
{
print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}
deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %#", name))
recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")
saveDatabase()
This is the simplest way to migrate to Swift 3.0, just add <Country>
(tested and worked)
let request = NSFetchRequest<Country>(entityName: "Country")
Swift 3.0 This should work.
let request: NSFetchRequest<NSFetchRequestResult> = NSManagedObject.fetchRequest()
request.entity = entityDescription(context)
request.predicate = predicate
I also had "ResultType" could not be inferred errors. They cleared once I rebuilt the data model setting each entity's Codegen to "Class Definition". I did a brief writeup with step by step instructions here:
Looking for a clear tutorial on the revised NSPersistentContainer in Xcode 8 with Swift 3
By "rebuilt" I mean that I created a new model file with new entries and attributes. A little tedious, but it worked!
What worked best for me so far was:
let request = Level.fetchRequest() as! NSFetchRequest<Level>
I had the same issue and I solved it with the following steps:
Select your xcdatamodeld file and go to the Data Model Inspector
Select your first Entity and go to Section class
Make sure that Codegen "Class Definition" is selected.
Remove all your generated Entity files. You don't need them anymore.
After doing that I had to remove/rewrite all occurences of fetchRequest as XCode seem to somehow mix up with the codegenerated version.
HTH
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func loadItemsCategory() {
let request: NSFetchRequest<Category> = Category.fetchRequest()
do {
categoryArray = try context.fetch(request)
} catch {
print(error)
}
tableView.reloadData()
}

Realm with ObjectMapper

I load a list of objects from my server and save them to Realm using ObjectMapper. Each object contains an url defining where to load the image for the object. I load the image and save the imagedata in the realm object so that I don't need to reload it every time. But unfortunately the image data is lost if I reload the data.
I use a primary key and my thought was that when the JSON. I fear that ObjectMapper doesn't update existing objects in Realm but overwrites them. So the imagedata is nil and must be refetched from server. Is there something I can do do prevent this?
Here is my simplified ObjectMapping-File:
import Foundation
import ObjectMapper
import RealmSwift
class OverviewItem: PersistentObject {
override var hashValue : Int {
get {
return self.overviewID.hashValue
}
}
dynamic var overviewID: Int = 0
dynamic var titleDe: String = ""
dynamic var imageUrl: String = ""
dynamic var imageData: NSData?
required convenience init?(_ map: Map) {
self.init()
}
//computed properties
dynamic var image: UIImage? {
get {
return self.imageData == nil ? nil : UIImage(data: self.imageData!)
}
set(newImage){
if let newImage = newImage, data = UIImagePNGRepresentation(newImage){
self.imageData = data
}
else{
self.imageData = nil
}
}
}
//image is a computed property and should be ignored by realm
override class func ignoredProperties() -> [String] {
return ["image"]
}
override func mapping(map: Map) {
overviewID <- map["infoid"]
titleDe <- map["titleDe"]
imageUrl <- map["imageurl"]
}
override class func primaryKey() -> String {
return "overviewID"
}
}
And here how I fetch the image and update the object:
func fetchImage(item: OverviewItem, successHandler: UIImage? ->(), errorHandler: (ErrorType?) -> ()){
AlamofireManager.Configured
.request(.GET, item.imageUrl)
.responseData({ (response: Response<NSData, NSError>) in
if let error = response.result.error{
logger.error("Error: \(error.localizedDescription)")
errorHandler(error)
return
}
if let imageData = response.result.value{
successHandler(UIImage(data: imageData))
let overviewID = item.overviewID
let queue = NSOperationQueue()
let blockOperation = NSBlockOperation {
let writeRealm = try! Realm()
do{
if let itemForUpdate = writeRealm.objects(OverviewItem).filter("overviewID = \(overviewID)").first{
try writeRealm.write{
itemForUpdate.imageData = imageData
writeRealm.add(itemForUpdate, update: true)
}
}
}
catch let err as NSError {
logger.error("Error with realm: " + err.localizedDescription)
}
}
queue.addOperation(blockOperation)
}
})
}
You would need to pull the existing image data by primary key before you add / upsert your object to the Realm. You could do that e.g. in a method, which abstracts ObjectMapper's mapping, but still allows providing a Realm instance.
But in general, I wouldn't recommend to store images in the Realm itself for that purpose. There are some really good image caching frameworks out there, which allow you to cache images on disk and in memory. These allow you in addition to organize the cache by size, they assist you with fast decompression and they allow you to manage expiry times.

Extra argument 'error' in call - do/catch?

I know there is new error handling i.e. do/catch but not sure if it applies here and even if it does it's pretty difficult for me even going through the documentation. Could someone show me the correct code block please.
/*** error Extra argument 'error' in call ***/
var plistDic = NSPropertyListSerialization.propertyListWithData(plistData!,
options:Int(NSPropertyListMutabilityOptions.MutableContainersAndLeaves.rawValue),
format: nil, error: &error) as Dictionary<String, Dictionary<String, String>>
assert(error == nil, "Can not read data from the plist")
return plistDic
}
// END
EDIT:
let YALCityName = "name"
let YALCityText = "text"
let YALCityPicture = "picture"
private let kCitiesSourcePlist = "Cities"
class YALCity: Equatable {
var name: String
var text: String
var image: UIImage
var identifier: String
// MARK: Class methods
class internal func defaultContent() -> Dictionary<String, Dictionary<String, String>> {
let path = NSBundle.mainBundle().pathForResource(kCitiesSourcePlist, ofType: "plist")
let plistData = NSData(contentsOfFile: path!)
assert(plistData != nil, "Source doesn't exist")
do {
let plistDic = try NSPropertyListSerialization.propertyListWithData(plistData!,
options:NSPropertyListMutabilityOptions.MutableContainersAndLeaves,
format: nil
)
if let dictionary = plistDic as? Dictionary< String, Dictionary<String, String> > {
print("\(dictionary)")
}
else {
print("Houston we have a problem")
}
}
catch let error as NSError {
print(error)
}
return defaultContent()
}
init(record:CKRecord) {
self.name = record.valueForKey(YALCityName) as! String
self.text = record.valueForKey(YALCityText) as! String
let imageData = record.valueForKey(YALCityPicture) as! NSData
self.image = UIImage(data:imageData)!
self.identifier = record.recordID.recordName
}
}
func ==(lhs: YALCity, rhs: YALCity) -> Bool {
return lhs.identifier == rhs.identifier
}
Try this code:
do {
var plistDic = try NSPropertyListSerialization.propertyListWithData(plistData!,
options:NSPropertyListMutabilityOptions.MutableContainersAndLeaves,
format: nil
)
// plistDic is of type 'AnyObject'. We need to cast it to the
// appropriate dictionary type before using it.
if let dictionary = plistDic as? Dictionary<String, Dictionary<String, String>> {
// You are good to go.
// Insert here your code that uses dictionary (otherwise
// the compiler will complain about unused variables).
// change 'let' for 'var' if you plan to modify the dictionary's
// contents.
// (...)
}
else {
// Cast to dictionary failed: plistDic is NOT a Dictionary with
// the structure: Dictionary<String, Dictionary<String, String>>
// It is either a dictionary of a different internal structure,
// or not a dictionary at all.
}
}
catch let error as NSError {
// Deserialization failed (see console for details:)
print(error)
}
Note: I split the call to a function that throws (try...) and the casting to your specific type of Dictionary (if let...) because I'm not really sure exactly what would happen if the call succeeds but the cast fails, or if it would be clear which one failed from the debugger. Also, I don't like too many things happening in one line...
EDIT: I fixed the options parameter. In Swift, Ints and enums aren't interchangeable; you need to pass the right type (I missed it the first time when modifying your code).