Swift optionals and their instantiation - ios

I'm new to Swift. I'll explain what I'm trying to do in Java terms and hopefully someone can help me understand.
I want a class scoped array that is instantiated/set in viewDidLoad of a view controller. It sounds simple enough, but this is what I had to do to get it to work. Could someone explain to me why the _dictionary must be instantiated as an empty array and why I need to use as? when unpacking dictionary even though the componentsSeparatedByString function returns an array? Thanks.
class ViewController: UIViewController, UITextFieldDelegate
{
var _dictionary : [String] = []
override func viewDidLoad()
{
super.viewDidLoad()
let bundle = NSBundle.mainBundle()
let path = bundle.pathForResource(“TextFile”, ofType: "txt")
var err: NSError?
let dico = NSString(contentsOfFile: path!, encoding: NSUTF8StringEncoding, error: &err)
if let dictionary = dico?.componentsSeparatedByString("\n") as? [String]
{
_dictionary = dictionary
}
else
{
println("Error reading dictionary")
}
}
}

_dictionary must be given an initial value because all instance properties must be given an initial value. That is a Swift safety feature to make sure that all instances are well-formed at birth. You could have done this by initializing _dictionary in an actual initializer, but you didn't. You chose to give it its first "real" value in viewDidLoad which is much later. Therefore you have forced yourself to supply a "fake" initial value until such time as viewDidLoad comes along and gives you a "real" initial value.
I don't understand your other question because I don't know what "unpacking dictionary" means. But perhaps you are referring to the rest of the stuff in this code. I'll talk you through it.
dico is an Optional wrapping a String. It is an Optional because the contentsOfFile: initializer of String returns an Optional - it is a failable initializer. That is because there might be no such file, or that file might not have that encoding, in which case the initializer needs to return nil.
So now dico is an Optional wrapping a String that must be unwrapped - or nil. So you do unwrap it as dico? in the next line. dico? is a String at that point, assuming that dico was not nil.
Then you call componentsSeparatedByString. It returns an array of AnyObject. So you have chosen to cast it down to an array of String using as?. I don't know why you chose to use as? since you know it will be an array of String if we get to this point - personally, I would have used as. But in any case, as? always returns an Optional! So you unwrap that, optionally, by using an if let conditional binding.
The irony throughout is that _dictionary is not and never was or will be a dictionary - it is an array. Your name for this (and for dictionary) is very poorly chosen! dico is a String, and dictionary and _dictionary are arrays. There is not a dictionary in sight.
Assuming we keep your rather odd names, though, you could have done all this much more briefly and clearly, like this:
var err : NSError?
if let dico = NSString(contentsOfFile: path!,
encoding: NSUTF8StringEncoding, error: nil) {
_dictionary = dico.componentsSeparatedByString("\n") as [String]
} else {
println(err)
}
Moreover, if you start with the String class instead of the NSString class, then componentsSeparatedByString will be an array of String instead of an untyped NSArray (i.e. an array of AnyObject), and you can skip the cast:
var err : NSError?
if let dico = String(contentsOfFile: path!,
encoding: NSUTF8StringEncoding, error: nil) {
_dictionary = dico.componentsSeparatedByString("\n")
} else {
println(err)
}

Related

How to encode and decode a nested array using Codable and PropertyListEncoder?

I have a UICollectionView with multiple sections. Thus my data source for the collection view is a nested array of type [[DayData?]]. There is an instance of DayData for every cell and each inside array corresponds with each section. In DayData.swift, DayData has one property, a String and conforms to Codable. The problem I am struggling with is how to properly encode and decode this nested array.
static func saveToFile(days: [[DayData?]]) {
let propertyListEncoder = PropertyListEncoder()
let encodedDays = try? propertyListEncoder.encode(days)
try? encodedDays?.write(to: archiveURL,
options: .noFileProtection)
}
static func loadFromFile() -> [[DayData?]]? {
let propertyListDecoder = PropertyListDecoder()
if let retrievedDayData = try? Data(contentsOf: archiveURL),
let decodedDays = try?
propertyListDecoder.decode(Array<Array<DayData>>.self, from:
retrievedDayData) {
return decodedDays
}
return nil
}
Right now I call saveToFile(days:) after the String property of an element DayData in the nested array is reassigned like this:
dayDataArray[indexPath!.section][indexPath!.item]?.moodColor =
When I call loadFromFile(), it returns nil, so my data isn't saving. The program builds successfully.
I would really appreciate any help. I'm new to iOS and Swift, so I apologize if this is a dumb question.
EDIT: The loadFromFile() method is throwing this error: "Expected to decode Dictionary<String, Any> but found a string/data instead." I'm guessing this means that the data is decoding into a String rather than a nested array of DayData objects, but I'm not sure why it's doing this or how to fix it.
EDIT: I made DayData not optional, and it works now.

IOS Swift Unwrapping NSData is Crashing

