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.
Related
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
Using a Playground and given these definitions:
import Foundation
protocol MoneyTrakObject {
var key: String { get set }
init()
}
extension MoneyTrakObject {
static func objectFromDB<T: MoneyTrakObject>(for key: String, queue: DispatchQueue? = nil, completion: #escaping (T) -> Void) -> String? {
// after data is retrieved, call completion closure
let valueObject = T()
completion(valueObject)
return "dbToken"
}
}
protocol Transaction: MoneyTrakObject {
var amount: Int { get set }
}
struct BasicTransaction: Transaction {
var key = UUID().uuidString
var amount = 0
init() {}
}
struct RecurringTransaction: Transaction {
var key = UUID().uuidString
var amount = 0
init() {}
}
I would expect that I could do this:
let token1 = BasicTransaction.objectFromDB(for: "") { (transaction) in
// use transaction
}
let token2 = RecurringTransaction.objectFromDB(for: "") { (transaction) in
// use transaction
}
However I get the Generic parameter 'T' could not be inferred error when calling the static method and I'm not sure why.
I do not see why you need the generic constraint. If you change the extension of your protocol to this:
extension MoneyTrakObject {
static func objectFromDB(for key: String, queue: DispatchQueue? = nil, completion: #escaping (Self) -> Void) -> String? {
// after data is retrieved, call completion closure
let valueObject = Self()
completion(valueObject)
return "dbToken"
}
}
Your code compiles just fine. Self is a placeholder for the actually implementing type.
Well... the only place where T is used is inside the completion handler argument. When you write this:
let token1 = BasicTransaction.objectFromDB(for: "") { (transaction) in
// use transaction
}
The compiler has no idea what type transaction is and hence cannot specialize the generic function. Provide some type info like this:
let token1 = BasicTransaction.objectFromDB(for: "") { (transaction: Transaction) in
// use transaction
}
let token2 = BasicTransaction.objectFromDB(for: "") { (transaction: BasicTransaction) in
// use transaction
}
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")
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`
})
If I have a method like:
func someMethod(contextPtr: UnsafeMutablePointer<Void>)
how do I get the object from the contextPtr?
func someMethod(contextPtr: UnsafeMutablePointer<Void>){
let object:MyObject = contextPtr.memory
}
gives:
'Void' is not convertible to 'MyObject'
What's the secret sauce
More detail:
What I'm actually doing here is setting up a global callback function for SCNetworkReachability:
func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>) {
let r:Reachability = info.memory
}
and then adding the callback as follows:
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
var s = self
withUnsafeMutablePointer(&s) {
context.info = UnsafeMutablePointer($0)
}
SCNetworkReachabilitySetCallback(reachability, callback, &context)
This should work: pass the object pointer as an opaque unmanaged pointer
to the callback:
context.info = UnsafeMutablePointer(Unmanaged.passUnretained(myObject).toOpaque())
SCNetworkReachabilitySetCallback(reachability, callback, &context)
and retrieve in the callback via:
func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>) {
let myObject = Unmanaged<MyObject>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
}
Of course this assumes that some strong reference to the object exists
as long as the callback is installed, so that the object is not
deallocated.
Update: Note that both conversions from object pointer to void pointer
and back can be simplified if you are willing to use "unsafe" functions:
context.info = unsafeAddressOf(myObject)
// ...
myObject = unsafeBitCast(info, MyObject.self)
The generated assembly code is – as far as I can see – identical.
Update 2: See also How to cast self to UnsafeMutablePointer<Void> type in swift for more information
about the "bridging" and some helper functions which can be used here.
Swift 3 update (Xcode 8 beta 6):
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
// ...
func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
if let info = info {
let myObject = Unmanaged<MyObject>.fromOpaque(info).takeUnretainedValue()
// ...
}
}
struct S {
var i: Int = 10
}
var first = S()
func foo(contextPtr: UnsafeMutablePointer<Void>){
let pS = UnsafeMutablePointer<S>(contextPtr)
pS.memory.i = 100
}
print(first.i) // 10
foo(&first)
print(first.i) // 100
if we need pass as UnsafeMutablePointer self to async function
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
import Foundation
// can be struct, class ...
class C {
let queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)
var s: String = ""
func foo() {
var c = self
dispatch_async(queue) { () -> Void in
f(&c)
}
}
}
func f(pV: UnsafeMutablePointer<Void>) {
let pC = UnsafeMutablePointer<C>(pV)
sleep(1)
print(pC.memory.s)
}
var c1: C? = C()
c1!.s = "C1"
c1!.foo() // C1
var c2: C? = C()
c2!.s = "C2"
c2!.foo() // C2
c1 = nil
c2 = nil
print("test")