The_iOS_Apprentice_v3.3_Changes_for_Swift_1.2

Transcription

The_iOS_Apprentice_v3.3_Changes_for_Swift_1.2
Changes for Swift 1.2 and
Xcode 6.3
Swift 1.2
In April 2015, Apple released Xcode 6.3, which includes Swift version 1.2.
This new version of Swift introduces a number of changes to the language that are
not backwards compatible. As a result, the projects from previous releases of The
iOS Apprentice for Xcode 6.2 and earlier will give errors when you try to compile
them with Xcode 6.3.
This document lists what changes you need to make to the source code in order to
build the projects without errors.
For a complete overview of what’s new in Swift 1.2, visit the following link:
http://www.raywenderlich.com/95181/whats-new-in-swift-1-2
New type casting operator: as!
The biggest change to Swift that causes existing code to break is the new as! type
cast. This replaces as in many cases.
For example:
cell.viewWithTag(1000) as UILabel
is now written as:
cell.viewWithTag(1000) as! UILabel
The previous version of Swift had just two type cast operators, as and as?. Swift
1.2 adds a new one, as!, with an exclamation point at the end.
The difference between the three casts is as follows:
as is a type cast that will always work. Swift knows enough about all the types
involved to guarantee this. This kind of cast is quite rare.
as? is an optional cast. If you write x as? Y, you’re trying to cast the variable x to
an object of type Y. There are two reasons why this may fail: x is nil or the type of
The iOS Apprentice (Third Edition)
StoreSearch
x isn’t compatible with type Y. Because as? is an optional cast, you can catch these
situations with if let.
as! is the new kid on the block. You need this kind of cast because the iOS APIs
often return AnyObject types, and Swift can’t draw any conclusions from these
vague AnyObjects at the moment it is compiling your app. It won’t know what type
the object is really supposed to be until you run the app.
With x as! Y you’re telling Swift that it’s OK to perform this cast, even though Swift
can’t verify that x is truly compatible with type Y. So you’re asking Swift to take a
leap of faith.
For example, if you know that the AnyObject you’re dealing with is really a
UITableViewCell, then it’s perfectly fine to write x as! UITableViewCell.
Of course, if you’re not careful and the objects you’re casting don’t have the type
you expect them to have, using as! will crash the app. C’est la vie. ;-)
Tutorial 1: Getting Started
No changes to the source code are needed for this tutorial.
Tutorial 2: Checklists
All the as type casts occurring in this tutorial are now as!, with an exclamation
mark at the end.
Tutorial 3: MyLocations
Almost all the as type casts are now written as! in this tutorial. There are two
exceptions to this change, both in LocationDetailsViewController.swift:
1) Inside imagePickerController(didFinishPickingMediaWithInfo), the line that
reads the UIImage from the info dictionary currently is:
image = info[UIImagePickerControllerEditedImage] as UIImage?
This should become:
image = info[UIImagePickerControllerEditedImage] as? UIImage
You could change this to as! UIImage?, but using the optional as? cast is simpler
and does the same thing.
3
The iOS Apprentice (Third Edition)
StoreSearch
2) In textView(shouldChangeTextInRange, replacementText), the following cast
should remain unchanged:
descriptionText = (textView.text as NSString).stringByReplacing...
The type of textView.text is String and this is “bridged” with NSString, so this kind
of cast can never fail. That’s why you don’t need as! or as? here.
Fixed a crashing bug with editing locations from the Map screen
To reproduce this bug, launch the app and immediately switch to the Map tab. Tap
a location to edit it and change its description. After you press Done, the app
crashes with the message,
“fatal error: unexpectedly found nil while unwrapping an Optional value”.
(Note: This crash does not happen when you change the location’s category, only
the description.)
The fix: In LocationsViewController.swift, in the extension for the
NSFetchedResultsControllerDelegate, the code in case .Update should be:
case .Update:
println("*** NSFetchedResultsChangeUpdate (object)")
if let cell = tableView.cellForRowAtIndexPath(indexPath!)
as? LocationCell {
let location = controller.objectAtIndexPath(indexPath!) as! Location
cell.configureForLocation(location)
}
This puts the call to tableView.cellForRowAtIndexPath() inside an if let
statement, so that the rest of the code is skipped when this returns nil.
In case you’re interested, this bug is actually the result of the workaround for
another bug. (Don’t you just love programming?)
The section on using NSFetchedResultsController describes a problem with Core
Data – the “FATAL ERROR: The persistent cache of section information does not
match the current configuration” crash.
One proposed solution to prevent this from occurring was to add the following line
to AppDelegate.swift:
let forceTheViewToLoad = locationsViewController.view
That solved the Core Data caching problem, but it also introduced this sneaky new
bug into the Map screen.
Let this be a lesson that even a fairly simple app such as this can have all kinds of
complicated bugs hidden inside it. ;-)
4
The iOS Apprentice (Third Edition)
StoreSearch
Tutorial 4: StoreSearch
Almost all the as type casts are now written as! in this tutorial, but pay attention to
the following.
String and NSString
As of Swift 1.2, NSString objects are no longer automatically converted to Swift’s
native String type. This affects the JSON parsing code. For example, the following
no longer works:
searchResult.name = dictionary["trackName"] as NSString
The type of searchResult.name is String, but the value on the right-hand side of the
= sign is an NSString. You should now write this as follows:
searchResult.name = dictionary["trackName"] as! String
This converts the value from dictionary["trackName"], which is an AnyObject,
directly to a String. (Of course, this only works if the AnyObject really is a string to
begin with; if not, the as! operator will crash the app.)
Xcode suggests that you fix this code as:
searchResult.name = dictionary["trackName"] as! NSString as String
That accomplishes the same; first it tries to cast the AnyObject to an NSString, and
then it converts that NSString to a native Swift string. The first cast may fail if
dictionary["trackName"] contains something else than a string, but the cast from
NSString to String will always succeed.
In previous Swift versions, this conversion was done automatically for you by the
compiler, but as of Swift 1.2 you have to be explicit about it.
To summarize, simply replace all occurrences of as NSString by as! String, and all
occurrences of as? NSString by as? String.
Double and NSNumber
In the JSON parsing code, replace the following code:
if let price = dictionary["trackPrice"] as? NSNumber {
searchResult.price = Double(price)
}
by this:
if let price = dictionary["trackPrice"] as? Double {
5
The iOS Apprentice (Third Edition)
StoreSearch
searchResult.price = price
}
It is no longer necessary to cast to NSNumber first and then convert to Double; you
can do this in one step now.
The end.
6