IOS swift problem while unwrapping, crashing. I am getting data as NSData, when I am printing in string or
var dict : AnyObject = NSJSONSerialization.JSONObjectWithData(returnData!, options: NSJSONReadingOptions(0), error: &respError) as AnyObject!
doing this its returning dict nil,
or
var datastring : NSString = NSString(data:returnData!, encoding:NSUTF8StringEncoding)[This is My Image Liknk] as! String
datastring causing fatal error:
unexpectedly found nil while unwrapping an Optional value.
You are unwrapping the values forcefully (Forced Unwrapping), If that object contains nil then it will generate runtime error.
Trying to use ! to access a non-existent optional value triggers a
runtime error. Always make sure that an optional contains a non-nil
value before using ! to force-unwrap its value.
You would do well to check all your values before you assume they're not nil. With the "!" you tell your code that this value can be safely unwrapped even though it may still be nil. Hence when it is nil, it crashes. What I usually do is:
// Unwrap the string, check if it ain't nil and store this in a new value.
if let content: String = item.content as? String {
if content != "" {
var error: NSError?
// Unwrap the data optional from dataUsingEncoding and also check if it ain't nil. Store this in a new value.
if let data: NSData = content.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true),
// Unwrap the optional returned from JSONObjectWithData and also check if it ain't nil. Store the value in a new object.
let json: NSMutableDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSMutableDictionary {
// Do stuff with the dictionary.
} else {
if let err: NSError = error {
println(err.localizedDescription)
}
}
}
}
With all these checks I am sure my code will not crash deserializing the string to json in swift.
It appears I was a bit slow with my answer but Nilesh Patel has a good quote there which you should look at.
It is because returnData! is nil. If you put a break point at var dict, run and then po returnData! I believe you will find that to be true. And because the data at which NSJSONSerialization is trying to accept as a parameter is nil, it will be as well. I would go through your logic or your source to figure out why it is nil.
Also if you are using NSURLSession to retrieve your data - make sure you have a valid configured session before calling dataTaskWithURL. Like so...
var config = NSURLSessionConfiguration.defaultSessionConfiguration()
var session = NSURLSession(configuration: config)
var dataTask:NSURLSessionDataTask = session.dataTaskWithURL...
I ran into this problem and figured out that the session needed to be configured before I called the data task.
EDIT Also try this. Use optional binding.
if let data: NSDictionary = NSJSONSerialization.JSONObjectWithData(returnData, options: NSJSONReadingOptions.AllowFragments, error: nil) as? NSDictionary {
println("json = \(data)")
}
else {
println("returnData really is nil")
}

Swift Subscript Error

