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

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!

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.

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

Related

How to successfully compile NSClassFromString in Swift

Code below can't compile successfully.I also attached a screenshot for reference!
func getXibViewWithClassNameString(classNameString:String)->AnyObject?{
let projectName = Bundle.main.infoDictionary!["CFBundleExecutable"] as? String
let MyClass:AnyClass = NSClassFromString(projectName! + "." + classNameString)!
var viewArray:NSArray?
var xibView:AnyClass?
Bundle.main.loadNibNamed(classNameString, owner: nil, topLevelObjects: &viewArray)
for viewInArray in viewArray ?? [] {
if (viewInArray is MyClass){
xibView = viewInArray as MyClass
}
}
return xibView
}
screenshot for reference
The key mistake here is that you can't as or is check on a dynamic type. You need to have a static type, known at compile time, to use that. As pointum notes, you need to use isKind(of:) or isMember(of:) on the underlying NSObject instead. Here's how I would recommend doing that:
func firstXibViewOfClass(named classNameString: String) -> NSView? {
// Create an AnyClass object
guard let projectName = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String,
let myClass = NSClassFromString(projectName + "." + classNameString)
else {
return nil
}
// Load the nib
var topLevelObjects: NSArray?
Bundle.main.loadNibNamed(classNameString, owner: nil, topLevelObjects: &topLevelObjects)
// Convert it to NSObjects (since they all are going to be)
guard let nsObjectArray = topLevelObjects as? [NSObject] else { return nil }
// Find the first matching view and return it as an NSView if possible
return nsObjectArray.first(where: {
$0.isKind(of: myClass) // Or isMember(of:) if you want to be strict
}) as? NSView
}
If you don't need to include subclasses, though, you might want to just check the classname directly. This gets rid of all the AnyClass stuff:
func firstXibViewOfClass(named classNameString: String) -> NSView? {
// Load the nib
var topLevelObjects: NSArray?
Bundle.main.loadNibNamed(classNameString, owner: nil, topLevelObjects: &topLevelObjects)
// Convert it to NSObjects (since they all are going to be)
guard let nsObjectArray = topLevelObjects as? [NSObject] else { return nil }
// Find the first matching view and return it as an NSView if possible
return nsObjectArray.first(where: { $0.className == classNameString }) as? NSView
}
You could of course just return AnyObject?, but the name of the method suggests that you expect it to be a view, so you should enforce that or rename the method.
You can test with
if viewInArray.isKind(of: MyClass) { ... }
or
if viewInArray.isMember(of: MyClass) { ... } // strict class match
But I don’t think you can cast to a class that is not available at compile time to Swift. You won’t be able to directly call any of its methods anyway. You can cast it to some NSObject subclass and send arbitrary (not defined at compile time) messages to it, though.

Change superclass property type on swift

