I have a Realm object called Task. I'm displaying these tasks in a table view. I have a variable decalred to hold these objects.
var tasks: Results<Task>!
At initial launch, I'm getting these tasks from an API. Until the local realm is empty. But still when the UITableViewController loads, it fires the data source methods. At this point, the tasks variable can still be nil. So the app crashes at methods like numberOfRowsInSection.
How can I initialize the above variable so that it can be in an empty state and it won't cause crashes?
So the app crashes at methods like numberOfRowsInSection
Don't let it. You are the one who is causing the crash, by assuming that tasks is not nil when in fact it can be. It is your job to check in numberOfSections to see whether tasks is nil. If it is, return 0 so you don't get asked any other questions.
The following code will demonstrate the way how to initialize an empty Results object.
class TaskManager {
private var _realm: Realm?
var realm: Realm {
if _realm == nil {
_realm = try! Realm()
}
return _realm!
}
var tasks: Results<Task> = realm.objects(Task.self).filter("FALSEPREDICATE")
}
Related
I have a Realm object called Trip. It stores data of a user's movement.
class Trip: Object {
dynamic var id: Int = 0
dynamic var startTimestamp: Int64 = 0
dynamic var endTimestamp: Int64 = 0
dynamic var distance: Double = 0.0
dynamic var calories: Double = 0.0
dynamic var averageSpeed: Double = 0.0
}
In the view controller, I keep a class-level variable called trip.
fileprivate var trip: Trip?
Whenever a user starts a trip, I initialize a Trip object and assigns it to this variable.
trip = Trip()
And throughout the user's movements, I keep updating this trip object with the data.
I need to save this data to the Realm database every 1 minute. So I run a timer.
Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(save()), userInfo: nil, repeats: true)
Which executes a function to save this object in a background thread.
fileprivate func save() {
do {
DispatchQueue(label: "RealmBackgroundThread").async {
autoreleasepool {
let realm = try! Realm()
try! realm.write {
realm.add(self.trip!, update: true)
}
}
}
} catch {
}
}
Up to here, it works fine. The problem is after the first save, when I try to access that trip object again, it crashes with the following error.
libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.
I think this happens because I open a new Realm to save this object in a background thread. I know that Realm isn't thread-safe.
But I'm not sure how to resolve this. How do I keep using the same trip object after saving it?
To quote from Realm's documentation about Passing Instances Across Threads:
Instances of Realm, Results, or List, or managed instances of Object are thread-confined, meaning that they can only be used on the thread on which they were created, otherwise an exception is thrown.
In your case, self.trip is a managed instance of an Object subclass, and you appear to be accessing it from both the main thread, and repeatedly from the serial dispatch queue you create within your save() method. It's important to keep in mind that the call to DispatchQueue.async will result in your code being executed on different threads depending on the whims of Grand Central Dispatch. That is to say that two consecutive calls like …
DispatchQueue(label: "RealmBackgroundThread").async {
// work
}
… will often result in the work being performed on two different threads.
Realm's documentation on Passing Instances Across Threads contains an example of how to pass a thread-safe reference to an object across threads and resolve it in the Realm instance on the target thread.
It's hard to provide a more specific suggestion in your case as I'm not clear what you're trying to do with your save() method and why you're calling it on a timer. Managed Object instances (instances retrieved from a Realm, or that have already been added to a Realm) can only be modified within a write transaction, so any modifications to self.trip would need to be performed within the write transaction opened by your save() method for it to serve any purpose. For the pattern of using a timer to make sense you'd need to leave self.trip as an unmanaged object, at which point save() would update the Trip instance with the same ID in the Realm file. To do this you'd want to use Realm.create(_:value:update:) instead of Realm.add(_:update:), as create does not convert unmanaged instances to managed instances like add does.
What I am trying to do is set up a series of AVPlayers which can be accessed via instance variables. Everything is stored in Realm and I've instantiated Collection with all the Realm vars in the previous view. Once the view loads, I want to load all the players, and then trigger them using some parameters from location. The thing is, when locationManager gets called, the player returns nil on the Player instance.
I'm guessing this is due to locationManager being called on a separate thread? What would be the thread-safe way of making sure the players have been instantiated if that is the case?
Here's the view with locationManager and the loader
class WalkMapViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
/* …lots of outlets */
var collection:Collection!
override func viewDidLoad() {
/* …lots of other init stuff */
self.loadPlayers()
}
func loadPlayers() {
// get a url from somewhere
for (i,_) in self.collection.players.enumerated() {
// loop through the players and load them
self.collection.players[i].player = AVPlayer(url: url)
}
}
func locationManager(_ manager:CLLocationManager, didUpdateLocations locations:[CLLocation]) {
for (i, _) in self.collection.players.enumerated() {
self.collection.players[i].play()
}
}
}
This is the heavily shortened Collection class which is a Realm object and which has players in ignoreProperties.
class Collection: Object, Mappable {
var players = List<Player>()
}
This is the Player class which is also a Realm object embedded in Collection, and has player in ignoreProperties
class Player: Object, Mappable {
var player:AVPlayer?
}
Your problem exists due to the fact that you are using ignored properties, which are not persisted by Realm. According to this GitHub issue about ignored properties, what you experience is the expected behaviour.
From the linked issue:
"Ignored properties are only guaranteed to be consistent across
individual Swift instances, but retrieving an object from a collection
creates a new Swift instance each time, thus the behavior that you
see."
This means that the self.collection.players[i] inside locationManager(didUpdateLocations:) is actually a new Swift instance of the same Realm object that you created in loadPlayers(), which is not an issue with normal Realm properties, since they are fetched from the Realm database, but is causes an issue with ignored properties, since they are linked to a specific Swift instance and only exist in memory. Hence, when you retrieve a specific object from a collection (Player instance from `List'), all ignored properties are lost.
To sum it up, you won't be able to use ignored Realm properties in collections.
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.
My question is very similar to several others here but I just can't get it to work. I'm making an API call via a helper class that I wrote.
First I tried a standard function with a return value and the result was as expected. The background task completed after I tired to assign the result.
Now I'm using a closure and I can get the value back into my view controller but its still stuck in the closure, I have the same problem. I know I need to use GCD to get the assignment to happen in the main queue.
this is what I have in my view controller
var artists = [String]()
let api = APIController()
api.getArtistList("foo fighters") { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
print("in the closure: \(artists)")
}
}
}
print ("method 1 results: \(artists)")
as the results are:
method 1 results: []
in the closure: [Foo Fighters & Brian May, UK Foo Fighters, John Fogerty with Foo Fighters, Foo Fighters, Foo Fighters feat. Norah Jones, Foo Fighters feat. Brian May, Foo Fighters vs. Beastie Boys]
I know why this is happening, I just don't know how to fix it :( The API calls need to be async, so what is the best practice for capturing these results? Based on what the user selects in the table view I'll be making subsequent api calls so its not like I can handle everything inside the closure
I completely agree with the #Craig proposal of the use of the GCD, but as your question involves the request of the API call every time you select a row, you can do the following:
Let's suppose you use the tableView:didSelectRowAtIndexPath: method to handle the selection, then you can do the following inside it:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// it is just a form to get the item
let selectedItem = items.objectAtIndex(indexPath.row) as String
api.getArtistList(selectedItem) { (thelist) -> Void in
if let names = thelist {
dispatch_async(dispatch_get_main_queue()) {
artists = names
}
}
}
}
And then you can observe the property and handle do you want inside it :
var artists: [String] = [] {
didSet {
self.tableView.reloadData() // or anything you need to handle.
}
}
It just another way to see it. I hope this help you.
The easy solution is to do whatever you're doing at your print(), inside the closure.
Since you're already dispatch_asyncing to the main queue (the main/GUI thread), you can complete any processing there. Push a new view controller, present some modal data, update your current view controller, etc.
Just make sure that you don't have multiple threads modifying/accessing your local/cached data that is being displayed. Especially if it's being used by UITableViewDelegate / UITableViewDataSource implementations, which will throw fits if you start getting wishy-washy or inconsistent with your return values.
As long as you can retrieve the data in the background, and the only processing that needs to occur on the main thread is an instance variable reassignment, or some kind of array appending, just do that on the main thread, using the data you retrieved on the back end. It's not heavy. If it is heavy, then you're going to need more sophisticated synchronization methods to protect your data.
Normally the pattern looks like:
dispatch_async(getBackgroundQueue(), {
var theData = getTheDataFromNetwork();
dispatch_async(dispatch_get_main_queue() {
self.data = theData // Update the instance variable of your ViewController
self.tableView.reloadData() // Or some other 'reload' method
});
})
So where you'd normally refresh a table view or notify your ViewController that the operation has completed (or that local data has been updated), you should continue your main-thread processing.
I'm new in swift and I want to know if the beast approach to reuse an array is to delete all object inside or reinitialize it.
For example if I have:
var my_array:NSMutableArray! //global variable
and the first method that I use:
func firstMethod{
my_array = NSMutableArray()
my_array.addObject("one")
my_array.addObject("two")
my_array.addObject("three")
...
}
for example now in another method I need my_array empty:
the best solution is this:
func newmethod() {
my_array = NSMutableArray() //reinitialize (drastic method)
//here, all new operation with my_array
}
or is this?:
func newmethod() {
if my_array == nil{
my_array = NSMutableArray()
} else {
my_array.removeAllObjects()
}
}
The general answer is it depends. There is a really major danger when it comes to having mutable public properties, particularly when it comes to multithreaded apps.
If you always reinitialize, it is more likely that you're safe from multithreading issues.
If you are emptying the array, you are prone multithreading issues. You're probably going to get index out of bounds crashes and the like when you are emptying the array on one thread and trying to access any index on another thread.
Thread A check's the array's count. It's 10. Thread B empties the
array. Array's count is now 0. Thread A tries to access an object at
index 3. Crash.
Even though your code looks fine, something like this:
if array.count > 3 {
array[3].doThings()
}
Multithreading means that another thread mutating the array between that code checking the count and that code accessing an index.
I would opt for immutable objects as much as possible, and I would avoid mutating mutable objects that have any chance of being accessed in a thread unsafe manner.
Why you are reinitializing the array there is no need to reinitialize it.And if you reinitialize then it will point to another memory address so it would be better to reuse without reinitialize. You can do like this
if (my_array == nil){
my_array = NSMutableArray();
}
else{
//my_array.removeAllObjects();
// Do your stuff here
}