Failable initializers for classes in Swift 2 [duplicate] - ios

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

Testing type for class conformance in Swift

EDIT: The previous answers alluded to in the comments don't answer the question, which was how to determine if any given Type was a reference type and how to safely conform said type to AnyObject.
Testing against the passed type doesn't work, as the underlying type could be optional, or it could be a protocol, in which case one needs to know the passed instance is a class type or value type.
The solution I came up with is similar to the revised answer provided below.
So I have a new dependency injection framework, Factory.
Factory allows for scoped instances, basically allowing you to cache services once they're created. And one of those scopes is shared. Any instance shared will be cached and returned just as long as someone in the outside world maintains a strong reference to it. After the last reference releases the object the cache releases the object and a new instance will be created on the next resolution.
This is implemented, obviously, as simply maintaining a weak reference to the created object. If the weak reference is nil it's time to create a new object.
And therein lies the problem
Weak references can only apply to reference types.
Factory uses generics internally to manage type information. But I can create Factories of any type: Classes, structs, strings, whatever.)
Scopes use dictionaries of boxed types internally. If an instance exists in the cache and in the box it's returned. So what I'd like to do is create this...
private struct WeakBox<T:AnyObject>: AnyBox {
weak var boxed: T
}
The AnyObject conformance is need in order to allow weak. You get a compiler error otherwise. Now I want to box and cache an object in my shared scope with something like this...
func cache<T>(id: Int, instance: T) {
cache[id] = WeakBox(boxed: instance)
}
But this also gives a compiler error. (Generic struct WeakBox requires T to be a class type.)
So how to bridge from on to the other? Doing the following doesn't work. Swift shows a warning that "Conditional cast from 'T' to 'AnyObject' always succeeds" and then converts the type anyway.
func cache<T>(id: Int, instance: T) {
if let instance = instance as? AnyObject {
cache[id] = WeakBox(boxed: instance)
}
}
I'd be happy with the following, but again, same problem. You can't test for class conformance and you can't conditionally cast to AnyObject. Again, it always succeeds.
private struct WeakBox: AnyBox {
weak var boxed: AnyObject?
}
func cache<T>(id: Int, instance: T) {
if let instance = instance as? AnyObject {
cache[id] = WeakBox(boxed: instance)
}
}
What I'm doing at the moment is something like...
private struct WeakBox: AnyBox {
weak var boxed: AnyObject?
}
func cache<T>(id: Int, instance: T) {
cache[id] = WeakBox(boxed: instance as AnyObject)
}
Which works, but that instance as AnyObject cast depends on some very weird Swift to Objective-C bridging behavior.
Not being able to test for class conformance at runtime is driving me bonkers, and seems like a semi-major loophole in the language.
You can't test for conformance, and you can't cast for conformance.
So what can you do?
As Martin notes in a comment, any value can be cast to AnyObject in Swift, because Swift will wrap value types in an opaque _SwiftValue class, and the cast will always succeed. There is a way around this, though.
The way to check whether a value is a reference type without this implicit casting is to check whether its type is AnyObject.Type, like so:
func printIsObject(_ value: Any) {
if type(of: value) is AnyObject.Type {
print("Object")
} else {
print("Other")
}
}
class Foo {}
struct Bar {}
enum Quux { case q }
printIsObject(Foo()) // => Object
printIsObject(Bar()) // => Other
printIsObject(Quux.q) // => Other
Note that it's crucial that you check whether the type is AnyObject.Type not is AnyObject. T.self, the object representing the type of the value, is itself an object, so is AnyObject will always succeed. Instead, is AnyObject.Type asks "does this inherit from the metatype of all objects", i.e., "does this object which represents a type inherit from an object that represents all object types?"
Edit: Evidently, I'd forgotten that Swift includes AnyClass as a synonym for AnyObject.Type, so the check can be simplified to be is AnyClass. However, leaving the above as a marginally-expanded explanation for how this works.
If you want this method to also be able to handle Optional values, you're going to have to do a bit of special-casing to add support. Specifically, because Optional<T> is an enum regardless of the type of T, you're going to need to reach in to figure out what T is.
There are a few ways to do this, but because Optional is a generic type, and it's not possible to ask "is this value an Optional<T>?" without knowing what T is up-front, one of the easier and more robust ways to do this is to introduce a protocol which Optional adopts that erases the type of the underlying value while still giving you access to it:
protocol OptionalProto {
var wrappedValue: Any? { get }
}
extension Optional: OptionalProto {
var wrappedValue: Any? {
switch self {
case .none: return nil
case let .some(value):
// Recursively reach in to grab nested optionals as needed.
if let innerOptional = value as? OptionalProto {
return innerOptional.wrappedValue
} else {
return value
}
}
}
}
We can then use this protocol to our advantage in cache:
func cache(id: Int, instance: Any) {
if let opt = instance as? OptionalProto {
if let wrappedValue = opt.wrappedValue {
cache(id: id, instance: wrappedValue)
}
return
}
// In production:
// cache[id] = WeakBox(boxed: instance as AnyObject)
if type(of: instance) is AnyClass {
print("\(type(of: instance)) is AnyClass")
} else {
print("\(type(of: instance)) is something else")
}
}
This approach handles all of the previous cases, but also infinitely-deeply-nested Optionals, and protocol types inside of Optionals:
class Foo {}
struct Bar {}
enum Quux { case q }
cache(id: 1, instance: Foo()) // => Foo is AnyClass
cache(id: 2, instance: Bar()) // => Bar is something else
cache(id: 3, instance: Quux.q) // => Quux is something else
let f: Optional<Foo> = Foo()
cache(id: 4, instance: f) // => Foo is AnyClass
protocol SomeProto {}
extension Foo: SomeProto {}
let p: Optional<SomeProto> = Foo()
cache(id: 5, instance: p) // => Foo is AnyClass
So this took a while to figure out and even longer to track down the clues needed for a solution, so I'm providing my own code and answer to the problem
Given the following protocol...
private protocol OptionalProtocol {
var hasWrappedValue: Bool { get }
var wrappedValue: Any? { get }
}
extension Optional : OptionalProtocol {
var hasWrappedValue: Bool {
switch self {
case .none:
return false
case .some:
return true
}
}
var wrappedValue: Any? {
switch self {
case .none:
return nil
case .some(let value):
return value
}
}
}
And a box type to hold a weak reference...
private protocol AnyBox {
var instance: Any { get }
}
private struct WeakBox: AnyBox {
weak var boxed: AnyObject?
var instance: Any {
boxed as Any
}
}
Then the code to test and box a give type looks like...
func box<T>(_ instance: T) -> AnyBox? {
if let optional = instance as? OptionalProtocol {
if let unwrapped = optional.wrappedValue, type(of: unwrapped) is AnyObject.Type {
return WeakBox(boxed: unwrapped as AnyObject)
}
} else if type(of: instance) is AnyObject.Type {
return WeakBox(boxed: instance as AnyObject)
}
return nil
}
Note that the type passed in could be a class, or a struct or some other value, or it could be a protocol. And it could be an optional version of any of those things.
As such, if it's optional we need to unwrap it and test the actual wrapped type to see if it's a class. If it is, then it's safe to perform our AnyObject cast.
If the passed value isn't optional, then we still need to check to see if it's a class.
There's also a StrongBox type used for non-shared type caching.
struct StrongBox<T>: AnyBox {
let boxed: T
var instance: Any {
boxed as Any
}
}
And the final cache routine looks like this.
func resolve<T>(id: UUID, factory: () -> T) -> T {
defer { lock.unlock() }
lock.lock()
if let box = cache[id], let instance = box.instance as? T {
if let optional = instance as? OptionalProtocol {
if optional.hasWrappedValue {
return instance
}
} else {
return instance
}
}
let instance: T = factory()
if let box = box(instance) {
cache[id] = box
}
return instance
}
Source for the entire project is in the Factory repository.

