I have this class Identity and a dictionary of instances of them with Strings as keys. I want to access one of the instances by a string and change some of its properties. I'm trying to use a switch statement to access the instances in the dictionary depending on the value of a string.
class Identity {
let provider: String
let uid: String?
let token: String?
let name: String?
init(provider: String){
self.provider = provider
self.uid = nil
self.token = nil
self.name = nil
}
}
var identities = [String:Identity]()
identities["twitter"] = Identity(provider: "twitter")
identities["twitter"].uid = "131241241241"
identities["twitter"].name = "#freedrull"
let provider: String = "twitter"
var i: Identity? {
switch provider {
case "twitter":
return identities["twitter"] as Identity?
case "facebook":
return identities["facebook"] as Identity?
case "soundcloud":
return identities["soundcloud"] as Identity?
default:
return nil
}
}
if i != nil {
i.name = "tony"
}
I get an error about assigning i.name to "tony". Do I need to cast i to an Identity somehow? I thought it already was.
You have declared i as an Optional:
var i: Identity? // ...
So it's still an Optional. It's not an Identity. It's an Optional wrapping an Identity. But you can't do anything to an Optional - until you unwrap it. Unwrap it, to get at the Identity. You have:
if i != nil {
i.name = "tony"
}
Instead:
if let i = i {
i.name = "tony"
}
Or:
if i != nil {
i!.name = "tony"
}
Both are ways of unwrapping the Optional.
Or, test and unwrap all in one move:
i?.name = "tony"
Then you'll have a new problem; you have declared name as a constant. You can't change a constant! You have:
let name: String?
Instead:
var name: String?
[By the way, much of this code is redundant:
init(provider: String){
self.provider = provider
self.uid = nil
self.token = nil
self.name = nil
}
uid, token, and name are all Optionals, so they are already nil. You can cut those three lines.]
Related
I've been using nested if let(s) to check nils. For example, a class that contains name and age
class Person {
var name: String?
var age: Int?
}
if let p = Person() as? Person {
if let name = p.name {
}
if let age = p.age {
}
}
When I'm unsure if a variable is empty/null, is it necessary to use if let on everything and everytime? just trying to make sure if im doing it right.
You need if-let only when you need to check for nil and use the unwrapped value. Otherwise you can simply check for nil using != or == operator.
Also there is no need to explicitly typecast p to Person using as? Person, since the type can be inferred already.
let p = Person()
if p.name != nil {
}
if p.age != nil {
}
You do not need to check anything
p = Person()
creating a new Person object with no initial values means that all properties are nil so the following checks are unnecessary, we already know they are nil
if let name = p.name {
if let age = p.age {
I would suggest you add a constructor with parameters so that you can set the properties directly
Either with mandatory values so you don't have to check for nil afterwards
init(name: String, age: Int) {
self.name = name
self.age = age
}
or with optional values
init(name: String?, age: Int?) {
self.name = name
self.age = age
}
but then you need to check
var name: String?
var age: Int?
//...other code
let p = Person(name: name, age: age)
if let personName = p.name {
}
if let personAge = p.age {
}
I have a class called User()
class User {
var name: String?
var email: String?
var id: String?
var identification_number: String?
var phone_number: NSMutableArray?
var user_group: String?
var date: NSDate?
}
I want to get all of the variables in the class and their respective values. I am trying to use Mirror in this case.
func updateProfile(user: User) {
let mirror = Mirror(reflecting: user)
for child in mirror.children {
print("\(child.label!), \(child.value)")
}
}
My question is, how can I convert child.value to any other datatype, say String ?
I only got to find out that child.value belongs to the Protocol 'Any'
child.value has the Any type. Casting from Any to an optional poses some problems, fortunately Sandy Chapman gave a very nice solution in this post.
With his function, the code would look like this:
func castToOptional<T>(x: Any) -> T? {
return Mirror(reflecting: x).descendant("Some") as? T
}
func updateProfile(user: User) {
let mirror = Mirror(reflecting: user)
for child in mirror.children {
print("\(child.label!), \(child.value)")
if let stringVal = castToOptional(child.value) as String? {
print("Unwrapped a string: \(stringVal)")
} else if let stringVal = child.value as? String {
print("Found a non-optional string: \(stringVal)")
}
}
}
So if you're looking for strings, you need to look for both optional and non-optional ones. This applies to all types you need to check.
Create a protocol for extending Optional<Any> type to return it's non-optional-value:
private protocol AnyOptional {
var objectValue: Any? { get }
}
extension Optional: AnyOptional {
var objectValue: Any? {
switch self {
case .None:
return nil
case .Some(_):
return self! as Any
}
}
}
Thereafter you can use AnyOptional protocol as a type, and cast Any? objects to AnyOptional, thereafter allowing us to make use of the .objectValue property of AnyOptional
class User {
var name: String?
var email: String?
var id: String = "Default ID" // Lets try also with one non-optional
var identification_number: String?
var phone_number: NSMutableArray?
var user_group: String?
var date: NSDate?
}
var myUser = User()
myUser.name = "John"
myUser.phone_number = ["+44", "701 23 45 67"]
func updateProfile(user: User) {
let mirror = Mirror(reflecting: user)
for child in mirror.children {
let value : Any = (child.value as? AnyOptional)?.objectValue ?? child.value
switch(value) {
case let obj as String: print("String item: User.\(child.label!) = " + obj)
case let obj as NSMutableArray: print("NSMutableArray item: User.\(child.label!) = \(obj)")
case let obj as NSDate: print("NSDate item: User.\(child.label!) = \(obj)")
case _ : print("Non-initialized optional item: User.\(child.label!) = \(value)")
}
}
}
Which yields the following output
updateProfile(myUser)
/*
String item: User.name = John
Non-initialized optional item: User.email = nil
String item: User.id = Default ID
Non-initialized optional item: User.identification_number = nil
NSMutableArray item: User.phone_number = (
"+44",
"701 23 45 67"
)
Non-initialized optional item: User.user_group = nil
Non-initialized optional item: User.date = nil */
The benefit of using this solution is that it will "unwrap" optional non-nil values of child.value (without the "Optional(...)" padding) as well as values of child.value that are not optional, without the need of separate "unwrapping" for the two cases. In the switch case above, you can handle whatever non-nil property of the User object that you need to work with, not just as String but any of the types in your User class. The obj property in the switch case will be of the non-optional type of each of the non-nil properties of your class. The default case corresponds to optionals with value nil (not assigned).
I have a class User:
import UIKit
import ObjectMapper
class User: NSObject, CustomStringConvertible, Mappable {
var FirstName: NSString! ;
var LastName: NSString! ;
required init?(_ map: Map){
}
func mapping(map: Map) {
FirstName <- map["FirstName"]
LastName <- map["LastName"]
}
override var description:String {
var s:String=""
//USE REFLECTION TO GET NAME AND VALUE OF DATA MEMBERS
for var index=1; index<reflect(self).count; ++index {
s += (reflect(self)[index].0 + ": "+reflect(self)[index].1.summary+"\t")
}
return s
}
}
In swift 1.2, I was using reflect() method to get array of all the data members with their names and values.
Now, after I have updated to swift 2, I am getting the following error:
'reflect' is unavailable: call the 'Mirror(reflecting:)' initializer
With some trials, I was able to get the count of data members by this: Int(Mirror(reflecting: self).children.count), but still, I am unable to get the member name and its value.
I have looked into the following resources:
https://netguru.co/blog/reflection-swift
http://nshipster.com/mirrortype/
UPDATE
I have found the an answer here: https://stackoverflow.com/a/32846514/4959077. But this doesn't tell how to find out the type of reflected value. If the value is int and we parse it into String then it gives error.
You may access the reflected attribute "label" name, value and type as follows:
let mirror = Mirror(reflecting: SomeObject)
var dictionary = [String: Any]()
for child in mirror.children {
guard let key = child.label else { continue }
let value: Any = child.value
dictionary[key] = value
switch value {
case is Int: print("integer = \(anyValue)")
case is String: print("string = \(anyValue)")
default: print("other type = \(anyValue)")
}
switch value {
case let i as Int: print("• integer = \(i)")
case let s as String: print("• string = \(s)")
default: print("• other type = \(anyValue)")
}
if let i = value as? Int {
print("•• integer = \(i)")
}
}
Note: per the question followup, three approaches to determine the type of the reflected value are shown.
I have a solution that finds the name and type of a property given any class that inherits from NSObject.
I wrote a lengthy explanation on StackOverflow here, and my project is available here on Github,
In short you can do something like this (but really check out the code Github):
public class func getTypesOfProperties(inClass clazz: NSObject.Type) -> Dictionary<String, Any>? {
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else { return nil }
var types: Dictionary<String, Any> = [:]
for i in 0..<Int(count) {
guard let property: objc_property_t = properties[i], let name = getNameOf(property: property) else { continue }
let type = getTypeOf(property: property)
types[name] = type
}
free(properties)
return types
}
I want to unwrap these 6 optional variables, and if they are null i want to give them a blank String value. This is so I can send these variables packaged into a parameters array that's sent to an API.
I'm still a beginner at Swift, and this is the only easiest way I have understood how to implement this, but the inner coder in me is saying this looks redundant and crappy as ****.
Can someone help me condense this or make it simpler?
if let fbEmail = self.fbEmail {
}else{
self.fbEmail = ""
}
if let fbDob = self.fbDob {
}else{
self.fbDob = ""
}
if let fbGender = self.fbGender {
}else{
self.fbGender = ""
}
if let fbUserIp = self.fbUserIp {
}else{
self.fbUserIp = ""
}
if let fbFacebookId = self.fbFacebookId {
}else{
self.fbFacebookId = ""
}
if let fbFacebookAccessToken = self.fbFacebookAccessToken {
}else{
self.fbFacebookAccessToken = ""
}
You can do that in exactly 6 lines of code:
self.fbEmail = self.fbEmail ?? ""
self.fbDob = self.fbDob ?? ""
self.fbGender = self.fbGender ?? ""
self.fbUserIp = self.fbUserIp ?? ""
self.fbFacebookId = self.fbFacebookId ?? ""
self.fbFacebookAccessToken = self.fbFacebookAccessToken ?? ""
Edit: what's up with the ?? syntax: It's a shortcut "if nil assign another value":
let c = a ?? b
will assign c = a if a != nil, otherwise c = b.
You can unwrap more than one at a time. But if you do, you will have no way of knowing which one is nil. You will only know that either some are nil or none are.
Aside from that do they all need to be optional? Can't you init them with the default values you are giving them when they are nil?
Snippets:
Just avoid the issue altogether.
// just init with a default value without them being an optional
var fbEmail : String = ""
var fbDob : String = ""
Replace checking for nil with .isEmpty
var string : String = ""
string.isEmpty // returns true
string = "SomeString"
string.isEmpty // returns false
Optionals with starting values.
// init wit default values while still optional
var fbEmail : String? = ""
var fbDob : String? = ""
Unwrap more than one at a time.
if let unwrfbEmail = fbEmail, let unwrfbDob = fbDob {
// stuff available here
} else {
// handle the error
}
guard let unwrfbEmail = fbEmail, let unwrfbDob = fbDob else {
// put return/break here and handle the error
}
// stuff available here
A container for all values. They are optionals but when set to nil they will reset to a default value. You can also declare them as forced unwrapped optionals !. Or unwrap them all at once with a method found above.
Obviously copy paste and alter the didSet to all variables.
// container stuct that uses didSet to avoid nil and reset to default values
struct container {
var fbEmail : String? = "" {
didSet {
if fbEmail == nil {
fbEmail = ""
}
}
}
var fbDob : String? = ""
var fbGender : String? = ""
var fbUserIp : String? = ""
var fbFacebookId : String? = ""
var fbFacebookAccessToken : String? = ""
}
It's trivial enough to do something like this:
class Collection {
init(json: [String: AnyObject]){
guard let id = json["id"] as? Int, name = json["name"] as? String else {
print("Oh noes, bad JSON!")
return
}
}
}
In that case we were using let to initialize local variables. However, modifying it to use class properties causes it to fail:
class Collection {
let id: Int
let name: String
init(json: [String: AnyObject]){
guard id = json["id"] as? Int, name = json["name"] as? String else {
print("Oh noes, bad JSON!")
return
}
}
}
It complains that let or var needs to be used but obviously that isn't the case. What's the proper way to do this in Swift 2?
In the if let, you are unwrapping values from the optional as new local variables. You can’t unwrap into existing variables. Instead, you have to unwrap, then assign i.e.
class Collection {
let id: Int
let name: String
init?(json: [String: AnyObject]){
// alternate type pattern matching syntax you might like to try
guard case let (id as Int, name as String) = (json["id"],json["name"])
else {
print("Oh noes, bad JSON!")
self.id = 0 // must assign to all values
self.name = "" // before returning nil
return nil
}
// now, assign those unwrapped values to self
self.id = id
self.name = name
}
}
This is not specific to class properties - you can’t conditionally bind into any variable, for example this doesn’t work:
var i = 0
let s = "1"
if i = Int(s) { // nope
}
Instead you need to do:
if let j = Int(s) {
i = j
}
(though of course, in this case you’d be better with let i = Int(s) ?? 0)