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)
}
}
Related
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.
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 get a better understanding of protocols in Swift. Specifically optional protocol methods. I thought the issue might have to do with my protocol being defined / used in a different file, but if you put the following in a playground you'll get the same issue:
import Foundation
#objc protocol MyProtocol {
optional func shouldJump() -> Bool
}
extension NSObject : MyProtocol {}
class Test {
func testJump() {
let object = NSObject()
let jump = object.shouldJump?() ?? true
print("should jump: \(jump)")
}
}
let t = Test()
t.testJump()
Here is the error message:
error: value of type 'NSObject' has no member 'shouldJump'
let jump = object.shouldJump?() ?? true
^~~~~~ ~~~~~~~~~~
For some reason it doesn't accept that the protocol has been defined on NSObject. Code completion finds it, but the compiler doesn't let it pass.
I'm not sure if my ?? true part will work, but I want that to be a default value incase the method isn't defined.
How do I get this to work?
Your NSObject conforms to MyProtocol, but because it doesn't implement the optional protocol method, the compiler knows it does not have the Selector shouldJump:
let object = NSObject()
object.conformsToProtocol(MyProtocol) // true
object.respondsToSelector("shouldJump") // false
One way to solve this is to implement the protocol method in the extension in order for the object to perform that selector:
extension NSObject : MyProtocol {
func shouldJump() -> Bool {
// some logic here
return true
}
}
class Test {
func testJump() {
let object = NSObject()
let jump = object.shouldJump()
print("should jump: \(jump)")
}
}
let t = Test()
t.testJump() // works
If you don't want to implement the optional method in the extension, you have to cast your NSObject as MyProtocol and verify that it responds to the optional Selector:
class Test {
func testJump() {
let object = NSObject()
let obj = object as MyProtocol
if object.respondsToSelector("shouldJump") {
let jump = obj.shouldJump?()
print("should jump: \(jump)")
} else {
print("nope")
}
}
}
You can also skip the respondsToSelector step and use an if let or guard to verify that shouldJump() returns non-nil.
class Test {
func testJump() {
let object = NSObject()
guard let obj: MyProtocol = object else {
return // object does not conform to MyProtocol
}
if let jump = obj.shouldJump?() { // if shouldJump() returns non-nil
print("should jump: \(jump)")
} else {
print("nope")
}
}
}
I think this is because the compiler knowns NSObject doesn't have shouldJump method, so the call object.shouldJump?() makes no sense. You can cast object to your protocol:
let jump = (object as MyProtocol).shouldJump?() ?? true
Swift is a type safe language. In order to be able to use shouldJump?() you first must have an object conformant to MyProtocol. In this case you can simply cast your type:
let jump = (object as MyProtocol).shouldJump?() ?? true
You can also store it in a variable:
let jumper = object as MyProtocol
let jump = jumper?.shouldJump() ?? true
I have the following class:
class Foo {
let a : Int?
let b : Int?
init?(){
}
}
I get the error "constant self.a used before being initialised" in the failable initialiser. What on earth is the compiler talking about? I haven't used a at all yet!
The problem is that each property declared with let in a class must be populated before the init does return.
In your case the init is not populating the 2 constant properties.
In Swift 2.1 each constant property of a class must be populated even when a failable initializer does fail.
class Foo {
let a: Int?
let b: Int?
init?() {
return nil // compile error
}
}
More details here.
Struct
On the other hand you can use a struct where a failable initializer can return nil without populating all the let properties.
struct Person {
let name: String
init?(name:String?) {
guard let name = name else { return nil }
self.name = 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! :)