Safely unwrapping optional values and add it to Alamofire parameters [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I have a computed property of type Parameters in my APIRouter
// MARK: - Parameters
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
var params: Parameters = ["main_category_id": doctorsFilter.0, "page": doctorsFilter.1, "specialty_id": doctorsFilter.2, "city_id": doctorsFilter.3, "region_id": doctorsFilter.4, "name": doctorsFilter.5, "company_id": doctorsFilter.6, "order_by": doctorsFilter.7]
return params
default:
return nil
}
}
some values in the Typealias called doctorsFilter are optional.
currently I have a warning asking me to provide default value for the optional values, and I don't want to provide default values , I want to check if the value exist to add it, otherwise i will not add the key and the value
how can I safely unwrap the optional values and add it to the parameters dictionary with out saying if let for all optional values?
example:
if let specialtyID = doctorsFilter.2 {
params["specialty_id"] = specialtyID
}
I don't want to unwrap it this way as I will check for all optional values and it will take more lines of code
EDIT:-
the DoctorsFilter type is documented, when I initialize an instance of type DoctorsFilter the autocomplete tells me which of them is what, I I've thought about making the DoctorsFilter class before but I'm looking for another way if any, maybe a built in reserved word can handle the whole situation! , I want to make it simple as much as possible.
making a function that handles the dictionary and returns it in DoctorsFilter class is an option. I'm thinking of adding this function to the APIRouter, is it fine to add it there? is it the rule of the APIRouter to handle the parameters ? or the APIRouter just interested in taking the parameters and will not handle it ?
There is no "one line" solution, but you can use KeyPaths to reduce the series of if let ... statements down to a loop.
Start by creating a struct for your filter rather than using a tuple.
To facilitate this, we define a protocol for Parameterable - This protocol requires a dictionary that maps parameter names (String) to the property (KeyPath) that holds that parameter name as well as a function to return the parameters dictionary.
protocol Parameterable {
var paramNames: [String:KeyPath<Self,String?>] {get}
func parameters() -> [String:Any]
}
Use an extension to create a default implementation of the parameters() function, as the code will be the same for all Parameterables. It iterates over the dictionary entries and uses the associated KeyPath to access the relevant property and put it in the output dictionary. If a given property is nil then it simply isn't added to the output dictionary, because that is how dictionaries work. No need to explicitly check.
(If you import Alamofire then you can use the typedef Parameters where I have used [String:Any])
extension Parameterable {
func parameters() -> [String:Any] {
var parameters = [String:Any]()
for (paramName,keypath) in self.paramNames {
parameters[paramName]=self[keyPath:keypath]
}
return parameters
}
}
Use this protocol to create a DoctorsFilter implementation:
struct DoctorsFilter: Parameterable {
var mainCategoryId: String?
var page: String?
var specialtyId: String?
var cityID: String?
var regionId: String?
var name: String?
var companyId: String?
var orderBy: String?
let paramNames:[String:KeyPath<Self,String?>] = [
"main_category_id":\.mainCategoryId,
"page":\.page,
"specialty_id":\.specialtyId,
"city_id":\.cityID,
"region_id":\.regionId,
"name":\.name,
"company_id":\.companyId,
"order_by":\.orderBy]
}
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
return doctorsFilter.parameters()
case .someOtherThing(let someOtherThing):
return someOtherThing.parameters()
default:
return nil
}
}
}
The other approach is to simply split your creation of the parameters dictionary into multiple lines; If you assign nil against a dictionary key then there is no key/value pair stored in the dictionary for that key. In this case I have left your tuple approach in place, but you could use the struct (and I strongly suggest you do so)
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
var params: Parameters()
params["main_category_id"] = doctorsFilter.0
params["page"] = doctorsFilter.1
params["specialty_id"] = doctorsFilter.2
params["city_id"] = doctorsFilter.3
params["region_id"] = doctorsFilter.4
params["name"] = doctorsFilter.5
params["company_id"] = doctorsFilter.6
params["order_by"] = doctorsFilter.7
return params
default:
return nil
}
}
If we want to handle mixed properties, rather than just optional strings, we need to modify the code slightly. We need to use PartialKeyPath. This makes the code a little more complex since the subscript operator for a PartialKeyPath returns a double optional. This needs to be handled.
protocol Parameterable {
var paramNames: [String:PartialKeyPath<Self>] {get}
func parameters() -> [String:Any]
}
extension Parameterable {
func parameters() -> [String:Any] {
var parameters = [String:Any]()
for (paramName,keypath) in self.paramNames {
let value = self[keyPath:keypath] as? Any?
if let value = value {
parameters[paramName] = value
}
}
return parameters
}
}
struct DoctorsFilter:Parameterable {
var mainCategoryId: String?
var page: String?
var specialtyId: String?
var cityID: Int
var regionId: String?
var name: String?
var companyId: String?
var orderBy: String?
let paramNames:[String:PartialKeyPath<Self>] =
["main_category_id":\Self.mainCategoryId,
"page":\Self.page,
"specialty_id":\Self.specialtyId,
"city_id":\Self.cityID,
"region_id":\Self.regionId,
"name":\Self.name,
"company_id":\Self.companyId,
"order_by":\Self.orderBy]
}
There are three primary ways to safely unwrap an optional. You can also provide default values if you wish to unwrap an optional.
Guard Statement Unwrapping
var firstString: String?
// In some cases you might performa a break, continue or a return.
guard let someString = firstString else { return }
print(someString)
If Let Unwrapping
var secondString: String?
var thirdString: String?
thirdString = "Hello, World!"
// Notice that we are able to use the same "IF LET" to unwrap
// multiple values. However, if one fails, they all fail.
// You can do the same thing with "Guard" statements.
if let someString = secondString,
let someOtherString = thirdString {
print(someString)
print(someOtherString)
} else {
// With this code snippet, we will ALWAYS hit this block.
// because secondString has no value.
print("We weren't able to unwrap.")
}
Default Value Unwrapping
var fourthString: String?
// The ?? is telling the compiler that if it cannot be unwrapped,
// use this value instead.
print(fourthString ?? "Hello, World")
In Swift it is recommended that anytime you see a ! that you use some form of unwrapping. Swift is very "Type Safe".
Here's a resource you can use for Optionals.
https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html
Your Solution
Your solution might look something like this.
private var parameters: Parameters? {
switch self {
case .searchForDoctors(let doctorsFilter):
if let mainCatID = doctorsFilter.0,
let page = doctorsFilter.1,
let specialtyID = doctorsFilter.2,
let cityID = doctorsFilter.3,
let regionID = doctorsFilter.4,
let name = doctorsFilter.5,
let companyID = doctorsFilter.6,
let orderBy = doctorsFilter.7 {
params: Parameters = ["main_category_id": mainCatID,
"page": page,
"specialty_id": specialtyID,
"city_id": cityID,
"region_id": regionID,
"name": name,
"company_id": companyID,
"order_by": orderBy]
return params
} else {
//Unable to safely unwrap, return nothing.
return nil
}
default:
return nil
}
}

