Add property observer to global variable inside class in Swift - ios

I have a variable globalVariable declared at global scope that may change at any time.
Different ViewControllers in my app need to react differently, when globalVariable changes.
Thus it would be desirable to add a property observer in each ViewController that execute the needed code when globalVariable changes.
I cannot seem to achieve it with override or extension. What is the way to go here?

If your goal is to simply know when your global variable changed, you could have it post a notification upon change:
extension NSNotification.Name {
static let globalVariableChanged = NSNotification.Name(Bundle.main.bundleIdentifier! + ".globalVariable")
}
var globalVariable: Int = 0 {
didSet {
NotificationCenter.default.post(name: .globalVariableChanged, object: nil)
}
}
Then any object can add an observer for that notification:
class ViewController: UIViewController {
private var observer: NSObjectProtocol!
override func viewDidLoad() {
super.viewDidLoad()
// add observer; make sure any `self` references are `weak` or `unowned`; obviously, if you don't reference `self`, that's not necessary
observer = NotificationCenter.default.addObserver(forName: .globalVariableChanged, object: nil, queue: .main) { [weak self] notification in
// do something with globalVariable here
}
}
deinit {
// remember to remove it when this object is deallocated
NotificationCenter.default.removeObserver(observer)
}
}
Note, this didSet mechanism will not detect changes if (a) the global variable is a reference type, i.e. a class; and (b) it merely mutates the object that the global variable references rather than replacing it with a new instance. To identify that scenario, you need to use KVO or other mechanism to detect mutation.

