iosswiftuiviewcontrollerpropertiesdidset

What if I want to assign a property to itself?


If I attempt to run the following code:

photographer = photographer

I get the error:

Assigning a property to itself.


I want to assign the property to itself to force the photographer didSet block to run.

Here's a real-life example: In the "16. Segues and Text Fields" lecture of the Winter 2013 Stanford iOS course (13:20), the professor recommends writing code similar to the following:

@IBOutlet weak var photographerLabel: UILabel!

var photographer: Photographer? {
    didSet {
        self.title = photographer.name
        if isViewLoaded() { reload() }
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    reload()
}

func reload() {
    photographerLabel.text = photographer.name
}

Note: I made the following changes: (1) the code was switched from Objective-C to Swift; (2) because it's in Swift, I use the didSet block of the property instead of the setPhotographer: method; (3) instead of self.view.window I am using isViewLoaded because the former erroneously forces the view to load upon access of the view property; (4) the reload() method (only) updates a label for simplicity purposes, and because it resembles my code more closely; (5) the photographer IBOutlet label was added to support this simpler code; (6) since I'm using Swift, the isViewLoaded() check no longer exists simply for performance reasons, it is now required to prevent a crash, since the IBOutlet is defined as UILabel! and not UILabel? so attempting to access it before the view is loaded will crash the application; this wasn't mandatory in Objective-C since it uses the null object pattern.

The reason we call reload twice is because we don't know if the property will be set before or after the view is created. For example, the user might first set the property, then present the view controller, or they might present the view controller, and then update the property.

I like how this property is agnostic as to when the view is loaded (it's best not to make any assumptions about view loading time), so I want to use this same pattern (only slightly modified) in my own code:

@IBOutlet weak var photographerLabel: UILabel?

var photographer: Photographer? {
    didSet {
        photographerLabel?.text = photographer.name
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    photographer = photographer
}

Here instead of creating a new method to be called from two places, I just want the code in the didSet block. I want viewDidLoad to force the didSet to be called, so I assign the property to itself. Swift doesn't allow me to do that, though. How can I force the didSet to be called?


Solution

  • Prior to Swift 3.1 you could assign the property name to itself with:

    name = (name)
    

    but this now gives the same error: "assigning a property to itself".

    There are many other ways to work around this including introducing a temporary variable:

    let temp = name
    name = temp
    

    This is just too fun not to be shared. I'm sure the community can come up with many more ways to do this, the crazier the better

    class Test: NSObject {
        var name: String? {
            didSet {
                print("It was set")
            }
        }
    
        func testit() {
            // name = (name)    // No longer works with Swift 3.1 (bug SR-4464)
            // (name) = name    // No longer works with Swift 3.1
            // (name) = (name)  // No longer works with Swift 3.1
            (name = name)
            name = [name][0]
            name = [name].last!
            name = [name].first!
            name = [1:name][1]!
            name = name ?? nil
            name = nil ?? name
            name = name ?? name
            name = {name}()
            name = Optional(name)!
            name = ImplicitlyUnwrappedOptional(name)
            name = true ? name : name
            name = false ? name : name
            let temp = name; name = temp
            name = name as Any as? String
            name = (name,0).0
            name = (0,name).1
            setValue(name, forKey: "name") // requires class derive from NSObject
            name = Unmanaged.passUnretained(self).takeUnretainedValue().name
            name = unsafeBitCast(name, to: type(of: name))
            name = unsafeDowncast(self, to: type(of: self)).name
            perform(#selector(setter:name), with: name) // requires class derive from NSObject
            name = (self as Test).name
            unsafeBitCast(dlsym(dlopen("/usr/lib/libobjc.A.dylib",RTLD_NOW),"objc_msgSend"),to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
            unsafeBitCast(class_getMethodImplementation(type(of: self), #selector(setter:name)), to:(@convention(c)(Any?,Selector!,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject
            unsafeBitCast(method(for: #selector(setter:name)),to:(@convention(c)(Any?,Selector,Any?)->Void).self)(self,#selector(setter:name),name) // requires class derive from NSObject 
            _ = UnsafeMutablePointer(&name)
            _ = UnsafeMutableRawPointer(&name)
            _ = UnsafeMutableBufferPointer(start: &name, count: 1)
            withUnsafePointer(to: &name) { name = $0.pointee }
    
            //Using NSInvocation, requires class derive from NSObject
            let invocation : NSObject = unsafeBitCast(method_getImplementation(class_getClassMethod(NSClassFromString("NSInvocation"), NSSelectorFromString("invocationWithMethodSignature:"))),to:(@convention(c)(AnyClass?,Selector,Any?)->Any).self)(NSClassFromString("NSInvocation"),NSSelectorFromString("invocationWithMethodSignature:"),unsafeBitCast(method(for: NSSelectorFromString("methodSignatureForSelector:"))!,to:(@convention(c)(Any?,Selector,Selector)->Any).self)(self,NSSelectorFromString("methodSignatureForSelector:"),#selector(setter:name))) as! NSObject
            unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setSelector:")),to:(@convention(c)(Any,Selector,Selector)->Void).self)(invocation,NSSelectorFromString("setSelector:"),#selector(setter:name))
            var localVarName = name
            withUnsafePointer(to: &localVarName) { unsafeBitCast(class_getMethodImplementation(NSClassFromString("NSInvocation"), NSSelectorFromString("setArgument:atIndex:")),to:(@convention(c)(Any,Selector,OpaquePointer,NSInteger)->Void).self)(invocation,NSSelectorFromString("setArgument:atIndex:"), OpaquePointer($0),2) }
            invocation.perform(NSSelectorFromString("invokeWithTarget:"), with: self)
        }
    }
    
    let test = Test()
    test.testit()