The code below highlights a problem I'm having combining optional chaining and casts with Apple's swift language
import Foundation
import CoreData
class MyExample {
var detailItem: NSManagedObject?
func example() {
//In the actual implementation it is assigned to a UITableViewCell textLabel.text with the same result.
let name: String = self.detailItem?.valueForKey("name") as String
}
}
The above results in:
'AnyObject' is not convertible to 'String'
I am able to do this by making the class variable an implicitly unwrapped optional as follows:
class MyExample2 {
var detailItem: NSManagedObject!
func example() {
let name: String = self.detailItem.valueForKey("name") as String
}
}
This works, but now this variable doesn't reflect my real world need for it to be optional
This next example also works with detailItem as optional:
class MyExample3 {
var detailItem: NSManagedObject?
func example() {
let name: String = self.detailItem?.valueForKey("name").description as String
}
}
The problem with the above is I had to use description. It's generally not a good idea to use output from this function to show to the user (according to Apple, and just common sense)
The above also only works because I am looking for a string. If I needed an object, it wouldn't work.
As a point of interest this example throws a different error:
class MyExample4 {
var detailItem: NSManagedObject?
func example() {
let name: String = self.detailItem?.valueForKey("name")
}
}
The above throws:
Could not find member 'valueForKey'
NSmanagedObject clearly has a valueForKey.
Trying one more thing, I discovered a potential solution:
class MyExamplePotentialSolution {
var detailItem: NSManagedObject?
func example() {
let name: NSString = self.detailItem?.valueForKey("name") as NSString
}
}
The problem with the above, is it doesn't work when actually assigned to a UITableViewCell detailTextLabel.text attribute.
Any ideas?
Updated Answer
The simplest real world usage turned out to be this:
cell.detailTextLabel.text = self.detailItem?.valueForKey("name") as? NSString
The key is AnyObject can't be cast to a native swift type directly. As the accepted answer showed, it can be cast as a native Swift string from NSString. It just isn't necessary in this case.
There are 2 problems: the first is that in this line:
let name: String = self.detailItem?.valueForKey("name") as String
the right part is an optional (detailItem is optional), so whatever the expression returns, it cannot be assigned to a non-optional variable having type String
The second problem is that valueForKey returns AnyObject!, which can be an instance of any class - but String is not a class, you'd need Any in order to be able to cast that into a String.
I presume that NSManagedObject returns an NSString, so you can achieve what you need with this line:
let name: String? = (self.detailItem?.valueForKey("name") as? NSString) as? String
Related
I have created a Realm object that needs to store an enum value. To do that I use a method outlined in this question which involves declaring a private property of type String, and then declaring another property of type Enum that sets/reads the private property using getters and setters.
For ease of reference here is the code for that:
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
override static func primaryKey() -> String? {
return "id"
}
}
I have noticed though that if I add a convenience init to the class declaration (to make it a bit easier to initialise the object) the revisionType properties on the objects I end up with adopt the default value declared in the class, and NOT the revision type value passed to the class using the convenience init.
Here is the class declaration with a convenience init
#objcMembers
class PlaylistRealmObject: Object {
dynamic var id: String = UUID().uuidString
dynamic var created: Date = Date()
dynamic var title: String = ""
private dynamic var revisionTypeRaw: String = RevisionType.noReminder.rawValue
var revisionType: RevisionType {
get { return RevisionType(rawValue: revisionTypeRaw)! }
set { revisionTypeRaw = newValue.rawValue }
}
let reminders = List<ReminderRealmObject>()
let cardsInPlaylist = List<CardRealmObject>()
convenience init(title: String, revisionType: RevisionType) {
self.init()
self.title = title
self.revisionType = revisionType
}
override static func primaryKey() -> String? {
return "id"
}
}
And - to make things even more perplexing - if I simply remove the word 'private' from the revisionTypeRaw property, everything works fine!
I am confused. 1) Why does adding a convenience init have this effect? 2) Why does making the property 'public' resolve the issue?
I have created a demo Xcode project to illustrate the issue and can share it if anyone needs it.
Update:
I found the problem. It has nothing to do with the convenience init. I am using #objcMembers at the top of the class as per the Realm docs: https://realm.io/docs/swift/latest/#property-attributes
If you remove this and place #objc in front of the private keyword, everything works as would be expected. I guess the question then is: what explains this behaviour?
This is a good question but I think the issue is elsewhere in the code. Let's test it.
I created a TestClass that has a Realm managed publicly visible var, name, as well as a non-managed public var visibleVar which is backed by a Realm managed private var, privateVar. I also included a convenience init per the question. The important part is the privateVar is being set to the string "placeholder" so we need to see if that is overwritten.
class TestClass: Object {
#objc dynamic var name = ""
#objc private dynamic var privateVar = "placeholder"
var visibleVar: String {
get {
return self.privateVar
}
set {
self.privateVar = newValue
}
}
convenience init(aName: String, aString: String) {
self.init()
self.name = aName
self.visibleVar = aString
}
}
We then create two instances and save them in Realm
let a = TestClass(aName: "some name", aString: "some string")
let b = TestClass(aName: "another name", aString: "another string")
realm.add(a)
realm.add(b)
Then a button action to load the two objects from Realm and print them.
let testResults = realm.objects(TestClass.self)
for test in testResults {
print(test.name, test.visibleVar)
}
and the output:
some name some string
another name another string
So in this case, the default value of "placeholder" is being overwritten correctly when the instances are being created.
Edit:
A bit more info.
By defining your entire class with #objMembers, it exposes your propertites to Objective-C, but then private hides them again. So that property is not exposed to ObjC. To reverse that hiding, you have to say #objc explicitly. So, better practice is to define the managed Realm properties per line with #objc dynamic.
I am converting my project Objective-C code to Swift. The thing fine but while having its for in loop i am facing error something like its type conversion issue. Thanks In Advance.
class GymUserSession: NSObject {
var passes = [AnyHashable]()
func getPassList() -> [AnyHashable]? {
var list = [AnyHashable]()
for pass: GymPass? in passes {
if pass?.isGift == nil || pass?.activated != nil {
if let aPass = pass {
list.append(aPass)
}
}
}
return list
}
}
GymPass is another NSObject Class
class GymPass: NSObject {
var gymID : String
var passID : String
var isGift : Bool
var activated : Bool
var dateCreated : Date?
var dateActivated : Date?
}
The short answer is that Swift can't implicitly downcast an AnyHashable to a GymPass, which is what you have asked it to do.
You could fix the error by explicitly downcasting, but really that is just addressing one small issue that would let the code compile
In Swift you should always use the most explicit type you can, when it is known. Types such as Any, AnyObject and AnyHashable should only be used when you don't know the type or there may be multiple types. For example a dictionary obtained decoding JSON could be [String:Any] since you know it will have String keys, but the value types will be varied.
In this case, presumably, you know that passes will contain GymPass instances. A more "Swifty" version of your code could look something like this (but it is hard to be specific as I don't have enough detail on where the data is coming from and what you are trying to achieve, exactly):
struct GymPass {
var gymID: String
var passID: String
var isGift: Bool
var dateCreated: Date
var dateActivated: Date?
var isActivated: Bool {
get {
return self.dateActivated != nil
}
}
}
class GymUserSession: NSObject {
var passes = [GymPass]()
func getPassList() -> [GymPass] {
return passes.filter ( { $0.isGift || $0.isActivated } )
}
}
Currently, I've made a custom class called SongNames. Here's the code for it:
import Foundation
class songAttributes {
private var _songTitle: String!
private var _songURL: String!
var songTitle: String {
return _songTitle
}
init(songTitle: String, songURL: String) {
_songURL = songURL
_songTitle = songTitle
}
var songURL: String {
return _songURL
}
}
I have no problem setting values for this class. For instance, I might write:
var songAttribute = songAttributes(songTitle: "That's What I Like", url: "some url")
However, if I try to change the properties of my variable that I just created, like saying:
songAttribute.songTitle = "Locked Out of Heaven"
I get the message: "error: songTitle is a get only property"
That being said, is there a way to mess with my SongName class so that these properties can be changed, and not just get-only?
If you want the properties to be settable then there is no need for the dance with the private properties. Also, the properties should not be implicitly unwrapped, since you are setting them in the initialiser.
import Foundation
class SongAttributes {
var songTitle: String
var songURL: String
init(songTitle: String, songURL: String) {
self.songURL = songURL
self.songTitle = songTitle
}
}
Note that, by convention, class and struct names should start with a capital letter.
You are doing a lot of unnecessary work. You have private variables backing all of your public variables, and have made your public variables computed properties. Get rid of all that. It's needless complexity:
import Foundation
class songAttributes {
var songTitle: String
var songURL: String
init(songTitle: String, songURL: String) {
self.songURL = songURL
self.songTitle = songTitle
}
}
By default properties are read/write, so you don't need to do anything special.
The pattern of having private backing variables that start with the _ prefix is largely a holdover from Objective-C, and rarely needed in Swift.
i am confused about that should i need to used NSObject to swift3 . if so please guide me also is it best practice to used NSNumber
class App: NSObject {
var id: NSNumber?
var name: String?
var category: String?
var imageName: String?
var price: NSNumber?
var screenshots: [String]?
var desc: String?
var appInformation: AnyObject?
// override func setValue(_ value: Any?, forKey key: String) {
// if key == "description" {
// self.desc = value as? String
// } else {
// super.setValue(value, forKey: key)
// }
// }
}
Note : could you tell me please why need to use NSObject ? what is the advantage ?
NSObject class:
This class is the root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects. (source).
AnyObject protocol: its a implicit confirmation of any object.
You use AnyObject when you need the flexibility of an untyped object or when you use bridged Objective-C methods and properties that return an untyped result. AnyObject can be used as the concrete type for an instance of any class, class type, or class-only protocol.
AnyObject can also be used as the concrete type for an instance of a type that bridges to an Objective-C class.
let s: AnyObject = "This is a bridged string." as NSString
print(s is NSString)
// Prints "true"
let v: AnyObject = 100 as NSNumber
print(type(of: v))
// Prints "__NSCFNumber"
The flexible behavior of the AnyObject protocol is similar to Objective-C’s id type. For this reason, imported Objective-C types frequently use AnyObject as the type for properties, method parameters, and return values. (source)
So you can use NSNumber variable as AnyObject which can be later type cast implicitly accordingly.
You're overriding a super class method:
override func setValue(_ value: Any?, forKey key: String) { }
So, if your class doesn't extends NSObject class, how could overrides its super class method?
You could not extend NSObject class, but you'll have to remove override keyword from code since setValue would become a method of your custom class. If you do it, you'll not be able to call super.setValue(value, forKey: key) obviously.
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)
}
}