Protocol definition as `NotificationCenter` object? - swift

I have a protocol definition:
protocol Environment {
var url: String {get}
}
I give it to the NotificationCenter as an object:
NotificationCenter.default.post(name: NotificationForEnvironmentChange, object: Environment)
But it does not show up in the object property of the function I assign to the selector for the NotificationForEnvironmentChange.
func environmentChange(sender: Notification) {
guard let newEnvironment = sender.object as? Environment else {
return
}
//Never gets the environment
}
Why?
Note: NotificationForEnvironmentChange is a Notification property I define like so:
let NotificationForEnvironmentChange = Notification.Name("NotificationForEnvironmentChange")

Related

Keeping a value updated throughout many ViewController's in iOS using Swift

In my ViewController (VC1) I have the following variable:
var orderInfo: Order!
Order is a struct itself like the following:
struct Order {
var orderId: String
var orderReferenceNumber: String
//more variables...
init(
orderId: String,
orderReferenceNumber: String,
){
self.orderId = orderId
self.orderReferenceNumber = orderReferenceNumber
}
init(data: [String: Any]){
orderId = data[DatabaseRef.orderId] as? String ?? ""
orderReferenceNumber = data[DatabaseRef.orderReferenceNumber] as? String ?? ""
}
static func modelToData(order: Order) -> [String: Any] {
let data : [String: Any] = [
DatabaseRef.orderId: order.orderId,
DatabaseRef.orderReferenceNumber: order.orderReferenceNumber,
]
return data
}
}
In VC1, I have a listener that updates its info from Firestore Database (throught addsnapshotslistener). When the variable orderInfo gets updated in VC1 because of a change in the order in the database the listener will update the orderInfo variable in VC1. While the user is in another ViewController (e.g. VC2), I would like to access orderInfo variable with its updated info from VC1. How can I make this happen?
There are numerous of ways of doing that. Lets mention some of them.
You can use NotificationCenter
Example
class VC1: UIViewController {
var orderInfo: Order!
func updateOrder() {
var orderDict = ["orderInfo":orderInfo]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "orderInfoUpdated"), object: nil, userInfo: orderDict)
}
}
// The receiving end
class VC2: UIViewController {
var orderInfo: Order!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(doSomething(_: )), name: NSNotification.Name(rawValue: "orderInfoUpdated"), object: nil)
}
func doSomething(_ notification: NSNotification) {
if let orderInfo = notification.userInfo?["orderInfo"] as? Order {
// Do something
}
}
}
You can create an "app state" service where you use the singleton
pattern making the user info accessible in all the views
Example:
class AppState {
static let shared = AppState()
var orderInfo: Order!
...
}
On your ViewController
class ViewController: UIViewController {
func doSomeWork() {
print(AppState.shared.orderInfo)
}
}
You can make it global (Not Recommended)

Is it possible to add an observer on struct variable in Swift?

