Thread-safe way of instantiating optional Realm objects - ios

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.

Related

Singleton variable not updating

The value of the variable 'switcheroo' in the view controller below is always the same when I attempt to access it via a singleton. I am trying to access its value from a custom label class that prints the characters of the label one by one. When the label is set, I try to get the updated value of switcheroo in the Viewcontroller singleton. However it always returns the initial value of switcheroo, not the updated value (which I can trace in the viewcontroller). Am I doing something wrong?
class TheViewController: UITableViewController, UIGestureRecognizerDelegate, UITabBarControllerDelegate {
static let shared = TheViewController()
var switcheroo = 0
... various operations that change the value of switcheroo...
}
class CustomLabel: UILabel {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var attributedText: NSAttributedString? {
didSet {
DispatchQueue.main.async {
let characterDelay = TimeInterval(0.01 + Float(arc4random()) / Float(UInt32.max)) / 100
for (index, _) in attributedText.string.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
print("switcheroo value in TheViewController is now: \(TheViewController.shared.switcheroo)")
super.attributedText = attributedText.attributedSubstring(from: NSRange(location: 0, length: index+1))
}
}
}
}
I would not suggest making a view controller a singleton solely for the purpose of some shared state or model property. View controllers have their own life cycle patterns (e.g. instantiated from storyboard scenes, released when they are dismissed, recreated when presented again, etc.), and you’re likely to have issues arising from dealing with separate instances of your view controller(s).
Instead, don’t try to fight the standard view controller life cycle, but rather just move this property into a separate object, e.g.
final class StateManager {
static let shared = StateManager()
var switcheroo = 0
private init() { }
}
Then your view controllers can use that:
class ViewController: UIViewController {
...
func examineSwitcheroo() {
print(StateManager.shared.switcheroo)
}
func updateSwitcheroo(to value: Int) {
StateManager.shared.switcheroo = value
}
}
This way, you enjoy this shared state, without entangling normal view controller life cycles in this process.
Now, what the right name for this singleton, StateManager in my example, depends entirely upon what this shared property is. But there’s not enough information about what this switcheroo object really is to offer better counsel on this matter.
And, probably needless to say, it’s a separate question as to whether you really should be using singletons at all for state variables and model objects, but that’s beyond the scope of this question.
If you have determined that having a ViewController singleton is the right decision, the likely answer is that you are not using that shared instance every time, instead accidentally calling the initializer at some point in your project (possibly Xcode is doing it automatically through interfaces).
To search through your entire project, you can use cmd + shift + F and then type TheViewController(). There should only be one occurrence (the shared instance). Be sure to also check for TheViewController.init(). That will find any time you do it.
If the issue persists, perhaps try setting the shared instance to self in the viewDidLoad method of TheViewController?
Hope this helps!
Don't manage your application's data in your view controller(s). The Cocoa and Cocoa Touch frameworks use the MVC paradigm, where the M is meant to stand for model, i.e. the application's data model. Any data that needs to be preserved, or that's relevant beyond the scope of the view controller, should be stored and managed in a model object. If you give your view controller's a reference to the model when you create them, you never need to worry about passing data from one view controller to another; instead, they each operate on the model, and any data they need comes from the model.

rxswift block life cycle

I have a viewmode class like this :
class ViewMode {
let validateCountResult: Driver<Bool>
init(username: Driver<String>) {
validateCountResult = username
.flatMapLatest { username in
return // validate username
}
}
And I have a subclass of UIViewController as follow :
class ViewController : UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
let viewmode =
ViewMode(textfiled.rx.texttext.orEmpty.asDriver())
viewmode.validateCountResult.drive(onNext:{
// TODO Something
FuncA()
})
}
}
When viewDidload finishes, I believe the viewmode should deinits as well. But I see the binding still exists and FuncA still get called!
Why is it so?
The easiest way to understand binding is to think of the relationship as a connection between two entities:
A producer, which produces the value。
A receiver, which processes the values from the producer.
A receiver cannot return a value. This is a general rule when using bindings of RxSwift.
The fundamental function of binding is bind(to:), and to bind an observable to another entity it's required that the receiver conforms to ObserverType.
You should dispose your Disposable types by creating a DisposeBag.
If you don’t, there is no guarrantee that your viewController even deinits or your disposable types get disposed as well. They create a mutal like together via your ViewMode and all three objects stay in memory.
Take a look at this article to see how to find memory leaks.

How to create initial Realm objects that get added upon installation of app

