Parse pointer to object EXC_BAD_ACCESS - ios

I have problem with parse. I have relation in my database between Event and Type. So, event has it's type, which consist of title and color(and other properties, but focus on relation)
Event:
#NSManaged var type: Type
Also, i have computer property eventColor:
var eventColor: UIColor{
return type?.typeColor ?? UIColor.clearColor()
}
And basic query:
override class func query() -> PFQuery? {
//1
let query = PFQuery(className: Event.parseClassName())
//2
query.includeKey("type")
//3
query.orderByDescending("startDate")
return query
}
Controller:
var events = [Event](){
didSet { eventsTableView.reloadData() }
}
Im making request:
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
self.activityIndicator.stopAnimating()
if let objects = objects as? [Event] where error == nil{
println("Count is: \(objects.count)")
for event in objects{
event.pinInBackground()
println("\(event.type)") //Here, type is available... and it prints out
}
self.events = objects // Property observer is called
}
else{
println("Handle error...")
}
})
And code that is called after table view reload:
tableView cellForRow....(){
let event = self.events[indexPath.row]
let color = event.eventColor // BAD ACCESS, because "type" dissappears.
}
So, after making this request, and assigning array of events to array in my view controller, i'm trying to read computer property event color and it fails(EXC_BAD_ACCESS). It seems like type object is available in block, which is executed after fetching events, but later it dissappears, and every time i want to read it, i have application runtime error.
I don't know, it feels like object is hold like weak in block(because i included "includeKey" and Type is downloaded), but later it's just immediatelly deallocated. What should i do, to prevent from deallocating type? I could make my own object of Type, not subclassing PFObject, but i believe that somewhere there should be the other, cleaner way.
Thank you
Edit:
Ok, so investigating a little bit more i discovered(cellForRow...)
I'm trying to get type and it's color property by subscripting PFObject:
let typeItem = eventItem["type"] as! PFObject
println("type is: \(typeItem)")
/*
type is: <Type: 0x7fe99bdf3330, objectId: dkEXw2r7zh, localId: (null)> {
color = "#F29C31";
name = swift;
}
*/
let colorFromType = typeItem["color"]
println("color: \(colorFromType)") //Works, returns valid value.
But writing it in other way:
let type = eventItem.type
let color = type?.color //Compiles, but runtime error:
'NSInvalidArgumentException', reason: '-[PFObject color]: unrecognized selector sent to instance 0x7fe99bdf3330'
println("type: \(type), color: \(color)")
So i'm not entirely sure, why this happens. It looks like this type property of Event could not be casted to Type, but i'm sure that's type of property.
EDIT, RESOLVED:
Ok, i'm so stupid, i didn't know about register subclass, and Type.registerSubclass resolved all problems.

It sounds like you need to typecast the object in the tableView cellForRow....() call.
Your call to .eventColor will fail if it doesn't have the cast, and the cast isn't saved to your self.events as far as I can see. Try this:
tableView cellForRow....(){
let event = self.events[indexPath.row] as! Event
let color = event.eventColor
}
Also, if you are using a subclass, you need to be sure to use registerSubclass ahead of time or your properties will not be available!

Related

Swift if let statement results in <<error type>> instead of custom object