I need to track the update in a variable of struct type.
Is it possible to add an observer on struct variable in Swift?
Example:
struct MyCustomStruct {
var error:Error?
var someVar:String?
}
class MyClass{
var myCustomStruct:MyCustomStruct?
}
I want to add an observer on myCustomStruct variable.
The standard Swift “property observers” (didSet and willSet) are designed to let a type observe changes to its own properties, but not for letting external objects add their own observers. And KVO, which does support external observers, is only for dynamic and #objc properties NSObject subclasses (as outlined in Using Key-Value Observing in Swift).
So, if you want to have an external object observe changes within a struct, as others have pointed out, you have to create your own observer mechanism using Swift didSet and the like. But rather than implementing that yourself, property by property, you can write a generic type to do this for you. E.g.,
struct Observable<T> {
typealias Observer = String
private var handlers: [Observer: (T) -> Void] = [:]
var value: T {
didSet {
handlers.forEach { $0.value(value) }
}
}
init(_ value: T) {
self.value = value
}
#discardableResult
mutating func observeNext(_ handler: #escaping (T) -> Void) -> Observer {
let key = UUID().uuidString as Observer
handlers[key] = handler
return key
}
mutating func remove(_ key: Observer) {
handlers.removeValue(forKey: key)
}
}
Then you can do things like:
struct Foo {
var i: Observable<Int>
var text: Observable<String>
init(i: Int, text: String) {
self.i = Observable(i)
self.text = Observable(text)
}
}
class MyClass {
var foo: Foo
init() {
foo = Foo(i: 0, text: "foo")
}
}
let object = MyClass()
object.foo.i.observeNext { [weak self] value in // the weak reference is really only needed if you reference self, but if you do, make sure to make it weak to avoid strong reference cycle
print("new value", value)
}
And then, when you update the property, for example like below, your observer handler closure will be called:
object.foo.i.value = 42
It’s worth noting that frameworks like Bond or RxSwift offer this sort of functionality, plus a lot more.
With variables you can use two default observers
willSet - represents moment before variable will be set with new value
didSet - represents moment when variable was set
Also in observer you can work with two values. With current variable in current state, and with constant depending on observer
struct Struct {
var variable: String {
willSet {
variable // before set
newValue // after set, immutable
}
didSet {
oldValue // before set, immutable
variable // after set
}
}
}
And the same you can do for any other stored property, so you can use it for struct variable in your class too
class Class {
var myStruct: Struct? {
didSet {
...
}
}
}
Also you can for example in did set observer of variable post notification with certain name
didSet {
NotificationCenter.default.post(name: Notification.Name("VariableSet"), object: nil)
}
and then you can add certain class as observer for notification with this name
class Class {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(variableSet), name: Notification.Name("VariableSet"), object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self, name: Notification.Name("VariableSet"), object: nil)
}
#objc func variableSet() {
...
}
}
Try this, first create a struct with an action variable and when you create an object of the struct set the action parameter on the action you want. ex.
struct testStruct {
var action: (()->())?
var variable: String? {
didSet {
self.action?()
}
}
}
And inside your main code - main class
var testS = testStruct()
testS.action = {
print("Hello")
}
testS.variable = "Hi"
When you set the testS.variabe = "Hi" it will call the print("Hello")
struct MyCustomStruct {
var error:Error?
var someVar:String?
}
class MyClass{
var myCustomStruct:MyCustomStruct? {
didSet{
print("my coustomeSruct changed")
}
}
}
let aClass = MyClass()
aClass.myCustomStruct?.someVar = " test"
//prints:my coustomeSruct changed

Different process between Struct and Class in mutating asynchronously in Swift3