I believe it has something to do with optionals, but I'm safely unwrapping sourceURL so I'm still not sure where the error is! I'm trying to access a JSON object's array's dictionary value.
However, I'm still getting the "could not find overload for 'subscript' that accepts the supplied arguments.
It seems simple, but I just can't seem to figure it out!
var dictTemp: NSDictionary! = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers, error: &localError) as? NSDictionary
var finalURL: String
// error line below
if let sourceURL = dictTemp[0]["source"]["sourceUrl"] as? NSString {
finalURL = sourceURL as String
}
NSDictionary accessed from Swift is an interesting beast.
As long as Swift only knows something is an NSDictionary (not a more specific [Key: Value] Swift-style dictionary), you can only retrieve AnyObject?s out of it.
let dictTemp: NSDictionary = // from somewhere...
let step1 = dictTemp[0] // step1 is an AnyObject?
But then, since you've imported Foundation, you can keep going with a magical subscript operator that works on AnyObject, and checks whether the thing is a dictionary:
let step2 = step1?["source"] // step2 is any AnyObject??
Here's where it gets interesting, because
if step1 was a dictionary with a "source" key inside it, step2 will be the corresponding value.
if step1 was a dictionary without a "source" key, step2 will be nil — in particular, it's AnyObject??.Some(AnyObject?.None).
if step1 was nil (the original dictionary didn't have 0 as a key), or not a dictionary (it had a 0 key with some other kind of value), then step2 will be nil — in particular, AnyObject??.None.
(The distinction between the last 2 cases is mostly unimportant and you shouldn't worry about it, but if you're interested you can see it by using dump).
And of course, we can apply the same principle again:
let step3 = step2??["sourceUrl"] // step3 is AnyObject?? again
Now, binding them all in one if:
if let url = dictTemp[0]?["source"]??["sourceUrl"] as? String {
// do something with url...
}
Caveat
This type of syntax can be dangerous, since it works with arrays and dictionaries at the same time. What would you expect in these situations?
let dict: NSDictionary = [0: ["source": [3: "result"]]]
dict[0]?["source"]??[3] // returns nil (surprise!)
dict[0]?["source"]??[3 as NSNumber] // returns "result"
let dict2: NSDictionary = [0: ["source": [8, 7, 6, 5, 4]]]
dict2[0]?["source"]??[3] // returns 5
dict2[0]?["source"]??[3 as NSNumber] // returns nil (surprise!)

AnyObject to Array?

I'm using NSJSONSerialization as so:
let twData: AnyObject? = NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.MutableLeaves, error: &dataError)
This gives me an AnyObject?.
From here, I want to convert it to Array<Dictionary<String,String>>
I've tried all sorts, leading up to this:
var twDataArray: Array<Dictionary<String,String>>? = twData? as? Array<Dictionary<String,String>>
which simply gives the error:
Type 'Array>' does not conform to protocol
'AnyObject'.
And putting the simpler version:
var twDataArray = twData as Array
gives the error:
Cannot convert the expression's type 'AnyObject?' to type 'Array'
To cast your data to an array:
var twDataArray = (twData as! NSArray) as Array
The code above first casts twData to an NSArray, and then to an Array via a bridging cast. A bridging cast is a special type of cast which converts an Objective-C type to it's _ObjectiveCBridgeable conformant, Swift counterpart.
(Note that I didn't need to write Array<AnyObject> because the element AnyObject is inferred in the bridging cast from NSArray → Array)
Note that the cast above is a forced downcast. Only use this if you're absolutely sure that twData is going to be an instance of NSArray. Otherwise, use an optional cast.
var twDataArray = (twData as? NSArray) as Array?
Try the following, you can iterate through the array as given below.
for element in twData as! Array<AnyObject> {
print(element)
}
This works in a playground:
var data: Array<Dictionary<String,String>>? = twData as? Array<Dictionary<String, String>>
the difference from your code is that twData does not require the ? at the end - it is an optional so the as? operator will take care of verifying that it can be case to an array of dictionaries - needless to say, if it's nil, as? will evaluate to nil
let twData: Any = NSJSONSerialization.JSONObjectWithData(responseData, options: NSJSONReadingOptions.MutableLeaves, error: &dataError)
Do not use AnyObject. Use Any instead of AnyObject. It will work fine. AnyObject is for all reference type and Array is a value type. That's why this comes. Change it to Any.
As you already know it is a String type you are inserting to something transformable, please do:
if let twoDataArray = twData as? Array<Dictionary<String, String>>{
for data in twoDataArray{
print(data)
}
}
This will guard you from a crashing app when the dictionary is not of type <String,String>.

Swift optionals: language issue, or doing something wrong?

I am doing what I believe to be a very simple task. I'm trying to get a value out of a dictionary if the key exists. I am doing this for a couple keys in the dictionary and then creating an object if they all exist (basically decoding a JSON object). I am new to the language but this seems to me like it should work, yet doesn't:
class func fromDict(d: [String : AnyObject]!) -> Todo? {
let title = d["title"]? as? String
// etc...
}
It gives me the error: Operand of postfix ? should have optional type; type is (String, AnyObject)
HOWEVER, if I do this, it works:
class func fromDict(d: [String : AnyObject]!) -> Todo? {
let maybeTitle = d["title"]?
let title = maybeTitle as? String
// etc...
}
It appears to be basic substitution but I may be missing some nuance of the language. Could anyone shed some light on this?
The recommended pattern is
if let maybeTitle = d["title"] as? String {
// do something with maybeTitle
}
else {
// abort object creation
}
It is possibly really a question of nuance. The form array[subscript]? is ambiguous because it could mean that the whole dictionary (<String:AnyObject>) is optional while you probably mean the result (String). In the above pattern, you leverage the fact that Dictionary is designed to assume that accessing some key results in an optional type.
After experimenting, and noticing that the ? after as is just as ambiguous, more, here is my solution:
var dictionary = ["one":"1", "two":"2"]
// or var dictionary = ["one":1, "two":2]
var message = ""
if let three = dictionary["three"] as Any? {
message = "\(three)"
}
else {
message = "No three available."
}
message // "No three available."
This would work with all non-object Swift objects, including Swift Strings, numbers etc. Thanks to Viktor for reminding me that String is not an object in Swift. +
If you know the type of the values you can substitute Any? with the appropriate optional type, like String?
There are a few of things going on here.
1) The ? in d["title"]? is not correct usage. If you're trying to unwrap d["title"] then use a ! but be careful because this will crash if title is not a valid key in your dictionary. (The ? is used for optional chaining like if you were trying to call a method on an optional variable or access a property. In that case, the access would just do nothing if the optional were nil). It doesn't appear that you're trying to unwrap d["title"] so leave off the ?. A dictionary access always returns an optional value because the key might not exist.
2) If you were to fix that:
let maybeTitle = d["title"] as? String
The error message changes to: error: '(String, AnyObject)' is not convertible to 'String'
The problem here is that a String is not an object. You need to cast to NSString.
let maybeTitle = d["title"] as? NSString
This will result in maybeTitle being an NSString?. If d["title"] doesn't exist or if the type is really NSNumber instead of NSString, then the optional will have a value of nil but the app won't crash.
3) Your statement:
let title = maybeTitle as? String
does not unwrap the optional variable as you would like. The correct form is:
if let title = maybeTitle as? String {
// title is unwrapped and now has type String
}
So putting that all together:
if let title = d["title"] as? NSString {
// If we get here we know "title" is a valid key in the dictionary, and
// we got the type right. title has now been unwrapped and is ready to use
}
title will have the type NSString which is what is stored in the dictionary since it holds objects. You can do most everything with NSString that you can do with String, but if you need title to be a String you can do this:
if var title:String = d["title"] as? NSString {
title += " by Poe"
}
and if your dictionary has NSNumbers as well:
if var age:Int = d["age"] as? NSNumber {
age += 1
}

Resources