In a super Class called TableViewCell I have a property
class TableViewCell {
var model: AnyObject?
}
In a class called CountryTableViewCell I wrote this code
class CountryTableViewCell : TableViewCell {
var model:[AnyObject]? {
didSet {
// do some stuff
}
}
}
and I got this error
property model with [AnyObject]? cannot override a property with type
Anyobject?
Is it not possible to change the property model to an array?
No, you cannot use like that. Also AnyObject should be replaced by Any if using Swift 3.
You can change your code as below:
class TableViewCell {
var model: Any?
}
class CountryTableViewCell : TableViewCell {
override var model: Any? {
didSet {
}
}
}
Now if you want to get an array of the model in didSet then you can type cast it as below code.
class CountryTableViewCell : TableViewCell {
override var model: Any? {
didSet {
if let arrModel = model as? [Any] {
// Do Stuff...
}
}
}
}
No the property's type cannot be changed. If this is allowed, it would violate type safety by:
let subcell: CountryTableViewCell = CountryTableViewCell()
let supercell: TableViewCell = subcell
supercell.model = "anything that is not an array" as NSString
let wrong = subcell.model // not an array!
#ParthAdroja's answer showed a runtime workaround for this. You won't get an array at compile type, but at least you can ensure you have an array at runtime.
In principle if the property is read-only this specialization in subclass should work (it is same as restricting the return type of a function), but it doesn't work as of Swift 3. Anyone care about this can file an SE request to the Swift team ☺.
(Additionally, Apple do have some magic to make it work with bridged Objective-C classes, but we don't know what it is yet.)

Swift Dictionary - Update value in setter function

My code looks a little like:
Protocol
protocol MyProtocol: class {
var featureConfigs: [String: Any]? { get set }
}
Base Class
class MyBaseClass: MyProtocol {
var featureConfigs: [String : Any]? {
get {
return nil
}
set {
self.featureConfigs = newValue
}
}
}
Subclass
class MySubclass: MyBaseClass {
override var featureConfigs: [String : Any]? {
get {
return ["feature-specific-configuration":""]
}
set {
self.featureConfigs = newValue
}
}
}
I want to be able to update the values stored in featuresConfig. I understand that doing self.featureConfigs = newValue causes an infinite loop but I am unsure of how to update the featuresConfig dictionary correctly. I've read about and tried some things with Subscript but I couldn't get that to work either. Any suggestions?
In order to be able to set a persistent value for your featureConfigs, it needs to be a stored property, rather than a calculated one (this could also be a private backing variable that you create for this task, as #AMomchilov says). You can then use willSet or didSet to observe changes.
In your subclass, you can override the stored property declaration with a calculated one in order to allow you to update a given value-key pair in the dictionary when it's accessed, using super in order to refer to the superclass's stored property.
For example:
protocol MyProtocol: class {
var featureConfigs: [String: Any]? { get set }
}
class MyBaseClass: MyProtocol {
var featureConfigs: [String : Any]? { // stored property with setter observers
willSet {
print("about to be set")
}
didSet {
print("was set")
}
}
}
class MySubclass: MyBaseClass {
// calculated property that forwards to super's stored property
override var featureConfigs: [String : Any]? {
get {
// injects the updated value of a given key when accessing
guard var config = super.featureConfigs else {return nil}
config["feature-specific-configuration"] = "baz"
return config
}
set {
super.featureConfigs = newValue
}
}
}
let s = MySubclass()
s.featureConfigs = ["foo":"bar"]
print(s.featureConfigs) // Optional(["feature-specific-configuration": "baz", "foo": "bar"])
What you wrote is a computed property. Unlike stored properties, it's not backed by a persistent instance variable.
You can manually create a private instance variable to back the storage of this psuedo-computed-property. You can then give this variable a default value specific to the subclass it belongs to.

Mutating property by its name

With the help of Reflection API I'm getting the properties list for my types.
func inspectedProperties(ignored: [String] = []) -> [Property] {
var properties = [String]()
for child in self.children() {
guard let label = child.label else {
continue
}
properties += [label]
}
return properties.filter { !ignored.contains($0) }
}
This function returns me the names for all properties.
Now I want to mutate a certain property just by knowing its name.
class Fruit {
private dynamic var name = "Apple"
}
If I call Fruit().inspectedProperties() I'll get the following array ["name"].
But is it possible to mutate the variable named "name"?
OK, I found a very simple solution but it is not flexible. Actually, you can use KVO to mutate your data types. For this purpose your models should be subclasses of NSObject to enable KVO features and variables marked as dynamic.
P.S.
typealias Property = String
class Fruit: NSObject {
private dynamic var name = "Apple"
}
Then there is the mutating function.
func mutateProperty<T>(property: Property) -> T -> () {
return { value in
let filtered = self.children().filter { label, value in
if let label = label where label == property {
return true
}
return false
}
guard let child = filtered.first else {
return
}
if let object = self as? NSObject where child.value is T {
object.setValue(value as? AnyObject, forKey: property)
}
}
}
Then try out:
let fruit = Fruit()
fruit.mutateProperty("name")("Google")
print(fruit.name) // "Google"
It works, but if you want to work with value types rather than with reference ones it won't work. There might be some low level solution but I'm not familiar with one. If anyone knows how to, please leave your answer here! :)

How to construct convenience init? in Swift [duplicate]

With the following code I try to define a simple model class and it's failable initializer, which takes a (json-) dictionary as parameter. The initializer should return nil if the user name is not defined in the original json.
1.
Why doesn't the code compile? The error message says:
All stored properties of a class instance must be initialized before returning nil from an initializer.
That doesn't make sense. Why should I initialize those properties when I plan to return nil?
2.
Is my approach the right one or would there be other ideas or common patterns to achieve my goal?
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
if let value: String = dictionary["user_name"] as? String {
userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
}
}
That doesn't make sense. Why should I initialize those properties when
I plan to return nil?
According to Chris Lattner this is a bug. Here is what he says:
This is an implementation limitation in the swift 1.1 compiler,
documented in the release notes. The compiler is currently unable to
destroy partially initialized classes in all cases, so it disallows
formation of a situation where it would have to. We consider this a
bug to be fixed in future releases, not a feature.
Source
EDIT:
So swift is now open source and according to this changelog it is fixed now in snapshots of swift 2.2
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
Update: From the Swift 2.2 Change Log (released March 21, 2016):
Designated class initializers declared as failable or throwing may now return nil or throw an error, respectively, before the object has been fully initialized.
For Swift 2.1 and earlier:
According to Apple's documentation (and your compiler error), a class must initialize all its stored properties before returning nil from a failable initializer:
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Note: It actually works fine for structures and enumerations, just not classes.
The suggested way to handle stored properties that can't be initialized before the initializer fails is to declare them as implicitly unwrapped optionals.
Example from the docs:
class Product {
let name: String!
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
In the example above, the name property of the Product class is
defined as having an implicitly unwrapped optional string type
(String!). Because it is of an optional type, this means that the name
property has a default value of nil before it is assigned a specific
value during initialization. This default value of nil in turn means
that all of the properties introduced by the Product class have a
valid initial value. As a result, the failable initializer for Product
can trigger an initialization failure at the start of the initializer
if it is passed an empty string, before assigning a specific value to
the name property within the initializer.
In your case, however, simply defining userName as a String! does not fix the compile error because you still need to worry about initializing the properties on your base class, NSObject. Luckily, with userName defined as a String!, you can actually call super.init() before you return nil which will init your NSObject base class and fix the compile error.
class User: NSObject {
let userName: String!
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: NSDictionary) {
super.init()
if let value = dictionary["user_name"] as? String {
self.userName = value
}
else {
return nil
}
if let value: Bool = dictionary["super_user"] as? Bool {
self.isSuperUser = value
}
self.someDetails = dictionary["some_details"] as? Array
}
}
I accept that Mike S's answer is Apple's recommendation, but I don't think it's best practice. The whole point of a strong type system is to move runtime errors to compile time. This "solution" defeats that purpose. IMHO, better would be to go ahead and initialize the username to "" and then check it after the super.init(). If blank userNames are allowed, then set a flag.
class User: NSObject {
let userName: String = ""
let isSuperUser: Bool = false
let someDetails: [String]?
init?(dictionary: [String: AnyObject]) {
if let user_name = dictionary["user_name"] as? String {
userName = user_name
}
if let value: Bool = dictionary["super_user"] as? Bool {
isSuperUser = value
}
someDetails = dictionary["some_details"] as? Array
super.init()
if userName.isEmpty {
return nil
}
}
}
Another way to circumvent the limitation is to work with a class-functions to do the initialisation.
You might even want to move that function to an extension:
class User: NSObject {
let username: String
let isSuperUser: Bool
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
super.init()
}
}
extension User {
class func fromDictionary(dictionary: NSDictionary) -> User? {
if let username: String = dictionary["user_name"] as? String {
let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
let someDetails = dictionary["some_details"] as? [String]
return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
}
return nil
}
}
Using it would become:
if let user = User.fromDictionary(someDict) {
// Party hard
}
Although Swift 2.2 has been released and you no longer have to fully initialize the object before failing the initializer, you need to hold your horses until https://bugs.swift.org/browse/SR-704 is fixed.
I found out this can be done in Swift 1.2
There are some conditions:
Required properties should be declared as implicitly unwrapped optionals
Assign a value to your required properties exactly once. This value may be nil.
Then call super.init() if your class is inheriting from another class.
After all your required properties have been assigned a value, check if their value is as expected. If not, return nil.
Example:
class ClassName: NSObject {
let property: String!
init?(propertyValue: String?) {
self.property = propertyValue
super.init()
if self.property == nil {
return nil
}
}
}
A failable initializer for a value type (that is, a structure or
enumeration) can trigger an initialization failure at any point within
its initializer implementation
For classes, however, a failable initializer can trigger an
initialization failure only after all stored properties introduced by
that class have been set to an initial value and any initializer
delegation has taken place.
Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/sg/jEUH0.l
You can use convenience init:
class User: NSObject {
let userName: String
let isSuperUser: Bool = false
let someDetails: [String]?
init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
self.userName = userName
self.isSuperUser = isSuperUser
self.someDetails = someDetails
}
convenience init? (dict: NSDictionary) {
guard let userName = dictionary["user_name"] as? String else { return nil }
guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
guard let someDetails = dictionary["some_details"] as? [String] else { return nil }
self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
}
}