There can be only one didSet{} function for your global variable and it must belong to the variable itself. What you can do is make the variable's didSet{} function call a list of functions from other objects.
You could use notifications for this or you could build your own mechanism.
Here's an example of how you could create your own mechanism:
(note that this is pretty generic and could work for any variable types or singleton instance)
// Container for an observer's function reference
// - will be used to call the observer's code when the variable is set
// - Separates the object reference from the function reference
// to avoid strong retention cycles.
struct GlobalDidSet<T>
{
weak var observer:AnyObject?
var didSetFunction:(AnyObject)->(T)->()
init(_ observer:AnyObject, didSet function:#escaping (AnyObject)->(T)->())
{
self.observer = observer
didSetFunction = function
}
}
// Container for a list of observers to be notified
// - maintains the list of observers
// - automatically clears entries that non longer have a valid object
// - calls all observers when variable changes
// - erases type of observer to allow generic use of GlobalDidSet<>
struct GlobalDidSets<T>
{
var observers : [GlobalDidSet<T>] = []
mutating func register<O:AnyObject>(_ observer:O, didSet function:#escaping (O)->(T)->())
{
let observer = GlobalDidSet<T>(observer)
{ (object:AnyObject) in function(object as! O) }
observers.append(observer)
}
mutating func notifyDidSet(_ oldValue:T)
{
observers = observers.filter{$0.observer != nil}
observers.forEach{ $0.didSetFunction($0.observer!)(oldValue) }
}
}
...
// To use this, you will need a second variable to manage the list of observers
// and your global variable's didSet{} must use that observer list
// to perform the multiple function calls
//
var globalVariableDidSets = GlobalDidSets<String>()
var globalVariable : String = "Initial Value"
{
didSet { globalVariableDidSets.notifyDidSet(oldValue) }
}
// In your view controllers (or any other class), you need to setup the
// reaction to the global variable changes by registering to the observer list
//
class MyVC:UIViewController
{
override func viewDidLoad()
{
globalVariableDidSets.register(self){ $0.handleVariableChange }
// ...
}
func handleVariableChange(_ oldValue:String)
{
//...
}
}

Related

Holding a Strong ref to local object that uses closure [Swift]

I have a question regarding holding a Strong ref to local object that uses closure.
I have the following code, which object B uses a method with closure of a local object of type A.
The method in object A, uses async action to perform some network task and then return the closure to object b.
Since object A is local in a method in B and since i'm using [weak self] in the the object A async task (to prevent retain cycle), the object gets released.
What should i change in the following code in order to ensure the local A object will get released only when the closure is done?
This is part of the important code:
class A {
var restAPI: RestAPI?
func fetchNews(completion: (_ json: [String:Any])->()) {
// .....
self.restAPI.fetch(url: url, results: { [weak self] (json) in //
completion(json)
})
// .....
}
}
class B {
// ....
// ... call to updateNews()
func updateNews() {
let aFetcher: A()
aFetcher.fetchNews(completion : {
// <<<< // aFetcher gets released and closue never called
// parse...
}
}
}
You declare aFetcher as a let in the scope of func updateNews()
When the scope of updateNews() reaches its end aFetcher will be released.
Your have [weak self] in your internal fetch function.
On this stage aFetcher will be released because updateNews() finish its executing and there are no strong references to this object.
You just need to add variable aFetcher to class B to ensure you have strong reference to aFetcher.
class B {
// MARK: - Vars
private let aFetcher = A()
// MARK: - Public
func updateNews() {
aFetcher.fetchNews(completion : {
// parse...
}
}
}
You need a strong reference on the top level of the class.
However not to keep the reference permanently and to retain and release it reliably add a optional stored property in class B and set it to nil in the completion closure:
class B {
var fetcher : A?
// MARK: - Public
func updateNews() {
fetcher = A()
fetcher!.fetchNews(completion : { [unowned self] in
// parse...
self.fetcher = nil
}
}
}

Delegate becomes nil in Operation of urlSession. How to keep delegate variable in separate thread?

I'm using an OperationQueue to upload files one by one to a remote server using URLSession.dataTask. A delegate is used to update a progress bar but after implementing OperationQueue my delegate becomes nil. It worked without OperationQueues. Looking at the stack while the program is running I don't see my progress bar's view controller. It's been a few days and I still can't quite figure it out. I'm guessing the view controller is getting deallocated but I'm not sure how to prevent it from getting deallocated. Thank you.
I have my delegate set to self in NetWorkViewController but inside my NetworkManager class's urlSession(didSendBodyData), the delegate becomes nil. The delegate is not weak and is a class variable.
However, my delegate becomes none-nil again within the completion block of my BlockOperation. This works for dismissing the ViewController via delegation. But the delegate is nil when I'm trying to update inside urlSession(didSendBodyData)...
UPDATE 10/30/2018
It seems that my urlSessions delegates are on a separate thread and is enqueued to the main thread when called but I lose reference to my custom delegate that updates the UI. I'm trying to read more about multithreading but any help would be appreciated!
UPDATE 2 10/30/2018
Solution found The issue was that I was creating another instance of NetworkManager inside each operation. This causes the delegate to be nil because a new instance of NetworkManager is being created for each operation. The fix is to pass self from the original NetworkManager so the delegate is retained.
uploadFiles
func uploadFiles(item: LocalEntry) {
let mainOperation = UploadMainFileOperation(file: item)
// This is where I need to give the operation its
// networkManager so the proper delegate is transferred.
mainOperation.networkManager = self
mainOperation.onDidUpload = { (uploadResult) in
if let result = uploadResult {
self.result.append(result)
}
}
if let lastOp = queue.operations.last {
mainOperation.addDependency(lastOp)
}
queue.addOperation(mainOperation)
....
....
let finishOperation = BlockOperation { [unowned self] in
self.dismissProgressController()
for result in self.result {
print(result)
}
self.delegate?.popToRootController()
}
if let lastOp = queue.operations.last {
finishOperation.addDependency(lastOp)
}
queue.addOperation(finishOperation)
queue.isSuspended = false
}
UploadMainFileOperation
class UploadMainFileOperation: NetworkOperation {
let file: LocalEntry
// First issue is here. I re-declared another NetworkManager that doesn't have its delegate properly set.
private let networkManager = NetworkManager()
// I have since have this class receive the original networkManager after it's declared.
var networkManager: NetworkManager?
var onDidUpload: ((_ uploadResult: String?) -> Void)!
init(file: LocalEntry) {
self.file = file
}
override func execute() {
uploadFile()
}
private func uploadFile() {
networkManager.uploadMainFile(item: file) {
(httpResult) in
self.onDidUpload(httpResult)
self.finished(error: "upload main")
}
}
}
urlSession(didSendBodyData)
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
// This is wrong.
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
updateDelegateWith(progress: uploadProgress)
// This is the correct way for my situation.
// Because each operation on the queue is on a separate thread. I need to update the UI from the main thread.
DispatchQueue.main.async {
let uploadProgress: Float = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
self.updateDelegateWith(progress: uploadProgress)
}
}
updateDelegateWith(progress: Float)
func updateDelegateWith(progress: Float) {
delegate?.uploadProgressWith(progress: progress)
}
NetworkManagerViewController where the progress bar lives
class NetworkViewController: UIViewController, NetWorkManagerDelegate {
var localEntry: LocalEntry?
var progressBackground = UIView()
var progressBar = UIProgressView()
func uploadProgressWith(progress: Float) {
progressBar.progress = progress
view.layoutSubviews()
}
deinit {
print("deallocate")
}
override func viewDidLoad() {
super.viewDidLoad()
let networkManager = NetworkManager()
networkManager.delegate = self
networkManager.uploadFiles(item: self.localEntry!)
....
....
}
}
With the latest code shared, i would suggest to keep NetworkManager instance at the class level instead of a function level scope as this will ensure the networkManager instance is not deallocated.
class NetworkViewController: UIViewController, NetWorkManagerDelegate {
var localEntry: LocalEntry?
var progressBackground = UIView()
var progressBar = UIProgressView()
let networkManager = NetworkManager()
func uploadProgressWith(progress: Float) {
progressBar.progress = progress
view.layoutSubviews()
}
deinit {
print("deallocate")
}
override func viewDidLoad() {
super.viewDidLoad()
networkManager.delegate = self
networkManager.uploadFiles(item: self.localEntry!)
}
...
Also, you need to be careful for retain-cycles that cause memory leaks. To avoid retain cycles, you need to declare your delegate variable as weak.
As user #Kamran pointed out, I was creating a class level instance of networkManager inside UploadMainFileOperation. The issue has been fixed by changed that variable to an Optional and giving it an instance of NetworkManager, as self ,that was queueing up the operations. The code blocks as been updated with comments of the correct code along with the incorrect code.
If you set a delegate and later it becomes nil, this means your delegate has been deallocated.
I would recommend to create an (empty) deinit in your delegate class and set a breakpoint for the debugger in that method. This will help you find out where you're losing the reference to said delegate.
You can probably avoid this by assigning your delegate to a property of one of your classes or make it a strong reference in one of your completion blocks.

One delegate Two classes

I am using a UISearchController with GoogleMap Autocomplete View controller to send data to my view controllers.
I have a Tabbar controller and I want to send the data two my ViewController A and B.
I have done all the necessary jobs but only one ViewController gets notified when user has used the UISearchController.
I have tried setting the delegate of each tab to nil when I move to the other tab, For example if I move from ViewController A to B
I will set the delegate of A to nil and then set the delegate of B to itself.
I am kinda new to swift so Can anyone help me understand why isn't this working?
I have tried debugging my code to see is my delegate is nil and it wasn't .
Here is how i set and unset the delegate
func setDelegate() {
print("MapViewController is not nil")
print(resultsViewController?.delegate)
resultsViewController?.delegate = self
print(resultsViewController?.delegate)
}
func unSetDelegate() {
print("MapViewController is nil")
resultsViewController?.delegate = nil
}
You need an observer pattern, if you need that one class instance notify to a number of other instances you need make an array of delegates (called observers) and register and deregister from that notifier instance class
Further info Wikipedia Observer Pattern
example code
This is the protocol that must implement any observer class
protocol GeotificationsManagerObserver : NSObjectProtocol{
func nearestGeotificationsHasChanged(pgeotifications:[Geotification])
}
Notifier class
class GeotificationsManager: NSObject {
/**...*//code
fileprivate var observers : [GeotificationsManagerObserver] = []
/**...*//code
}
Observers methods
extension GeotificationsManager
{
func addGeotificationsManagerObserver(observer:GeotificationsManagerObserver)
{
for currentObs in self.observers {
if(observer.isEqual(currentObs))
{
//we don't want add again
return
}
}
self.observers.append(observer)
}
func removeGeotificationsManagerObserver(observer:GeotificationsManagerObserver)
{
var observerIndex = -1
for (index,currObserver) in self.observers.enumerated() {
if(observer.isEqual(currObserver))
{
observerIndex = index
break
}
}
if(observerIndex != -1)
{
self.observers.remove(at: observerIndex)
}
}
//here make the notification to all observers in observers array
func nearestsGeotificationsHasChanged()
{
for currObserver in self.observers {
currObserver.nearestGeotificationsHasChanged(pgeotifications: self.getNearesGeotifications())
}
}
}
Important
You must remove the observer once you don't need being notified if not you will have memory issue
Example: You can add a UIViewController as Observer in viewDidAppear and can be removed in viewDidDisappear

ARC in Swift how to resolve strong reference cycle when assign property as function

Flowing code, I did try to create an object assign object property to a functions. And after init object I did try assign it to nil. But object did not release (because deinit never called).
I think problem is strong reference cycle between property and owner object. If really has strong reference cycle here, how to resolve this problem when assign property directly with a function?
class MyClass {
var aProperty: (() -> ())?
init() {
// problem when assign property as a method
self.aProperty = aMethod
}
func aMethod() {
print("method!!!")
}
deinit {
print("MyClass is being deinitialized")
}
}
var instance: MyClass? = MyClass()
instance?.aProperty?()
instance = nil
You resolve a strong reference cycle between a closure and a class instance by defining a capture list as part of the closure’s definition. A capture list defines the rules to use when capturing one or more reference types within the closure’s body. As with strong reference cycles between two class instances, you declare each captured reference to be a weak or unowned reference rather than a strong reference. The appropriate choice of weak or unowned depends on the relationships between the different parts of your code.
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// closure body goes here
}
from
Strong Reference Cycles for Closures
In your case you should apply capture list when assigning a method to the property like this
init() {
self.aProperty = { [unowned self] in self.aMethod() }
}
You can still use a capture list to prevent the cycle. Just wrap the method call in a closure as shown in the code below.
class MyClass {
var aProperty: (() -> ())?
init() {
// use a capture list to prevent a reference cycle
self.aProperty = { [unowned self] in self.aMethod() }
}
func aMethod() {
print("method!!!")
}
deinit {
print("MyClass is being deinitialized")
}
}
var instance: MyClass? = MyClass()
instance?.aProperty?()
instance = nil
This eliminates the strong reference cycle in my testing.

Execute a method when a variable value changes in Swift

I need to execute a function when a variable value changes.
I have a singleton class containing a shared variable called labelChange. Values of this variable are taken from another class called Model. I have two VC classes, one of them has a button and a label and the second only a button.
When the button in the first VC class is pressed I am updating the label with this func:
func updateLabel(){
self.label.text = SharingManager.sharedInstance.labelChange
}
But I want to call the same method whenever the value of the labelChange is changed. So in button click I will only update the labelChange value and when this thing happen I want to update the label with the new value of the labelChange. Also in the second VC I am able to update the labelChange value but I am not able to update the label when this value is changed.
Maybe properties are the solution but can anyone show me how to do so.
Edited second time:
Singleton Class:
class SharingManager {
func updateLabel() {
println(labelChange)
ViewController().label.text = SharingManager.sharedInstance.labelChange
}
var labelChange: String = Model().callElements() {
willSet {
updateLabel()
}
}
static let sharedInstance = SharingManager()
}
First VC:
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBAction func Button(sender: UIButton) {
SViewController().updateMessageAndDismiss()
}
}
Second VC:
func updateMessageAndDismiss() {
SharingManager.sharedInstance.labelChange = modelFromS.callElements()
self.dismissViewControllerAnimated(true, completion: nil)
}
#IBAction func b2(sender: UIButton) {
updateMessageAndDismiss()
}
I made some improvements but I need to reference a label from the first VC class in singleton. Therefore I will update that label of VC in singleton.
When I print the value of labelChange the value is being updated and everything is fine. But when I try to update that value on label from singleton I receive an error:
unexpectedly found nil while unwrapping an Optional value
and the error is pointing in 4th line of singleton class.
You can simply use a property observer for the variable, labelChange, and call the function that you want to call inside didSet (or willSet if you want to call it before it has been set):
class SharingManager {
var labelChange: String = Model().callElements() {
didSet {
updateLabel()
}
}
static let sharedInstance = SharingManager()
}
This is explained in Property Observers.
I'm not sure why this didn't work when you tried it, but if you are having trouble because the function you are trying to call (updateLabel) is in a different class, you could add a variable in the SharingManager class to store the function to call when didSet has been called, which you would set to updateLabel in this case.
Edited:
So if you want to edit a label from the ViewController, you would want to have that updateLabel() function in the ViewController class to update the label, but store that function in the singleton class so it can know which function to call:
class SharingManager {
static let sharedInstance = SharingManager()
var updateLabel: (() -> Void)?
var labelChange: String = Model().callElements() {
didSet {
updateLabel?()
}
}
}
and then set it in whichever class that you have the function that you want to be called, like (assuming updateLabel is the function that you want to call):
SharingManager.sharedInstance.updateLabel = updateLabel
Of course, you will want to make sure that the view controller that is responsible for that function still exists, so the singleton class can call the function.
If you need to call different functions depending on which view controller is visible, you might want to consider Key-Value Observing to get notifications whenever the value for certain variables change.
Also, you never want to initialize a view controller like that and then immediately set the IBOutlets of the view controller, since IBOutlets don't get initialized until the its view actually get loaded. You need to use an existing view controller object in some way.
Hope this helps.
In Swift 4 you can use Key-Value Observation.
label.observe(\.text, changeHandler: { (label, change) in
// text has changed
})
This is basically it, but there is a little catch. "observe" returns an NSKeyValueObservation object that you need to hold! - when it is deallocated, you’ll receive no more notifications. To avoid that we can assign it to a property which will be retained.
var observer:NSKeyValueObservation?
// then assign the return value of "observe" to it
observer = label.observe(\.text, changeHandler: { (label, change) in
// text has changed,
})
You can also observe if the the value has changed or has been set for the first time
observer = label.observe(\.text, changeHandler: { (label, change) in
// just check for the old value in "change" is not Nil
if let oldValue = change.oldValue {
print("\(label.text) has changed from \(oldValue) to \(label.text)")
} else {
print("\(label.text) is now set")
}
})
For More Information please consult Apples documentation here
Apple provide these property declaration type :-
1. Computed Properties:-
In addition to stored properties, classes, structures, and enumerations can define computed properties, which do not actually store a value. Instead, they provide a getter and an optional setter to retrieve and set other properties and values indirectly.
var otherBool:Bool = false
public var enable:Bool {
get{
print("i can do editional work when setter set value ")
return self.enable
}
set(newValue){
print("i can do editional work when setter set value ")
self.otherBool = newValue
}
}
2. Read-Only Computed Properties:-
A computed property with a getter but no setter is known as a read-only computed property. A read-only computed property always returns a value, and can be accessed through dot syntax, but cannot be set to a different value.
var volume: Double {
return volume
}
3. Property Observers:-
You have the option to define either or both of these observers on a property:
willSet is called just before the value is stored.
didSet is called immediately after the new value is stored.
public var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
NOTE:- For More Information go on professional link
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
There is another way of doing so, by using RxSwift:
Add RxSwift and RxCocoa pods into your project
Modify your SharingManager:
import RxSwift
class SharingManager {
static let sharedInstance = SharingManager()
private let _labelUpdate = PublishSubject<String>()
let onUpdateLabel: Observable<String>? // any object can subscribe to text change using this observable
// call this method whenever you need to change text
func triggerLabelUpdate(newValue: String) {
_labelUpdate.onNext(newValue)
}
init() {
onUpdateLabel = _labelUpdate.shareReplay(1)
}
}
In your ViewController you can subscribe to value update in two ways:
a. subscribe to updates, and change label text manually
// add this ivar somewhere in ViewController
let disposeBag = DisposeBag()
// put this somewhere in viewDidLoad
SharingManager.sharedInstance.onUpdateLabel?
.observeOn(MainScheduler.instance) // make sure we're on main thread
.subscribeNext { [weak self] newValue in
// do whatever you need with this string here, like:
// self?.myLabel.text = newValue
}
.addDisposableTo(disposeBag) // for resource management
b. bind updates directly to UILabel
// add this ivar somewhere in ViewController
let disposeBag = DisposeBag()
// put this somewhere in viewDidLoad
SharingManager.sharedInstance.onUpdateLabel?
.distinctUntilChanged() // only if value has been changed since previous value
.observeOn(MainScheduler.instance) // do in main thread
.bindTo(myLabel.rx_text) // will setText: for that label when value changed
.addDisposableTo(disposeBag) // for resource management
And don't forget to import RxCocoa in ViewController.
For triggering event just call
SharingManager.sharedInstance.triggerLabelUpdate("whatever string here")
HERE you can find example project. Just do pod update and run workspace file.
var item = "initial value" {
didSet { //called when item changes
print("changed")
}
willSet {
print("about to change")
}
}
item = "p"
override var isHighlighted: Bool {
get { super.isHighlighted }
set {
super.isHighlighted = newValue
if newValue {
label.textColor = highlightedTextColor
contentView.backgroundColor = highlightedBackgroundColor
} else {
label.textColor = normalTextColor
contentView.backgroundColor = normalBackgroundColor
}
}
}

Resources