What if I want to assign a property to itself?

Senseful picture Senseful · Aug 11, 2015 · Viewed 8.2k times · Source

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?

Answer

vacawama picture vacawama · Aug 11, 2015

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()