I have a list of json objects that I want to write into CoreData. Json objects are coming from API. I have a method to Upsert a data into CoreData. The code for that is the following.
public class func upsertSetting (resultsArr : NSArray, settingTypeId: Int, countryId: Int) {
if let moc = CoreDataHelper().managedObjectContext {
var settings : [Setting]
settings = resultsArr as [Setting]
var existingSettings : [SettingCoreDataModel]?
existingSettings = fetchAllSettings(settingTypeId, countryId: countryId)
for result in settings {
var key = result.key
var value = result.value
if (value == nil)
{
value = ""
}
var id = result.id
var settingTypeId = result.settingTypeId
var countryId = result.countryId
var settingCoreData: [SettingCoreDataModel]?
var existingSetting: SettingCoreDataModel?
if(existingSettings? == nil){
existingSetting = nil
}
else{
settingCoreData = filter(existingSettings!) { (e:SettingCoreDataModel) in e.key == key }
if(settingCoreData? != nil && settingCoreData?.count > 0){
existingSetting = settingCoreData?.first
}
}
if(existingSetting == nil){
SettingCoreDataModel.createInManagedObjectContext(moc, key: key!, value: value!, id: id!, settingTypeId: settingTypeId!, countryId: countryId!)
}
else if(existingSetting?.value != value!){
existingSetting?.value = value!
save()
}
}
}
}
public class func save() {
var error : NSError?
if !CoreDataHelper().managedObjectContext!.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
}
fetchAllSettings looks like this:
public class func fetchAllSettings (settingTypeId: Int, countryId: Int) -> [SettingCoreDataModel]?
{
let fetchRequest = NSFetchRequest(entityName: "SettingCoreDataModel")
let predicate = NSPredicate(format: "settingTypeId == %d AND countryId == %d", settingTypeId, countryId)
fetchRequest.predicate = predicate
if let fetchResults = CoreDataHelper().managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [SettingCoreDataModel] {
return fetchResults
}
else{
return []
}
}
And here is the SettingCoreDataModel with createInManagedObjectContext :
public class SettingCoreDataModel: NSManagedObject {
#NSManaged var key: String
#NSManaged var value: String
#NSManaged var id: NSNumber
#NSManaged var settingTypeId: NSNumber
#NSManaged var countryId: NSNumber
class func createInManagedObjectContext(moc: NSManagedObjectContext, key: NSString, value: NSString, id: int, settingTypeId: int, countryId: int) -> SettingCoreDataModel {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("SettingCoreDataModel", inManagedObjectContext: moc) as? SettingCoreDataModel
newItem?.key = key
newItem?.value = value
newItem?.id = id
newItem?.settingTypeId = settingTypeId
newItem?.countryId = countryId
return newItem!
}
}
My problem is, when I call createInManagedObjectContext it does not seem to be saving into CoreData. I couldn't figure the issue and I'm fairly new to Swift and XCode
Edit: One thing I forgot to mention is that, I'm trying to create a plugin with this. All my code is in Pod but my data model is in example project. I'm not sure if that makes any difference to that, but while fetching and everything it seems to be working fine.
Using save() didn't help either by the way.
I call upsertSetting on application didFinishLaunchingWithOptions. After that, on my first view I try to get a single record by key and use it on the table view:
var textColorSetting: Setting?
override func viewDidLoad() {
super.viewDidLoad()
textColorSetting = SettingCoreData.fetchSetting("text_color", countryId: 3)
}
Table cells:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as UITableViewCell
textColorSetting = SettingCoreData.fetchSetting("text_color", countryId: 3)
var textColor = textColorSetting?.value
if (textColor? == nil){
textColor = "#ff00ff"
}
let color = SettingCoreDataModel.hexStringToUIColor(textColor!)
cell.textLabel?.textColor = color
}
Fetching single setting:
public class func fetchSetting(key : NSString, countryId: Int) -> Setting? {
let fetchRequest = NSFetchRequest(entityName: "SettingCoreDataModel")
var existingSetting: SettingCoreDataModel?
let predicate = NSPredicate(format: "key == %# AND countryId == %d", key, countryId)
fetchRequest.predicate = predicate
var error: NSError?
if let fetchResults = CoreDataHelper().managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) as? [SettingCoreDataModel] {
println(fetchResults.count)
if (!fetchResults.isEmpty && fetchResults.count > 0) {
existingSetting = fetchResults.first!
var setting: Setting?
setting?.id = existingSetting?.id as? Int
setting?.key = existingSetting?.key
setting?.value = existingSetting?.value
setting?.settingTypeId = existingSetting?.settingTypeId as? Int
setting?.countryId = existingSetting?.countryId as? Int
return setting
} else {
return nil
}
} else {
return nil
}
}
In here println(fetchResults.count) always prints 0. That's why I assume save() is not working.
In the case where you create a new setting, you need to invoke save (or better yet, take save out of the inside of the loop and just call it once after the loop)
if(existingSetting == nil){
SettingCoreDataModel.createInManagedObjectContext(moc, key: key!, value: value!, id: id!, settingTypeId: settingTypeId!, countryId: countryId!)
save() // this is missing
}
Some other observations and simplifications:
fetchAllSettings will always return a valid array (possibly empty) of settings, declare it as -> [SettingCoreDataModel] instead of declaring it as an optional:
public class func fetchAllSettings (settingTypeId: Int, countryId: Int) -> [SettingCoreDataModel]
var existingSettings = fetchAllSettings(settingTypeId, countryId: countryId)
having done that you can use the fact that first returns an optional to simplify the logic to find an existing setting to:
var existingSetting = existingSettings.filter({ (e:SettingCoreDataModel) in e.key == key }).first
To insure that value always has a valid value, so you don't have to worry about unwrapping it, use nil checking:
var value = result.value ?? ""
Related
I am facing an issue where I am unable to keep existing relationships after calling add(_, update: true) function.
I wrote a TaskSync class that is responsible for creating/updating Task objects:
class TaskSync: ISync {
typealias Model = Task
func sync(model: Task) {
let realm = try! Realm()
let inWrite = realm.isInWriteTransaction
if !inWrite {
realm.beginWrite()
}
let _task = realm.object(ofType: Task.self, forPrimaryKey: model.id)
// Persist matches as they are not getting fetched with the task
if let _task = _task {
print("matches: \(_task.matches.count)")
model.matches = _task.matches
}
realm.add(model, update: true)
if _task == nil {
var user = realm.object(ofType: User.self, forPrimaryKey: model.getUser().id)
if (user == nil) {
user = model.getUser()
realm.add(user!, update: true)
}
user!.tasks.append(model)
}
if !inWrite {
try! realm.commitWrite()
}
}
func sync(models: List<Task>) {
let realm = try! Realm()
try! realm.write {
models.forEach { task in
sync(model: task)
}
}
}
}
When a model is to be synced, I check if it already exists in the Realm and if so, I fetch it and try to include the matches property as this one is not included in the model.
Right before the call realm.add(model, update: true), model contains list of matches, however right after the realm.add is executed, the matches list is empty.
Here are the two models:
class Task: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var title: String = ""
dynamic var desc: String = ""
dynamic var price: Float = 0.0
dynamic var calculatedPrice: Float = 0.0
dynamic var location: String = ""
dynamic var duration: Int = 0
dynamic var date: String = ""
dynamic var category: Category?
dynamic var currency: Currency?
dynamic var longitude: Double = 0.0
dynamic var latitude: Double = 0.0
dynamic var state: Int = 0
dynamic var userId: Int = 0
// Existing images
var imagesExisting = List<URLImage>()
// New images
var imagesNew = List<Image>()
// Images deleted
var imagesDeleted = List<URLImage>()
private let users = LinkingObjects(fromType: User.self, property: "tasks")
var user: User?
var matches = List<Match>()
dynamic var notification: Notification?
override static func ignoredProperties() -> [String] {
return ["imagesExisting", "imagesNew", "imagesDeleted", "user", "tmpUser"]
}
override static func primaryKey() -> String? {
return "id"
}
func getImageMain() -> URLImage? {
for image in imagesExisting {
if image.main {
return image
}
}
return imagesExisting.first
}
func getSection() -> Int {
return state
}
func getSectionFieldName() -> String? {
return "state"
}
func getId() -> Int {
return id
}
func getURL() -> URL? {
if let image = getImageMain() {
return image.getResizedURL()
}
return nil
}
func getState() -> TaskOwnState {
return TaskOwnState(rawValue: state)!
}
func getUser() -> User {
return (user != nil ? user : users.first)!
}
}
class Match: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var state: Int = -1
dynamic var priorityOwnRaw: Int = 0
dynamic var priorityOtherRaw: Int = 0
dynamic var user: User!
var messages = List<Message>()
private let tasks = LinkingObjects(fromType: Task.self, property: "matches")
var task: Task?
dynamic var notification: Notification?
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["task"]
}
func getId() -> Int {
return id
}
func getSection() -> Int {
return 0
}
func getURL() -> URL? {
if let image = user.getImageMain() {
return image.getResizedURL()
}
return nil
}
func getPriorityOwn() -> PriorityType {
if priorityOwnRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getPriorityOther() -> PriorityType {
if priorityOtherRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getSectionFieldName() -> String? {
return nil
}
func getTask() -> Task {
return (task != nil ? task : tasks.first)!
}
}
I spent hours trying to figure out why I am unable to keep the matches relationship when updating the task. Every advice will be highly appreciated!
This question was also asked upon Realm's GitHub issue tracker. For posterity, here is the solution.
List properties should always be declared as let properties, as assigning to them does not do anything useful. The correct way to copy all objects from one List to another is model.tasks.append(objectsIn: _user.tasks).
I have used Alamofire and SwiftyJSON To Populate the UiCollectionview and its working fine but didSelectItemAtIndexPath function shows array out of index thought I have printed the array count and it's not empty
any suggestion
here is my code:-
The model
import Foundation
class ProductModel {
private var _ProductItemId: String!
private var _ProductMainCategory: String!
private var _ProductCategoryId: String!
private var _ProductName: String!
private var _ProductItemNo: String!
private var _ProductAvalaibility: String!
private var _ProductSeoDesc: String!
private var _ProductImageURL: String!
private var _ProductBrand_ID: String!
private var _ProductCat_name: String!
//Level 1
private var _ProductTotalQuantity : String!
private var _Productprice : String!
private var _ProductSalePrice : String!
private var _ProductWeightName : String!
private var _ProductCode : String!
var ProductItemId : String {
return _ProductItemId
}
var ProductMainCategory : String {
return _ProductMainCategory
}
var ProductCategoryId : String {
return _ProductCategoryId
}
var ProductName : String {
return _ProductName
}
var ProductItemNo : String {
return _ProductItemNo
}
var ProductAvalaibility : String {
return _ProductAvalaibility
}
var ProductSeoDesc : String {
return _ProductSeoDesc
}
var ProductImageURL : String {
return _ProductImageURL
}
var ProductBrand_ID: String {
return _ProductBrand_ID
}
var ProductCat_name: String {
return _ProductCat_name
}
//Level 1
var ProductTotalQuantity : String {
return _ProductTotalQuantity
}
var Productprice : String {
return _Productprice
}
var ProductSalePrice : String {
return _ProductSalePrice
}
var ProductWeightName : String {
return _ProductWeightName
}
var ProductCode : String {
return _ProductCode
}
//Initilizer
init(ProductImageURL : String, ProductName : String, Productprice : String, ProductSalePrice : String)
{
self._ProductName = ProductName
self._ProductImageURL = ProductImageURL//
//Level 1
self._Productprice = Productprice//
self._ProductSalePrice = ProductSalePrice//
}
My CollectionView Delegates and Data sources
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ProductCell", forIndexPath: indexPath)as? ProductCell {
let _prod: ProductModel!
_prod = prod [indexPath.row]
cell.configureCell(_prod)
return cell
}
else{
return UICollectionViewCell()
}
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let prodDetail: ProductModel!
prodDetail = prod[indexPath.row] //error Array index out of range
print(prodDetail.Productprice)
performSegueWithIdentifier("productDetailSegue", sender: prodDetail)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
//if inSearchMode{
//return filteredProd.count
// }
return prod.count
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
Calling API and Parsing
Alamofire.request(.POST, "http://www.picknget.com/webservice/index.php/Home/filter_grocery_product_practice/", parameters: parameterDictionary as? [String : AnyObject])
.responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
print(json)
if let _statusCode = json["status"].string {
// print("the ststus code is ", _statusCode)
if (_statusCode == "1"){
self.parseJSON(json)
}
if (_statusCode == "0"){
SwiftSpinner.hide({
self.callAlert("OOP's", _msg: "No More Product is available in this section right now")
})
}
}
//print ("json result ", json)
}
}.responseString { response in
//print("response ",response.result.value)
}
}
func parseJSON(json: JSON) {
for result in json["cat"].arrayValue {
let name = result["Name"]
let aString: String = "\(result["ImageURL"])"
let product_Image_Url = aString.stringByReplacingOccurrencesOfString("~", withString: "http://www.picknget.com", options: NSStringCompareOptions.LiteralSearch, range: nil)
let price = result["cat_price"][0]["Price"].string
let SalePrice = result["cat_price"][0]["SalePrice"].string
let product = ProductModel(ProductImageURL: "\(product_Image_Url)", ProductName: "\(name)", Productprice: "\(price!)", ProductSalePrice: "\(SalePrice!)")
prod.append(product)
}
print("########")
print(prod.count)
dispatch_async(dispatch_get_main_queue(),{
self.productCollect.reloadData()
});
}
According your comments, I believe the issue is related to how you set the obtained Products for the Collection View.
It's very likely that the function parseJSON executes on a secondary thread. This is actually, the same execution context of the completion handler of method responseJSON.
Within function parseJSON you have this statement:
prod.append(product)
Here, prod should not be a global variable or not a member variable of the view controller! Make it a local variable in function parseJSON!
Your view controller should have a property of this array as well, e.g. products. This serves as the "model" of the view controller. It will be accesses only from the main thread.
In parseJSON assign the view controller the products as follows:
func parseJSON(json: JSON) {
var tmpProducts: [Product] = []
for result in json["cat"].arrayValue {
let name = result["Name"]
let aString: String = "\(result["ImageURL"])"
let product_Image_Url = aString.stringByReplacingOccurrencesOfString("~", withString: "http://www.picknget.com", options: NSStringCompareOptions.LiteralSearch, range: nil)
let price = result["cat_price"][0]["Price"].string
let SalePrice = result["cat_price"][0]["SalePrice"].string
let product = ProductModel(ProductImageURL: "\(product_Image_Url)", ProductName: "\(name)", Productprice: "\(price!)", ProductSalePrice: "\(SalePrice!)")
tmpProducts.append(product)
}
dispatch_async(dispatch_get_main_queue(),{
self.products = tmpProducts // assuming `self` is the ViewController
self.productCollect.reloadData()
});
}
Note: you need to change your Data Source Delegates accordingly, e.g. accessing the "model" (self.products.count) etc.
I have some JSON I'm iterating through (it contains an array of dictionaries) and add to an array of custom objects I made in order to work with the data and add fields to my UITableView cell.
I'm creating an array of my custom objects at the top of the page:
var warehouseItems: [Inventory] = [];
In my viewDidLoad() I have the following:
let urlString = "myUrl.php";
let session = NSURLSession.sharedSession();
let url = NSURL(string: urlString)!;
session.dataTaskWithURL(url) { (data: NSData?, response:NSURLResponse?, error: NSError?) -> Void in
if let responseData = data {
do {
let json = try NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.AllowFragments) as! Dictionary<String, AnyObject>;
//print(json);
if let inventoryDictionary = json["inventory"] as? [Dictionary<String, AnyObject>] {
//print(inventoryDictionary);
for anItem in inventoryDictionary {
if let id = anItem["id"] as? Int, let item = anItem["item"] as? String, let description = anItem["description"] as? String, let quantityOnHand = anItem["quantityOnHand"] as? Int, let supplierId = anItem["supplier_id"] as? Int, let supplierName = anItem["supplierName"] as? String {
let item = Inventory(id: id, item: item, description: description, quantityOnHand: quantityOnHand, supplierId: supplierId, supplierName: supplierName);
self.warehouseItems.append(item);
}
}
print(self.warehouseItems); //This just prints 'Warehouse.Inventory' for every item I have
}
} catch {
print("Could not serialize");
}
}
}.resume()
For some reason it doesn't add the item to the Inventory object. My tableView is blank. It only works when I manually add a string to the cell, so I know my tableView is wired up properly.
When I print(self.warehouseItems) it just shows Warehouse.Inventory 6 times (current number of items my JSON is returning) in the output. In fact if I print(inventoryDictionary) I get the following:
[["item": Item 1, "supplierName": Supplier 1, "quantityOnHand": 42, "id": 1, "supplier_id": 1, "description": Description],
["item": Item 2, "supplierName": Supplier 1, "quantityOnHand": 1001, "id": 2, "supplier_id": 1, "description": Description], ... and so on
Here's my tableview protocol where I'm trying to assign the object's properties to my row:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return warehouseItems.count; //Make sure to set count of array from json
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(textCellIdentifier, forIndexPath: indexPath) as UITableViewCell;
let row = indexPath.row;
cell.textLabel!.text = warehouseItems[row].item;
cell.detailTextLabel!.text = warehouseItems[row].description;
return cell;
}
In case you need it, here's my custom Inventory object class:
class Inventory {
private var _id, _quantityOnHand: Int!;
private var _item, _description: String!;
private var _supplierId: Int?;
private var _supplierName: String?;
var id: Int {
get {
return _id;
}
}
var item: String {
get {
return _item;
}
}
var description: String {
get {
return _description;
}
}
var quantityOnHand: Int {
get {
return _quantityOnHand;
}
}
var supplierId: Int {
get {
return _supplierId!;
}
}
var supplierName: String {
get {
return _supplierName!;
}
}
init(id: Int, item: String, description: String, quantityOnHand: Int, supplierId: Int, supplierName: String) {
_id = id;
_item = item;
_description = description;
_quantityOnHand = quantityOnHand;
_supplierId = supplierId;
_supplierName = supplierName;
}
My head is spinning since I switched to swift, especially all the optionals and unwrapping.
I have a swift dictionary with custom objects and I use it to fill my UITableview. However, when I fill the dictionary, I realize that it deletes previous data when I append a new data to it. The related code blocks like below:
var dict = Dictionary<ListItem, [ListItem]>()
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.dict.keys.array.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell: CategoryCell! = tableView.dequeueReusableCellWithIdentifier("categoryCell", forIndexPath: indexPath) as? CategoryCell
if(cell == nil)
{
cell = CategoryCell(style: UITableViewCellStyle.Default, reuseIdentifier: "categoryCell")
}
else
{
cell.ClearCurrentCellContext(cell.image, labelTitle: cell.lblCategory, labelDescription: cell.lblDescription)
}
if let ip = indexPath as NSIndexPath?
{
let key = Array(self.dict.keys)[ip.row] as ListItem
cell.SetCell(key, image: cell.image, labelTitle: cell.lblCategory, labelDescription: cell.lblDescription)
}
return cell
}
And I fill the dictionary inside of this block:
Alamofire.request(.POST, WSConstants.WebServiceAddress, parameters: nil)
.responseJSON
{(request, response, JSON, error) in
if let jsonResult: NSDictionary = JSON as? NSDictionary
{
if let categories = jsonResult[WSConstants.CATEGORIES] as? NSDictionary
{
if let wsStatus = categories.valueForKey(WSConstants.WS_STATUS) as? String
{
if(wsStatus == "ok")
{
var mainObj: NSDictionary!
var mainItem: ListItem!
for(var i=0; i<mainJSONArray.count; i++)
{
mainObj = mainJSONArray.objectAtIndex(i) as! NSDictionary
mainItem = ListItem()
mainItem.itemId = mainObj.valueForKey(WSConstants.ID) as! Int
mainItem.itemName = mainObj.valueForKey(WSConstants.TITLE) as! String
mainItem.itemDescription = mainObj.valueForKey(WSConstants.DESCRIPTION) as! String
mainItem.itemIcon = mainObj.valueForKey(WSConstants.ICON) as! String
mainItem.parentId = mainObj.valueForKey(WSConstants.PARENT) as! Int
mainItem.postCount = mainObj.valueForKey(WSConstants.POST_COUNT) as! Int
if let subJSONArray = mainObj.valueForKey(WSConstants.SUB) as? NSArray
{
var midCategoryList: [ListItem]! = [ListItem]()
var subObj: NSDictionary!
var subItem: ListItem!
for(var i=0; i<subJSONArray.count; i++)
{
subObj = subJSONArray.objectAtIndex(i) as! NSDictionary
subItem = ListItem()
subItem.itemId = subObj.valueForKey(WSConstants.ID) as! Int
subItem.itemName = subObj.valueForKey(WSConstants.TITLE) as! String
subItem.itemDescription = subObj.valueForKey(WSConstants.DESCRIPTION) as! String
subItem.itemIcon = subObj.valueForKey(WSConstants.ICON) as! String
subItem.parentId = subObj.valueForKey(WSConstants.PARENT) as! Int
subItem.postCount = subObj.valueForKey(WSConstants.POST_COUNT) as! Int
midCategoryList.append(subItem)
subItem = nil
subObj = nil
}
// The code below line fills the dictionary with key and its values
self.dict[mainItem] = midCategoryList
midCategoryList = nil
}
mainItem = nil
mainObj = nil
}
Utility.ReloadTableViewDataWithAnimations(self.categoryTableView)
}
}
}
}
ListItem class:
class ListItem: Serializable, Hashable, NSCoding
{
var uniqueID: Int = 0
override var hashValue: Int { return uniqueID.hashValue }
var itemId: Int
var itemName: String!
var itemIcon: String!
var itemDescription: String!
var parentId: Int!
var postCount: Int!
var content: String!
override init()
{
self.itemId = 0
self.itemName = ""
self.itemIcon = ""
self.parentId = 0
self.postCount = 0
self.content = ""
self.itemDescription = ""
}
init(itemId: Int, itemName: String!, itemIcon: String!, itemDescription: String!, parentId: Int!, postCount: Int, content: String!, date: String!, url: String!)
{
self.itemId = itemId
self.itemName = itemName
self.itemIcon = itemIcon
self.parentId = parentId
self.postCount = postCount
self.content = content
self.itemDescription = itemDescription
}
deinit
{
}
required init(coder aDecoder: NSCoder)
{
self.itemId = aDecoder.decodeIntegerForKey("itemId") as Int
self.itemName = aDecoder.decodeObjectForKey("itemName") as! String
self.itemIcon = aDecoder.decodeObjectForKey("itemIcon") as! String
self.itemDescription = aDecoder.decodeObjectForKey("itemDescription") as! String
}
func encodeWithCoder(aCoder: NSCoder)
{
aCoder.encodeInteger(self.itemId, forKey: "itemId")
aCoder.encodeObject(self.itemName, forKey: "itemName")
aCoder.encodeObject(self.itemIcon, forKey: "itemIcon")
aCoder.encodeObject(self.itemDescription, forKey: "itemDescription")
}
}
func ==(lhs: ListItem, rhs: ListItem) -> Bool
{
return lhs.uniqueID == rhs.uniqueID
}
Is this a swift bug or I make some mistakes to fill it ?
Thank you for your answers
King regards
Solution:
The reason of the problem is that using == instead of === in ListItem class. Daniel T.'s solution is the right answer. Thank you all.
Without seeing more code, my guess is that the problem lies in your ListItem's == operator. The following code does not exhibit the problem you are describing:
//: Playground - noun: a place where people can play
class ListItem: Hashable {
var itemId: Int = 0
var itemName: String = ""
var itemDescription: String = ""
var itemIcon: String = ""
var parentId: Int = 0
var postCount: Int = 0
var hashValue: Int {
return itemId.hashValue
}
}
func ==(lhs: ListItem, rhs: ListItem) -> Bool {
return lhs === rhs // simple identity
}
var dict = Dictionary<ListItem, [ListItem]>()
let arr = [ListItem(), ListItem()]
dict[ListItem()] = arr
dict[ListItem()] = arr
dict.keys.array.count
I have a syncing engine with a server that follows this code for created updated and deleted objects
let lastSynchronizationDate = (NSUserDefaults.standardUserDefaults().objectForKey("com.fridge.sync") as [String: NSDate])["skin"]!
query.whereKey("updatedAt", greaterThanOrEqualTo: lastSynchronizationDate)
query.findObjectsInBackgroundWithBlock { (remoteSkins, error) -> Void in
if error != nil {
return
}
if remoteSkins.isEmpty {
return
}
RLMRealm.defaultRealm().transactionWithBlock {
let deletedSkins = (remoteSkins as [PFObject]).filter {
$0["state"] as Int == 0
}.map {
Skin(forPrimaryKey: $0["color"])
}.filter {
$0 != nil
}.map {
$0!
}
let createdSkins = (remoteSkins as [PFObject]).filter {
$0["state"] as Int != 0
}.filter {
let skin = Skin(forPrimaryKey: $0["color"])
println(skin.name)
return skin == nil
}.map { (remoteSkin) -> Skin in
let remoteSkinModel = RemoteSkinModel(remoteSkin)
let skin = Skin()
skin.skinModel = remoteSkinModel // Error
return skin
}
let updatedSkins = (remoteSkins as [PFObject]).filter {
$0["state"] as Int != 0
}.map {
($0, Skin(forPrimaryKey: $0["color"]))
}.filter {
$0.1 != nil
}.map {
$0.1.skinModel = RemoteSkinModel($0.0)
}
RLMRealm.defaultRealm().deleteObjects(deletedSkins)
RLMRealm.defaultRealm().addObjects(createdSkins)
}
}
My skin model:
class Skin: RLMObject {
dynamic var name: String = ""
dynamic var hexString: String = ""
var skinModel: SkinModel! {
didSet {
name = skinModel.name ?? oldValue.name ?? ""
hexString = skinModel.hexString ?? oldValue.name ?? ""
}
}
override class func primaryKey() -> String {
return "hexString"
}
override class func ignoredProperties() -> [AnyObject] {
return ["skinModel"]
}
func save() {
}
}
protocol SkinModel {
var name: String { get }
var hexString: String { get }
}
class RemoteSkinModel: SkinModel {
let remoteSkin: PFObject
let name: String
let hexString: String
init(_ remoteSkin: PFObject) {
self.remoteSkin = remoteSkin
self.name = remoteSkin["name"] as? String ?? ""
self.hexString = remoteSkin["color"] as? String ?? ""
}
}
The first the engine runs everything goes well, but the next time it pops the bad access error when comparing if the realm object is nil or not in the createdSkins' code.
Any idea as to why this would happen?
Thanks in advance
What happens if you force skin to be an optional?
let skin: Sin? = Skin(forPrimaryKey: $0["color"])
if let skin = skin {
println(skin.name)
}
return skin == nil
I think the Swift compiler may be preventing checks to nil because Skin(forPrimaryKey:) should be a failable initializer, but Realm can't mark that as such (only Apple code can have those).
If that's not the issue, let me know and we'll keep troubleshooting this together. (I work at Realm).