How can I get specific instance when I use static method

I hope to get a specific instance when I use my static method. For example:
class Food: NSObject {
var name: String
class func initFruit() -> Food? {
let fruitName = NSStringFromClass(self).components(separatedBy: ".").last! as String
if "Apple" == fruitName {
return Apple(name: fruitName)
} else if "Orange" == fruitName {
return Orange(name: fruitName)
}
return nil
}
init(name: String) {
self.name = name
}
}
class Apple: Food {
}
class Orange: Food {
}
When I create an Apple instance with the method:
let apple = Apple.initFruit() as? Apple
How can I get the specific instance apple rather than use as? Apple?. I wonder how to modify the method:
static func initFruit() -> Food?
There are a couple of problems with your design, let me try and enumerate them:
base classes should not be aware of their subclasses, it's bad practice and it's not scalable, as adding a new subclass would require maintaining the base class method
the static method is not needed at all, at least in the shape it's written in the question, you could simply directly call the initializers for the subclasses
Leaving all those aside, you can use Self as return type for the static method, this will allow dynamic results.
class Food: NSObject {
var name: String
class func initFruit() -> Self {
let fruitName = NSStringFromClass(self).components(separatedBy: ".").last! as String
return self.init(name: fruitName)
}
required init(name: String) {
self.name = name
}
}
class Apple: Food {
}
class Orange: Food {
}
let apple = Apple.initFruit() // is an Apple, no cast needed
i think it's not good idea because Food is parent class and Apple inherits Food. Apple may know it's parent class cause is extends Food but Food does not.
So, if you want to create instance by some string or some variable. I would like to recommend you to adopt "Factory pattern"
reference here:
https://medium.com/swift-programming/design-patterns-creational-patterns-factory-pattern-in-swift-d049af54235b
Inspired by the question Generics in Swift - "Generic parameter 'T' could not be inferred I find another way to resolve this question. I add a method to infer the specific type.
func ascertainFruitType<T>() -> T {
return self as! T // as! is dangerous
}
Then the method initFruit is changed on below:
class func initFruit() -> Self {
let fruitName = NSStringFromClass(self).components(separatedBy: ".").last! as String
if "Apple" == fruitName {
return Apple(name: fruitName).ascertainFruitType()
} else {
return Orange(name: fruitName).ascertainFruitType()
}
}

