Chen Li Yong
Chen Li Yong

Reputation: 6107

How can I "blindly inject" an object variable with arbitrary value?

For example, I have a variable with type AnyObject. I do not know what class is that. But I want to test, whether if the object can accept specific attribute, and want to assign value to it.

For example, if I have:

class BaseViewController : UIViewController {
    var containerVc : UIViewController?;
}

If I know that a variable can be typecasted into BaseViewController, then, of course, I can just typecast it to BaseViewController, and then assign the variable to it.

let vc : UIViewController?;
vc = BaseViewController();
(vc as? BaseViewController)?.containerVc = self;

The problem is if the BaseViewController type itself is inaccessible or unknowable.

So what I want to do is that I just want to test if an attribute is available to be set, if the operation can't be performed, it can fail silently. So for example, the code I have in mind if this is possible:

var vc : UIViewController? = generateUnknownVc();
vc.setValue(self, forAttribute: "containerVc");

or genericaly:

var abc : AnyObject = generateRandomObject();
abc.setValue(123, forAttribute: "randomAttribute");

I ask this because I remember somewhere that you can supply value to an object the way Storyboard does (User Defined Runtime Attributes). But I don't know how that works programmatically.

CONCLUSION:

This is the code I finally ended up with, borrowed heavily from Ehsan Saddique's answer. This code has been improved to also check the ancestors (superclass).

extension NSObject {

    func safeValue(forKey key: String) -> Any? {
        var copy : Mirror? = Mirror(reflecting: self);
        while copy != nil {
            for child in copy!.children.makeIterator() {
                if let label = child.label, label == key {
                    return child.value
                }
            }
            copy = copy?.superclassMirror;
        }
        return nil
    }

    func setValueSafe(_ value: Any?, forKey key: String) {
        if safeValue(forKey: key) != nil { self.setValue(value, forKey: key); }
    }

}

And from Andreas Oetjen's answer, I need to make mental note that this only works if the object is descendant from NSObject or tagged with @objc, and the function is also tagged with @objc.

Thanks!

Upvotes: 0

Views: 114

Answers (2)

Andreas Oetjen
Andreas Oetjen

Reputation: 10209

You would use Key-Value-Coding, which is supported in swift if (and only if)

  • Your class is somehow a subclass of NSObject or tagged with @objc
  • Your properties you want to access are tagged with @objc

I currently have no Xcode available, but this sample code should work:

class A : NSObject {
    @objc var name:String = "hello"
}

var theA = A()
theA.setValue("world", forKey:"name")
print(theA.name)  // shoud print "world"

To check if an property exists (instead of just crashing), see this answer: Check if class has a value for a key

Upvotes: 1

Ehsan
Ehsan

Reputation: 668

UIViewController is inherited from NSObject. You can use Key-Value-Coding to find if the key exists. Add this extension to your code.

extension NSObject {
    func safeValue(forKey key: String) -> Any? {
        let copy = Mirror(reflecting: self)
        for child in copy.children.makeIterator() {
            if let label = child.label, label == key {
                return child.value
            }
        }
        return nil
    }
}

Now you can use if-let to check if key exists.

if let key = yourViewController.safeValue(forKey: "someKey") {
    print("key exists")
    yourViewController.setValue("someValue", forKey:"someKey")
}
else {
    print("key doesn't exist")
}

You will have to mark your properties with @objc to use KVC.

Upvotes: 1

Related Questions