I'm trying to use if let Swift statement to use an optional if it's not equal to nil. But for some reason, when I use it, Xcode shows the object is `<>z
I have a function which returns MyObject? and I want to check if it is nil, and if it's not I want to use it.
I'm trying to do it like this:
if let anObject = self.myFunc() {
anObject //Xcode shows that anObject is not MyObject but <<error type>>
}
(I'm using SwiftUI if that matters)
Does anyone knows why?
The if let is not allowed within body as is, so if you just need to conditionally shown some view on result of function, then the solution will be
if self.myFunc() != nil {
// some view here
}

self captured by a closure before all members were initialized - but I did initialize them

This is a toy example but it reduces exactly the situation I'm in:
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
super.init(tableView: UITableView()) { (_, _, _) -> UITableViewCell? in
print(self.string) // error
return nil
}
}
}
I'm trying to make my table view data source self-contained, and my way of doing that (so far) is to subclass UITableViewDiffableDataSource. This works fine except when I try to give my subclass a custom initializer. The toy example shows the problem.
The way I want to populate the cell absolutely depends upon a value that can change later in the life of the data source. Therefore it cannot be hard-coded into the cell provider function. I cannot refer here simply to string, the value that was passed in the initializer; I must refer to self.string because other code is going to have the power to change this data source's string instance property later on, and I want the cell provider to use that new value when that happens.
However, I'm getting the error "self captured by a closure before all members were initialized". That seems unfair. I did initialize my string instance property before calling super.init. So it does have a value at the earliest moment when the cell provider method can possibly be called.
While I'm not entirely sure why Swift doesn't allow this (something to do with capturing self to create the closure before the actual call to super.init is made), I do at least know of a workaround for it. Capture a weak local variable instead, and after the call to super.init set that local variable to self:
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
weak var selfWorkaround: MyDataSource?
super.init(tableView: UITableView()) { (_, _, _) -> UITableViewCell? in
print(selfWorkaround?.string)
return nil
}
selfWorkaround = self
}
}
The only problem with this, though, is that if the closure is executed during the call to super.init, then selfWorkaround would be nil inside the closure and you may get unexpected results. (In this case, though, I don't think it is - so you should be safe to do this.)
Edit: The reason we make the local variable weak is to prevent the self object from being leaked.
you can access self via tableView.datasource and it will sort most of the problem.
Expanding on Abhiraj Kumar's answer from Feb 16 2020, here's an example of using the TableView provided to "reach back" to get the data source you attached to the table... i.e. "self":
class MyDataSource: UITableViewDiffableDataSource<String,String> {
var string : String?
init(string:String?) {
self.string = string
super.init(tableView: UITableView()) { (tableView, _, _) -> UITableViewCell? in
// Very sketchy reach-through to get "self", forced by API design where
// super.init() requires closure as a parameter
let hack_self = tableView.dataSource! as! MyDataSource
let selfDotStr = hack_self.string
print("In closure, self.string is \(selfDotStr)")
return nil // would return a real cell here in real application
}
}
}

Downcast set of type X that are subclass of of Y

Note: although the question has a CoreData example, it's not related to CoreData, it's just an example
We are working on a Swift project with CoreData as a caching layer.
We make use of Notifications in our mainViewController a lot to listen to the changes after our NSManagedObjectContext has new changes.
This is working great until we added new entities with the following hierarchy:
Entity Vehicle is a base class with some attributes.
Entity Car is a subclass of Vehicle with specific attributes and a toMany relationship to Human entity.
Entity Human is a base class with specific attributes, and it has a relationship the Car.
The problem is in the following:
when a new Car object is added, the notification fires, and in the mainViewController, we need to check if it's of type Car, like this:
if let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<Car> {
print("we have some cars") // this will never execute
}
The type downcast Set<Car> will never evaluate to true because the Set has elements of type Car and also Human.
What I want:
Check if the Set has NSManagedObject subclass of type Car or Human as I downcast it.
What I tried to do:
downcast it to NSManagedObject, and check if the Set contains Car by the adding the following where condition:
insertedObjects.contains(Car), but it has a compile-time error:
Cannot convert value of type '(Car).Type' to expected argument type 'NSManagedObject'
Let me know if you have any question instead of just downvoting.
Not sure about the type casting (I think I remember doing it the same way and it worked, although it was with an array), but checking if there is a car in the set is different:
set.contains { (element) -> Bool in
return element is Car
}
Or shorter (more concise) version of the same call:
set.contains(where: { $0 is Car })
First downcast the inserted object to Set<NSManagedObject>.
To check if any car has been inserted, use
if let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject> {
if insertedObjects.contains(where: { $0 is Car }) {
print("we have some cars")
}
}
To get the inserted car objects as a (possibly empty) array,
use flatMap():
if let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject> {
let insertedCars = insertedObjects.flatMap { $0 as? Car }
}
Your approach
if insertedObjects.contains(Car)
does not compile because
func contains(_ member: Set.Element) -> Bool
expects an instance of the element type as argument.
As shown above, you can use the predicate-based variant
func contains(where predicate: (Element) throws -> Bool) rethrows -> Bool
instead.

How to Update uitableview from CloudKit using discoverAllIdentities

So, I am new to cloudKit and to working with multiple threads in general, which I think is the source of the problem here, so if I simply need to research more, please just comment so and I will take that to heart.
Here is my question:
I am working in Swift 3 Xcode 8.1
I have in my view controller this variable:
var contactsNearby: [String:CLLocation]?
Then at the end of ViewDidLoad I call one of my view controllers methods let's call it:
populateContactsNearby()
inside that method I call:
container.discoverAllIdentities(completionHandler: { (identities, error) in
for userIdentity in identities! {
self.container.publicCloudDatabase.fetch(withRecordID: userIdentity.userRecordID!, completionHandler: { (userRecord, error) in
let contactsLocation = userRecord?.object(forKey: "currentLocation")
if self.closeEnough(self.myLocation!, contactLocation: contactsLocation as! CLLocation) {
var contactsName = ""
contactsFirstName = userIdentity.nameComponents?.givenName
if contactsName != "" && contactsLocation != nil {
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
}
}
})
}
})
}
I apologize if I am missing or have an extra bracket somewhere. I have omitted some error checking code and so forth in order to get this down to bare-bones. So the goal of all that is to populate my contactsNearby Dictionary with data from CloudKit. A name as the key a location as the value. I want to use that data to populate a tableview. In the above code, the call to closeEnough is a call to another one of my view controllers methods to check if the contact from CloudKit has a location close enough to my user to be relevant to the apps purposes. Also myLocation is a variable that is populated before the segue. It holds the CLLocation of the app users current location.
The Problem:
The if statement:
if contactsName != "" && contactsLocation != nil { }
Appears to succeed. But my view controllers variable:
var contactsNearby: [String:CLLocation]?
Is never populated and I know there is data available in cloudKit.
If it's relevant here is some test code that I have in cellForRowAtIndexPath right now:
let contact = self.contactsNearby?.popFirst()
let name = contact?.key
if name != nil {
cell.textLabel?.text = name
}else {
cell.textLabel?.text = "nothing was there"
}
My rows alway populate with "nothing was there". I have seen answers where people have done CKQueries to update the UI, but in those answers, the user built the query themselves. That seems different from using a CloudKit function like discoverAllIdentities.
I have tried to be as specific as possible in asking this question. If this question could be improved please let me know. I think it's a question that could benefit the community.
Okay, I need to do some more testing, but I think I got it working. Thank you Paulw11 for your comment. It got me on the right track.
As it turns out there were 2 problems.
First, as pointed out I have an asynchronous call inside a for loop. As recommended, I used a dispatchGroup.
Inside the cloudKit call to discoverAllIdentities I declared a dispatchGroup, kind of like so:
var icloudDispatchGroup = DispatchGroup()
Then just inside the for loop that is going to make an async call, I enter the dispatchGroup:
icloudDispatchGroup.enter()
Then just before the end of the publicCloudDatabase.fetch completion handler I call:
icloudDispatchGroup.leave()
and
icloudDispatchGroup.wait()
Which, I believe, I'm still new to this remember, ends the dispatchGroup and causes the current thread to wait until that dispatchGroup finishes before allowing the current thread to continue.
The Above took care of the multithreading issue, but my contactsNearby[String:CLLocation]? Dictionary was still not being populated.
Which leads me to the 2nd problem
At the top of my view controller I declared my Dictionary:
var contactsNearby: [String: CLLocation]?
This declared a dictionary, but does not initialize it, which I don't think I fully realized, so when I attempted to populate it:
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
It quietly failed because it is optional and returned nil
So, in viewDidLoad before I even call populateContactsNearby I initialize the dictionary:
contactsNearby = [String:CLLocation]()
This does not make it cease to be an optional, which Swift being strongly typed would not allow, but merely initializes contactsNearby as an optional empty Dictionary.
At least, that is my understanding of what is going on. If anyone has a more elegant solution, I am always trying to improve.
In case you are wondering how I then update the UI, I do so with a property observer on the contactsNearby Dictionary. So the declaration of the dictionary at the top of the view controller looks like this:
var contactsNearby: [String: CLLocation]? {
didSet {
if (contactsNearby?.isEmpty)! || contactsNearby == nil {
return
}else{
DispatchQueue.main.sync {
self.nearbyTableView.reloadData()
}
}
}
}
I suppose I didn't really need to check for empty and nil. So then in cellForRowAtIndexPath I have something kind of like so:
let cell = tableview.dequeueReusableCell(withIdentifier: "nearbyCell", for: indexPath)
if contactsNearby?.isEmpty == false {
let contact = contactsNearby?.popFirst()
cell.textLabel?.text = contact?.key
}else {
cell.textLabel?.text = "Some Placeholder Text Here"
}
return cell
If anyone sees an error in my thinking or sees any of this heading for disaster, feel free to let me know. I still have a lot of testing to do, but I wanted to get back here and let you know what I have found.

Use Realm with Collection View Data Source Best Practise

I'll make it short as possible.
I have an API request that I fetch data from (i.e. Parse).
When I'm getting the results I'm writing it to Realm and then adding them to a UICollectionView's data source.
There are requests that take a bit more time, which run asynchronous. I'm getting the needed results after the data source and collection view was already reloaded.
I'm writing the needed update from the results to my Realm database.
I have read that it's possible to use Realm's Results. But I honestly didn't understood it. I guess there is a dynamic and safe way working with collection views and Realm. Here is my approach for now.
This is how I populate the collection view's data source at the moment:
Declaration
var dataSource = [Realm_item]()
where Realm_item is a Realm Object type.
Looping and Writing
override func viewDidLoad() {
super.viewDidLoad()
for nowResult in FetchedResultsFromAPI
{
let item = Realm_item()
item.item_Title = nowResult["Title"] as! String
item.item_Price = nowResult["Price"] as! String
// Example - Will write it later after the collectionView Done - Async request
GetFileFromImageAndThanWriteRealm(x.image)
// Example - Will write it later after the collectionView Done - Async request
dataSource.append(item)
}
//After finish running over the results *Before writing the image data*
try! self.realm.write {
self.realm.add(self.dataSource)
}
myCollectionView.reloadData()
}
After I write the image to Realm to an already created "object". Will the same Realm Object (with the same primary key) automatically update over in the data source?
What is the right way to update the object from the data source after I wrote the update to same object from the Realm DB?
Update
Model class
class Realm_item: Object {
dynamic var item_ID : String!
dynamic var item_Title : String!
dynamic var item_Price : String!
dynamic var imgPath : String?
override class func primaryKey() -> String {
return "item_ID"
}
}
First I'm checking whether the "object id" exists in the Realm. If it does, I fetch the object from Realm and append it to the data source. If it doesn't exist, I create a new Realm object, write it and than appending it.
Fetching the data from Parse
This happens in the viewDidLoad method and prepares the data source:
var query = PFQuery(className:"Realm_item")
query.limit = 100
query.findObjectsInBackgroundWithBlock { (respond, error) -> Void in
if error == nil
{
for x in respond!
{
if let FetchedItem = self.realm.objectForPrimaryKey(Realm_item.self, key: x.objectId!)
{
self.dataSource.append(FetchedItem)
}
else
{
let item = Realm_item()
item.item_ID = x.objectId
item.item_Title = x["Title"] as! String
item.item_Price = x["Price"] as! String
let file = x["Images"] as! PFFile
RealmHelper().getAndSaveImageFromPFFile(file, named: x.objectId!)
self.dataSource.append(item)
}
}
try! self.realm.write {
self.realm.add(self.dataSource)
}
self.myCollectionView.reloadData()
print(respond?.count)
}
}
Thank you!
You seem to have a few questions and problems here, so I'll do my best.
I suggest you use the Results type as your data source, something like:
var dataSource: Results<Realm_item>?
Then, in your viewDidLoad():
dataSource = realm.objects(Realm_item).
Be sure to use the relevant error checking before using dataSource. We use an optional Results<Realm_item> because the Realm object you're using it from needs to be initialised first. I.e., you'll get something like "Instance member * cannot be used on type *" if you try declaring the results like let dataSource = realm.objects(Realm_item).
The Realm documentation (a very well-written and useful reference to have when you're using Realm as beginner like myself), has this to say about Results...
Results are live, auto-updating views into the underlying data, which means results never have to be re-fetched. Modifying objects that affect the query will be reflected in the results immediately.
Your mileage may vary depending on how you have everything set up. You could try posting your Realm models and Parse-related code for review and comment.
Your last question:
What is the right way to update the "object" from the Data Source after i wrote the update to same object from the Realm DB?
I gather you're asking the best way to update your UI (CollectionView) when the underlying data has been updated? If so...
You can subscribe to Realm notifications to know when Realm data is updated, indicating when your app’s UI should be refreshed for example, without having to re-fetch your Results.

Resources