Swift KeyPath on optional value

If I have
class Info {
var name: String?
}
class User {
var info: Info?
}
class WrappedClass {
var user: User = User()
}
let nameKeyPath = \WrappedClass.user.info?.name //Gets me KeyPath
let referencedNameKeyPath = \WrappedClass.user.info!.name //Gets me ReferenceWritableKeyPath
nameKeyPath gets me a KeyPath which I can't later on use to modify the name value, but if I force unwrap it I get a ReferenceWritableKeyPath which is what I'm after.
Unfortunately using referencedNameKeyPath with a nil value along the line expectedly crashes, due to unexpectedly finding nil.
My question is is there a way to convert the KeyPath to a ReferenceWritableKeyPath or somehow unwrap it along the way?
You can also use optional chaining on keypaths. Create two keypaths one for user and the other one for name in info.
let infoKeyPath = \WrappedClass.user.info
let nameKeyPath = \Info.name
Now, use optional chaining with keypath and it will yield String? as the result.
let name = wrappedInstance[keyPath: infoKeyPath]?[keyPath: nameKeyPath]
Try having the key path as a computed optional variable. Like that:
class WrappedClass {
var user: User = User()
var nameKeyPath: ReferenceWritableKeyPath<WrappedClass, String?>? {
guard let _ = user.info else { return nil }
return \WrappedClass.user.info!.name
}
}
You still end up with the force unwrap notation but it should not cause any issues since you specifically guard for it.
That way you can use the computed key path in a safe and convenient way:
let instance = WrappedClass()
if let nameKeyPath = instance.nameKeyPath {
instance[keyPath: nameKeyPath] = "Nikola"
}

swift optional chaining with cast

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

Resources