In struct type, mutating self in async process makes error as below.
closure cannot implicitly captured a mutating self
If I change the struct to class type, the error disappear.
What is difference between struct and class when mutate self in asynchronously?
struct Media {
static let loadedDataNoti = "loadedDataNotification"
let imagePath: String
let originalPath: String
let description: String
var imageData: Data?
let tag: String
var likeCount: Int?
var commentCount: Int?
var username: String?
var delegate: MediaDelegate?
public init(imagePath: String, originalPath: String, description: String, tag: String, imageData: Data? = nil) {
self.imagePath = imagePath
self.originalPath = originalPath
self.description = description
self.tag = tag
if imageData != nil {
self.imageData = imageData
} else {
loadImageData()
}
}
mutating func loadImageData() {
if let url = URL(string: imagePath) {
Data.getDataFromUrl(url: url, completion: { (data, response, error) in
if (error != nil) {
print(error.debugDescription)
return
}
if data != nil {
self.imageData = data! // Error: closure cannot implicitly captured a mutating self
NotificationCenter.default.post(name: NSNotification.Name(rawValue: Media.loadedDataNoti), object: data)
}
})
}
}
A struct is a value type. How does struct mutating work? It works by making a completely new struct and substituting it for the original. Even in a simple case like this:
struct S {
var name = "matt"
}
var s = S()
s.name = "me"
... you are actually replacing one S instance by another — that is exactly why s must be declared as var in order to do this.
Thus, when you capture a struct's self into an asynchronously executed closure and ask to mutate it, you are threatening to appear at some future time and suddenly rip away the existing struct and replace it by another one in the middle of executing this very code. That is an incoherent concept and the compiler rightly stops you. It is incoherent especially because how do you even know that this same self will even exist at that time? An intervening mutation may have destroyed and replaced it.
Thus, this is legal:
struct S {
var name = "matt"
mutating func change() {self.name = "me"}
}
But this is not:
func delay(_ delay:Double, closure:#escaping ()->()) {
let when = DispatchTime.now() + delay
DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
struct S {
var name = "matt"
mutating func change() {delay(1) {self.name = "me"}} // error
}
When you mutate an instance of a value type -- such as a struct -- you're conceptually replacing it with a new instance of the same type, i.e. doing this:
myMedia.mutatingFuncToLoadImageData()
...can be thought of as doing something like this:
myMedia = Media(withLoadedData: theDownloadedData)
...except you don't see the assignment in code.
You're effectively replacing the instance that you call a mutating function on. In this case myMedia. As you may realize, the mutation has to have finished at the end of the mutating function for this to work, or your instance would keep changing after calling the mutating function.
You're handing off a reference to self to an asynchronous function that will try to mutate your instance after your mutating function has ended.
You could compile your code by doing something like
var myself = self // making a copy of self
let closure = {
myself.myThing = "thing"
}
but that would only change the value of the variable myself, and not affect anything outside of your function.

Create member variable with closure in its constructor

I'm having the following code in my class:
// MARK: - Lifecycle
init() {
authenticationContext = AuthenticationContext()
synchronizationContext = SynchronizationContext()
employeesCoordinator = EmployeesCoordinator()
serverErrorObserver =
NotificationObserver(notification: serverErrorNotification,
block: handleServerError) // <- Error
}
// MARK: - Listeners
private let serverErrorObserver: NotificationObserver!
private lazy var handleServerError: NSError -> () = {
[unowned self] (error) in
// Currently means that the token is expired, so remove stored instance
self.handleAuthorizationDidExpired()
}
It looks legit, but I'm getting the following complier error:
Use of 'self' in property access 'handleServerError' before all stored
properties are initialized
If it would help, this is the source behind NotificationObserver:
class ValueWrapper<T> {
let value: T
init(_ value: T) { self.value = value }
}
// Notification
struct Notification<A> {
let name: String
}
// Global Functions
func publish<A>(note: Notification<A>, value: A) {
let userInfo = ["value": ValueWrapper(value)]
NSNotificationCenter.defaultCenter().postNotificationName(note.name, object: nil, userInfo: userInfo)
}
//
class NotificationObserver {
let observer: NSObjectProtocol
init<A>(notification: Notification<A>, block aBlock: A -> ()) {
observer = NSNotificationCenter.defaultCenter().addObserverForName(notification.name, object: nil, queue: nil) { note in
let wrapper = note.userInfo?["value"] as? ValueWrapper<A>
if let value = wrapper?.value {
aBlock(value)
} else {
assert(false, "Couldn't understand user info")
}
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(observer)
}
}
// Global variables
let serverErrorNotification: Notification<NSError> = Notification(name: "ServerErrorNotification")
let synchronizationDidCompleteNotification: Notification<Int> = Notification(name: "SynchronizationDidCompleteNotification")
let authorizationDidCompleteNotification: Notification<Authorization> = Notification(name: "SynchronizationDidCompleteNotification")
You cannot call self until you have properly initialized the object using super.init()
if you have un-initialized let variables they should initialize before super.init() call.
so doing so you cannot call to self - closure calls to self
so you have to change let to var, then call super.init() before assigning closure
private let serverErrorObserver: NotificationObserver!
to
private var serverErrorObserver: NotificationObserver!
eg.
init() {
super.init()
authenticationContext = AuthenticationContext()
synchronizationContext = SynchronizationContext()
employeesCoordinator = EmployeesCoordinator()
serverErrorObserver =
NotificationObserver(notification: serverErrorNotification,
block: handleServerError) // <- Error
}
The problem is that you are accessing self in the init() in the following line:
self.handleAuthorizationDidExpired()
You can't do it until all of the stored properties are initialised. And the only property, which is not initialised yet in your case, is serverErrorObserver.
In order to fix it easily, you can mark this property in the following way:
private(set) var serverErrorObserver: NotificationObserver?
By marking it optional you tell the compiler that this property doesn't need to be initialised when the object is created.

NSNotification subclass with Generics in Swift 2.1

Can't make subclass of NSNotification with Generic payload object. Getting either runtime error or compile error (see comments in code below). Is it even possible with Swift 2.1? Any ideas appreciated. Thanks!
Runtime error because NSNotification is abstract class (class cluster).
Compile error because designated initializer should be used.
public class Notification<T: Any>: NSNotification {
private var _name: String
private var _object: AnyObject?
private var _payload: T?
public override var name: String {
return _name
}
public override var object: AnyObject? {
return _object
}
public var payload: T? {
return _payload
}
/// Always nil. Use payload
public override var userInfo: [NSObject : AnyObject]? {
return nil
}
/// Change to false to "swap" implementation
#if true
init(name: String, object: AnyObject? = nil, payload: T? = nil) {
_name = name
_object = object
_payload = payload
/*
Runtime error:
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'*** initialization method -initWithName:object:userInfo:
cannot be sent to an abstract object of class
_TtGC14__lldb_expr_1612NotificationSS_:
Create a concrete instance!'
*/
super.init(name: name, object: object, userInfo: nil)
}
#else
convenience init(name: String, object: AnyObject? = nil, payload: T? = nil) {
self.init()
_name = name
_object = object
_payload = payload
}
init() {
/// compiler error:
/// must call a designated initializer of the superclass
/// But using designated initializer cause runtime error listed above.
super.init()
}
#endif
}
let n = Notification<String>(name: "xyz", payload: "Hello")
From the docs, emphasis mine:
You can subclass NSNotification to contain information in addition to the notification name, object, and dictionary. This extra data must be agreed upon between notifiers and observers.
NSNotification is a class cluster with no instance variables. As such, you must subclass NSNotification and override the primitive methods name, object, and userInfo. You can choose any designated initializer you like, but be sure that your initializer does not call [super init]. NSNotification is not meant to be instantiated directly, and its init method raises an exception.
There's no way subclass NSNotification from Swift code right now, as Swift has no concept of "uninitializable classes" and requires that all subclasses invoke their superclass's init (which, in this case, is the wrong thing to do).
You'll have to write the subclass in Objective-C and bridge it into your Swift code.
Unfortunately, even though you can declare your Objective-C class generic, that information is lost in the bridging process. From the docs:
Aside from these Foundation collection classes, Objective-C lightweight generics are ignored by Swift. Any other types using lightweight generics are imported into Swift as if they were unparameterized.
:(
I solved original problem without subclassing.
File GenericNotification.swift:
private let genericNotificationPayloadKey = "com.mc.notification-payload"
class GenericNotification<T: Any> {
let payload: T
let name: NSNotification.Name
let object: Any?
init(name: NSNotification.Name, object: Any? = nil, payload: T) {
self.name = name
self.object = object
self.payload = payload
}
init?(notification: Notification) {
guard let payload = notification.userInfo?[genericNotificationPayloadKey] as? T else {
return nil
}
self.payload = payload
name = notification.name
object = notification.object
}
}
extension GenericNotification {
var notification: Notification {
return Notification(name: name, object: object, userInfo: [genericNotificationPayloadKey: payload])
}
static func observe(name: NSNotification.Name,
object: Any? = nil,
queue: OperationQueue = .main,
handler: #escaping (T) -> Void) -> NotificationObserver {
return NotificationObserver(name: name, object: object, queue: queue) {
if let notification = GenericNotification(notification: $0) {
handler(notification.payload)
}
}
}
func post(center: NotificationCenter = NotificationCenter.default) {
center.post(notification)
}
}
File NotificationObserver.swift:
class NotificationObserver: NSObject {
typealias Handler = ((Foundation.Notification) -> Void)
private var notificationObserver: NSObjectProtocol!
var notificationHandler: Handler?
private let notificationName: NSNotification.Name
private let notificationObject: Any?
init(name: NSNotification.Name, object: Any? = nil, queue: OperationQueue = .main, handler: Handler? = nil) {
notificationName = name
notificationObject = object
notificationHandler = handler
super.init()
notificationObserver = NotificationCenter.default.addObserver(forName: name, object: object, queue: queue) { [weak self] in
self?.handleNotification($0)
}
}
deinit {
NotificationCenter.default.removeObserver(notificationObserver, name: notificationName, object: notificationObject)
}
/// Calls block which was passed as *usingBlock* parameter.
/// Child classes may override to change default behaviour.
/// - parameter notification: Notification to handle.
func handleNotification(_ notification: Foundation.Notification) {
notificationHandler?(notification)
}
}
Usage:
// Send notification
let action = MyType.doSomething
GenericNotification(name: .myNotificationName, payload: action).post()
// Receive notification
private var notificationObservers: [NotificationObserver] = []
...
notificationObservers.append(GenericNotification<MyType>.observe(name: .myNotificationName) { instanceOfMyType in
// Got `instanceOfMyType` which is `MyType.doSomething`
})