Say I am creating an object that takes two strings and acts like a dictionary.
class WordInDictionary: Object {
#objc dynamic var word: String = ""
#objc dynamic var meaning: String = ""
What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?
Also, is there a way to make it so that just those initial objects can't be deleted?
"What should I do if I wanted to have some initial objects that get added to the database just once upon installation/update of the app?"
One option would be to have some code near the realm initialisation that checks if there are any WordInDictionary objects already in the realm - if not then add the required default objects.
E.g.
let realm = try! Realm()
if realm.objects(WordInDictionary.self).isEmpty
{
// Add required words here
}
"Also, is there a way to make it so that just those initial objects can't be deleted?"
I don't know of a way to make realm objects read-only. You'd have to implement this in code in some way, e.g. have a isDeletable boolean member which is true for every user-created object and false for your default members, then only delete those from realm.
E.g. for your deletion code:
func deleteWords(wordsToDelete: Results<WordInDictionary>)
{
try! realm.write
{
realm.delete(wordsToDelete.filter("isDeletable = true")
}
}

KVO not working for custom property of NSManagedObject

I have a subclass of NSManagedObject Folder with a state of Availability
#objc enum Availability: Int16 {
case unknown
case available
case unavailable
}
Folder has to do extra stuff (like delete related files) whenever it's availability changes. So I have
internalAvailability saved in core data
Computed property availability using above property
`
extension Folder {
#NSManaged private var internalAvailability: Availability
}
extension Folder {
private func deleteFiles(...) {
...
}
#objc dynamic public var availability: Availability {
get {
return internalAvailability
}
set {
willChangeValue(forKey: "availability")
deleteFiles()
internalAvailability = newValue
didChangeValue(forKey: "availability")
}
}
}
Using Reactive, I want to change navigation item's title based on availability but the signal is never called after once!
```
let property = DynamicProperty<NSNumber>(object: folder, keyPath: "availability")
internalVariable = property // To have a reference of property
navigationItem.reactive.title <~ property.map { (stateNumber) -> String in
guard let a = Availability(rawValue: stateNumber.int16Value) else {
assertionFailure()
return ""
}
let prefix = a == .available ? "" : "(Nope) "
return "\(prefix)\(folder.name)"
}
I have explicitly added KVO compliance to the property in hopes that this starts working, but alas no results.
Edit: if I create the DynamicProperty on internalAvailability instead of availability, everything works smoothly..
Adding as an answer since it became a learning exercise. Hopefully someone else too would be benefitted.
The app uses a multiple managedObjectContext(moc) architecture. 1 private moc to make changes and 1 main thread moc that synchronises itself using mergeChanges.
In above code, navigationItem is using the folder instance kept with main-moc. The DynamicProperty is listening to KVO changes on this main-moc's folder instance. Let's call this main-folder. When I make changes, I modify the folder instance we have on private-moc. Let's call it private-folder.
On modifying private-folder and calling save on private-moc, a notification of name NSManagedObjectContextDidSave is broadcasted. main-moc synchronizes itself using mergeChanges.
mergeChanges changes main-folder, but notice that it would never call the computed-property-setter availability. It directly changes internalAvailability.
And thus, no KVO notifications are posted of our computed property.
TL;DR When doing KVO on a NSManagedObject subclass, use a stored property instead of computed one. In case you have a multi-moc (managed object context) scenario and use mergeChanges to synchronise, setter for your computed property is not called when synchronising.
Edit (Solution): add method of the pattern keyPathsForValuesAffecting<KeyName> KVO relevant documentation
#objc class func keyPathsForValuesAffectingAvailability() -> Set<NSObject> {
return [#keyPath(Folder.internalAvailability) as NSObject]
}
When using Core Data we use the NSManagedObjectContextObjectsDidChange notification instead of KVO. This brings many advantages including coalescing of change events and undo support. If we need to know what attributes changed on an object we can examine changedValuesForCurrentEvent which even includes transient attributes that have a matching keyPathsForValuesAffecting.... These advantages likely outweigh those from a KVO binding framework aka reactive.

(Swift) How can I refer to an object's property from a different class?

I am working on making an iPhone app with Swift in Xcode. I have a switch object in one view controller, let's call it switchX, and I want an if statement in another class to refer to the state of that switch.
Here is a sample of my code
import UIKit
class Settings
{
#IBOutlet var switchX: UISwitch!
...
}
import UIKit
class Game
{
#IBAction func buttonGame(sender: UIButton)
if //here's where I need some way to determine the state of switchX
{
...
}
}
This may be SUPER simple, I am extremely new to swift.
You will need access to the instance of your Settings class.
var mySettings = <wherever your instance came from>
if (mySettings.switchX.on) {
// Your switch is on.
}
As a side note, you should be updating a data model that is disjoint from your view. Run-time information should be stored in a run-time singleton data model. If you want this information to persist between app launches, you should consider using NSUserDefaults or a sqlite database or CoreData.

Resources