[SearchStockCell retain]: message sent to deallocated instance - ios

I'm getting the following error:
SearchStockCell retain]: message sent to deallocated instance 0x7f9fa1922c00
but I am having a hard time tracing the issue because whenever I profile with zombies, it stops without any warning or error(2-3 secs).
I'm using realm for this project and the data parsing is performed at background.Not sure if this information is relevant.
Is there other way to track this? or is possible I use weak for tableview cell?
Updated
class SearchStockCell: SSBaseTableCell {
#IBOutlet var symbolLabel: UILabel!
#IBOutlet var marketLabel: UILabel!
#IBOutlet var priceLabel: UILabel!
var stock: StockInfo? {
willSet{ "About to step to \(newValue)"
if let aStock = newValue {
// add KVO on newValue
aStock.addObserver(self,
forKeyPath: "price",
options: NSKeyValueObservingOptions.New,
context: nil)
aStock.addObserver(self,
forKeyPath: "change",
options: NSKeyValueObservingOptions.New,
context: nil)
}
}
didSet { "Just stepped from \(oldValue)"
if let aStock = oldValue {
// remove KVO on old value
aStock.removeObserver(self, forKeyPath: "price")
}
if let aStock = oldValue {
// remove KVO on old value
aStock.removeObserver(self, forKeyPath: "change")
}
self.configureCell()
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "price" {
self.updatePrice()
}
if keyPath == "change" {
self.updateChange()
}
}
....
...
..
.
Here is the code happened in SearchStockCell.
I will fire an API to update my visible cells(it will update my realm) which later will prompt the changes on the SearchStockCell by KVO. Note that I can't really reload the table again because I need to maintain the position of tableview and there's thousands rows of data in it.

It is really hard to guess from code but would try my best to look answer
Please look for places
have used SearchStockCell as a property somewhere if yes check for attributes strong/weak. There is possible cycle of holding each other.
Check if you are using SearchStockCell object in block, if yes try using it as weak object. Also check for other things done inside the block.
you are using KVO, check if at any point of time is observer going out of memory.
Most likely issue which I can think of right is at some place you are assigning/using SearchStockCell object as weak/strong due to which ARC is handling retain count wrongly.

It looks like you're vastly overcomplicating this situation by adding and balancing KVO on these table cells.
You mentioned that you don't want to reload the table since you'll lose your position in the scroll view. Have you considered simply saving the scroll position of the table view before reloading and then re-setting it afterwards?
As a side note, Realm will soon introduce a feature to track insertions/updates/deletions on a table view data source, so hopefully once that's out, you could use that here instead (Disclaimer: I work for Realm).

Related

Swift Keypath with UIButtons

I'm trying to get a keypath to the selected property of an IBOutlet within a class. But get:
Type 'UIButton?' has no member 'isSelected'
directly accessing the UIButton.isSelected keypath works, but doesn't fulfill my usecase.
#objc class Demo: UIViewController{
#IBOutlet #objc dynamic weak var button: UIButton!
}
var demo = Demo()
print(#keyPath(UIButton.isSelected)) // no error
print(#keyPath(Demo.button.isSelected)) // error
print(#keyPath(demo.button.isSelected)) // error
what am I missing?
#keyPath is just syntactic sugar that creates a string value, While ensuring that the keyPath is valid for the object you specify; It helps to prevent crashes when using KVO, since it validates, at compile time, that your keyPath is valid, rather than crashing at runtime if it isn't.
Accordingly, you don't specify a keyPath on a particular instance, you specify it on the object type. This is why your first line works and the second two don't.
You specify the specific object instance on which you want to observe the keyPath when you call addObserver:
demo.addObserver(someObserver, forKeyPath: #keyPath(UIButton.isSelected), options: [], context: nil)
You could also say
demo.addObserver(someObserver, forKeyPath: "selected", options: [], context: nil)
with the same result
But if you accidentally typed "slected" instead of "selected" you wouldn't find out until your app crashed at runtime, while #keyPath(UIButton.isSlected) will give you a compiler error straight away.

Referencing UIView subclass .center with Swift 3 #keyPath

In a view controller in my app, I'm reacting to changes to a view's positioning by key-value observing its center property, like this:
class CoordinatingViewController: UIViewController {
#IBOutlet weak var cardContainerView: CardView!
override func viewDidLoad() {
super.viewDidLoad()
addObserver(self, forKeyPath: "cardContainerView.center", options: [.new], context: nil)
}
}
This works just fine for now, but since that key path is a string, it can't be checked by the compiler. To mitigate this, I'd like to use Swift 3's #keyPath() syntax, which takes a compiler-checked key path and returns the proper corresponding string. However, when I change that line to use it like this:
addObserver(self, forKeyPath: #keyPath(cardContainerView.center), options: [.new], context: nil)
The compiler gives me 2 errors:
Type 'CardView!' has no member 'center'
Cannot refer to type member 'center' within instance of type 'CardView!'
I don't understand why I'm getting these errors, as center is a documented property of UIView, which CardView inherits from directly, which I can both read and write to outside the #keyPath() statement there. Furthermore, everything appears to work ok when I pass the key path directly as a string, as in my first example, which makes this even more confusing. How can I have the compiler check that key path for me?
It seems like Swift is not happy about the weak modifier. Remove it and the code will compile. From this, we can see that outlets are recommended to be strong, unless you actually have a retain cycle. See my answer here for how to find retain cycles.
In Swift 4, you will be able to do this, using the new \ key path syntax:
// no problems with "weak"!
let observation = observe(\.cardContainerView.center) { (object, change) in
...
}

iOS KVO - detect when the same value is set again

Is it possible to use KVO in a way that it detects not only if the value changed, but also if the same value was set again? I'm currently receiving notifications only when the value changed (is different from the previously set one). I need to receive notification every time the value is set (even if it's the same as the one previously set). How can I achieve this?
My code:
private func addObserver() {
defaults.addObserver(self, forKeyPath: DefaultsKeys.testKey._key, options: .new, context: nil)
}
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let value = change?[NSKeyValueChangeKey.newKey] as? Bool else { return }
statusCallback?(value)
}
private func removeObserver() {
defaults.removeObserver(self, forKeyPath: DefaultsKeys.testKey._key)
}
KVO generally is called every time the observed property is set, even if it's the same value it was last time. But I guess you're observing UserDefaults, and which has an idiosyncrasy that prevents this from happening (probably an optimization that prevents unnecessary saves of the store).
You can register for .didChangeNotification, which appears to called whether the value changed or not:
NotificationCenter.default.addObserver(forName: UserDefaults.didChangeNotification, object: nil, queue: .main) { notification in
print("notification", notification)
}
You can achieve this by doing like:
public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
let value = change?[.oldKey] as? Bool
guard value == nil || value != yourVariableToCheck else { return }
statusCallback?(value)
}
Only change yourVariableToCheck on your own variable.
KVO mechanism is very simple - it does not perform any additional checks upon setting a new value, it is merely triggered when a setter is called. Hence it is not possible to differentiate if the value is different from that which is already being set. And it's good. Firstly, because, it's not typical in practice to assign the same value to a variable.
Second, introducing additional checks would be consumable and in most cases unneeded. If that check existed, it would negatively affect performance.
That being said, as far as Swift is concerned, you can consider replacing KVO-mechanism (essentially an Objective-C legacy) with native Swift property observers: willSet and didSet. And this would essentially play the same role as by passing both options: NSKeyValueObservingOptionNew and NSKeyValueObservingOptionOld (.old and .new in Swift) to addObserver method. Once having specified these flags, whenever KVO mechanism is triggered, you will receive both values (old an new) in observeValue(...), from where you can decide what to do with either. But why would you need such complexity when willSet does practically the same and is much more conveinent:
var myVariable : String! {
willSet {
print("Old value is: \(myVariable)")
print("New value is: \(newValue)")
// Let's do something with our old value
}
didSet {
print("Just set the new value: \(newValue)")
// New value is set. Let's do some actions.
}
}
If you want to track every setting a value in NSUserDefaults, even if the new value is the same as previous, wrap the value with the NSDictionary, and put inside the dictionary an NSUUID value, generated every time as a new setValue being called.
Before (observeValueForKeyPath did not called on every setValue):
[self.mySharedDefaults setValue: #"CHECKING" forKey:#"appStatusOUT"];
After (observeValueForKeyPath being called on every setValue):
[self.mySharedDefaults setValue: [NSDictionary dictionaryWithObjectsAndKeys: #"CHECKING", #"CMD",
[NSUUID UUID].UUIDString, #"UUID", nil] forKey:#"appStatusOUT"];

Automatically update an UILabel at var update in swift

At some point of my project I got an UILabel displaying the number of element in an array.
I want to update automatically the label text when I modifying the array.
I can make my UILabel global and access it with a "didSet" on the array, but I don't want to make all my UILabel global (I got 8 UILabel for 8 different evolutive var).
Here is a way to update the UILabel content like a pointer or a reference on a specific variable ?
Edit
Some code example :
I got a global which is a dictionary,
let eg:[string : [SomeClass]]!
In a ViewController I got a UILabel, with
label.text = eg["key"].count
I want to automatically update the value display if I do something like that
eg["key"].append(something)
Since you are using global data, one approach you might try is using NSNotificationCenter. If you were to encapsulate your global data inside an object, the object could post a notification every time the data is updated.
This approach will allow multiple observers the opportunity to act on changes to global state "automatically". It will also add the benefit of keeping your UIKit elements from being exposed.
Inside the userInfo property of the posted notification, you would place the key and associated count value.
Make your view controller an observer of this notification. When it receives the notification, it can update its UILabels itself, using the data received inside userInfo.
A simple implementation might look like this:
class SomeClass {
// ...
}
class MyGlobalObject {
var eg:[String : [SomeClass]]!
static let sharedInstance = MyGlobalObject()
private init() {
eg = ["someKey":[], "someOtherKey":[]]
}
func appendTo(key:String, value:SomeClass) {
eg[key]?.append(value)
NSNotificationCenter.defaultCenter().postNotification(NSNotification(name:"ValueChanged", object: nil, userInfo: ["Key":key, "Count" : (eg[key]?.count)!]))
}
}
class ViewController: UIViewController {
// Define labels, etc....
override func viewDidLoad() {
super.viewDidLoad()
// Put this wherever makes the most sense. viewWillAppear() works too. Don't forget to remove yourself as an observer when you are done.
NSNotificationCenter.defaultCenter().addObserver(self, selector:"didUpdate:", name: "ValueChanged", object: nil)
}
func didUpdate(notif:NSNotification) {
print("Received Notification!")
let userInfo = notif.userInfo
// Update appropriate label with the data
}
}
Then anywhere in your app, you could do this:
MyGlobalObject.sharedInstance.appendTo("someKey", value: SomeClass())

How can you collect data from NSOperations into an array?

I have a number of NSOperations which create some data asynchronously. I want to collect all of the results into one array. Because I'm accessing the array on multiple different threads, I've put locking around the array.
The NSOperationQueue is appending the data to the array but the results seem to miss some of the data objects. The results seem to change each time I run it.
I've created a simplified example project that recreates the issue. The code is in Swift but I don't think this is Swift-specific.
import UIKit
class ViewController: UIViewController {
let queue = NSOperationQueue()
var bucket = [String]()
override func viewDidLoad() {
super.viewDidLoad()
queue.addObserver(self, forKeyPath: "operations", options: NSKeyValueObservingOptions.New, context: nil)
for _ in 0..<10 {
queue.addOperation(NSBlockOperation {
// Let's pretend that creating the "fish" string is actually potentially
// expensive and that's why we're doing it in an NSOperation.
let fish = "fish"
objc_sync_enter(self.bucket)
self.bucket.append(fish)
let fishCount = self.bucket.count
print("Bucket contains \(fishCount) fish" + ((fishCount != 1) ? "es" : ""))
objc_sync_exit(self.bucket)
})
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if let keyPath = keyPath {
if let object = object as? NSOperationQueue {
if object == queue && keyPath == "operations" {
if queue.operationCount == 0 {
objc_sync_enter(self.bucket)
let fishCount = bucket.count
print("Bucket finally contains \(fishCount) fish" + ((fishCount != 1) ? "es" : ""))
objc_sync_exit(self.bucket)
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
}
}
}
The results vary but are often something like this:
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 2 fishes
Bucket contains 1 fish
Bucket contains 1 fish
Bucket contains 3 fishes
Also, sometimes the code crashed with an EXC_BAD_ACCESS on the line self.bucket.append(fish)
In addition, the line print("Bucket finally contains \(fishCount) fish" + ((fishCount != 1) ? "es" : "")) in observeValueForKeyPath never gets called. I'm not sure if this is a separate issue or not.
You should look at subclassing NSOperation, since it is an abstract class.
See this Stackoverflow question for subclassing.
With that in mind I would suggest that you have an identifier property on each operation instance so that you can keep track of your operations, that way you can tell when all of your operations have finished. You might also consider pulling this code out of your view controller class and creating a class to handle your fish Plus it will help you with encapsulation further down the road when say you are no longer interested in fish but cats :)
The Concurrency Programming Guide is really good at explaining the basics of asynchronous application design.
The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation) to perform the actual task. Despite being abstract, the base implementation of NSOperation does include significant logic to coordinate the safe execution of your task. The presence of this built-in logic allows you to focus on the actual implementation of your task, rather than on the glue code needed to ensure it works correctly with other system objects.

Resources