I am trying to associated a property to an Array Extension:
private var AssociatedObjectHandle: String = "BlaBLabla"
extension Array {
var emptyIndex:Int {
mutating get {
if let object = objc_getAssociatedObject(self, &AssociatedObjectHandle) {
return object as! Int
}
let index = self.searchEmptyIndex()
self.emptyIndex = index
return index
}
set {
let new = (newValue as NSInteger)
objc_setAssociatedObject(self, &AssociatedObjectHandle, new, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
func searchEmptyIndex() -> Int {
if let arr = self as? [Int] {
return arr.index(of: -1)!
}
return -1
}
}
The objc_getAssociatedObject call always returns nil!!
Anyone has any idea why?
I am banging my head for the last hour about this...
You cannot add associated objects to a Swift Array (or any Swift value type).
objc_setAssociatedObject() and objc_getAssociatedObject() are from the
Objective-C runtime, they expect an instance of NSObject as first
argument.
Your code compiles and runs only because any Swift value is automatically
bridged to an object if necessary.
When you call objc_setAssociatedObject(self, ...) then self
is bridged to a (temporary) instance of NSArray, and the association
is made on that object.
Later, when objc_getAssociatedObject(self, ...) is called,
another (temporary) instance of NSArray is created, and that
has no associated object.
That's why you get nil as the result.
Related
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
I'm trying to do a set and get method to this Data manager class, from a dictionary and I don't know how to insert the values of the dictionary' in the set and get method (i have changed it from an array to Dic) thanks
class DataManager {
private static var sharedInstance: DataManager?
private var recordsArray: [[String:String]] = []
private let defaults = UserDefaults.standard
let userRecord: String = "userRecord";
private init(){}
public static func getInstance()-> DataManager{
if DataManager.sharedInstance == nil {
DataManager.sharedInstance = DataManager()
}
return DataManager.sharedInstance!
}
//here is my problem - set and get methods
//I don't know how to move the parameters
public func setRecordsArray([_:String,path:String]) {
self.recordsArray.append(?);
defaults.set(self.recordsArray, forKey: self.userRecord)
}
// again the same problem
public func getRecordsArray() -> [String] {
let a = self.defaults.array(forKey: self.userRecord);
return a as! [String];
}
}
The key to answering your question is to know the type of variable you want to set and get.
In this case, the recordsArray variable is an array of dictionaries of string values and keys: [[String:String]]
So, one way to pass this parameter is to create a variable of the same type as it should be set:
public func setRecordsArray(array:[[String:String]]) {
self.recordsArray = array
defaults.set(self.recordsArray, forKey: self.userRecord)
}
It simply updates the value of the self.recordsArray variable and sets the value in user defaults.
The get method works similar, however it returns a variable with the same type that should be returned.
A ? was added in the return because if there is no saved user defalts, the method returns nil:
public func getRecordsArray() -> [[String:String]]?{
if let array = defaults.object(forKey: self.userRecord) as? [[String:String]]{
self.recordsArray = array
return array
}
return nil
}
Also, you can make a set method for insert elements inside the array.
in this case, parameter type must be like [String:String], the element type of this array:
public func setRecord(record:[String:String]) {
if let array = defaults.object(forKey: self.userRecord) as? [[String:String]]{
self.recordsArray = array
self.recordsArray.append(record)
defaults.set(self.recordsArray, forKey: self.userRecord)
} else{
self.recordsArray.append(record)
defaults.set(self.recordsArray, forKey: self.userRecord)
}
}
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! :)
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)
}
}
I am looking for best way how can I make this code to one line:
if (dictionary["Amoumt"] is Double) {
amount = dictionary["Amount"] as Double
} else {
amount = NSString(string: dictionary["Amount"] as String).doubleValue
}
I have Dictionary<String, AnyObject> and I would like to parse values from it. I am using code like above but it's too many lines. I would like to make it to one line. Something like:
dictionary["Amount"].parseDouble()
There is no problem to create extension with this method:
func parseDouble() -> Double {
if (self is Double) {
return self as Double
} else {
return NSString(string:(self as String)).doubleValue
}
}
But which type should I extend? Next could you help me with generic method? So I could call something like this:
dictionary["Amount"].parse(Double)
And is this good way how to do this or should I do it another way?
You can use AnyObject as is. Try:
var dictionary:[String:AnyObject] = [
"foo": 4.21, // NSNumber
"bar": "42.5", // NSString
"baz": [1,2,3], // NSArray
]
let foo = dictionary["foo"]?.doubleValue ?? 0 // -> 4.21
let bar = dictionary["bar"]?.doubleValue ?? 0 // -> 42.5
let baz = dictionary["baz"]?.doubleValue ?? 0 // -> 0.0
This works because both NSNumber and NSString have .doubleValue property.
On the other hand, NSArray does not have that property, in this case it returns nil.
As described in the document:
You can also call any Objective-C method and access any property without casting to a more specific class type. This includes Objective-C compatible methods marked with the #objc attribute.
Maybe you are looking for this kind of extenison:
extension Dictionary{
func parseDouble(key:Key) -> Double {
let result = self[key]
if (result is Double) {
return result as Double
} else {
return NSString(string:(result as String)).doubleValue
}
}
}
And getting value for key by using this:
var doubleValue = dictionary.parseDouble("amount")
And a generic function
extension Dictionary{
func parse<T>(key:Key) -> T? {
let result = self[key]
if (result is T) {
return result as? T
}
return nil
}
}
var doubleValue:Double? = myDistionary.parse("someKeyForDouble")
var stringValue:String? = myDistionary.parse("someKeyForString")
The easiest way to condense this is to use the nil coalescing operator and an optional double. This attempts to cast the value as a double. If it succeeds, it unwraps it, if it fails, it defaults to the second value, which comes from converting `dict[amount]' to a string and taking its double value.
var amount: Double? = nil
let dict: [String: AnyObject] = ["amount" : "1.2"]
amount = (dict["amount"] as? Double) ?? NSString(string: dict["amount"] as! String).doubleValue
And to make your function:
func getDouble(obj: AnyObject) -> Double {
return (obj as? Double) ?? NSString(string: obj as! String).doubleValue
}
and you call it using getDouble(dict["amount"]). An extension for something like this is probably overkill in my opinion.
If you wanted an extension, the best place to put it in theory would be AnyObject – because it is an AnyObject that you want to convert.
But AnyObject is actually a protocol, so you can't extend it.
I wouldn't recommend putting it as an extension to Dictionary – the coercing of a type into a double is not really anything to do with dictionaries.
So the best approach is to do it as a free function not as an extension:
func getDouble(obj: AnyObject?) -> Double {
return (obj as? Double) ?? (obj as? NSString)?.doubleValue ?? 0
}
asDouble(dictionary["Amount"])
Note, this function is safe if you pass it a function that is neither a double nor a string. Other solutions using as or ! instead of as? will crash at runtime if you ever pass something else in.
You could argue it should return an optional, with nil if the value was not convertible to a double. This is what String.toInt() does. Unfortunately, the limitation of NSString.doubleValue is that it doesn't do this – it defaults to zero instead – so you can't combine these two approaches.