This is Part 1 of a series of articles advocating Swift to the often stubborn Objective-C developer.
Some time has passed since WWDC 2014 and the reveal of the Swift programming language. The hype has settled and most of us have gotten on with our lives continuing to write our code in Objective-C, toying with Swift in our free time or using it for small modules within our production code. There have indeed been some early adopters writing new apps from scratch in the language, but I imagine that has more to do with the ability to announce the fact rather than any benefit to the programmer or business. After quite some fanfare, nothing has really changed.
I believe this is a good thing. Objective-C has been validated as a production-ready programming language for decades. Swift... not so much. It was necessary for Apple to release Swift when they did in order to garner the interest of the community and incorporate feedback. Creating a programming language is a process that spans years, and we're only in the very beginning stages. Swift is a language for 2016. This is made painfully obvious by the available tools and the state of the compiler itself. We're priviledged with a very mature toolset as Objective-C developers working with Clang & LLDB. But it wasn't very long ago that we were stuck with GCC and its less-than-perfect compiler errors, and that project was around for twenty years before it was fixed. We must be patient.
Along with patience, we should also be incredibly excited! Swift represents the future for Objective-C developers and that future looks quite bright. Swift may be new, but it is built on a solid foundation of programming paradigms that are much older than most Objective-C developers (certainly me, anyway!). It is a mix and match of features from the best languages that alone are not approachable enough for the demographic that Apple is trying to reach (for example, Haskell).
In the meantime, I'd like to show some of the more stubborn Objective-C developers the benefits they will receive from adopting Swift, be it now or later. To begin, let's discuss the concept of Optional Values, a new feature in Swift that is not available in Objective-C.
In Objective-C, objects are represented by pointers to their respective instances in memory, an implementation detail rooted in its history of being built on top of C. You will never see a statically initialized Objective-C object. That is, an object whose type is not a pointer. Indeed, even the common id
type is essentially a typedef for void *
.
Keeping that in mind it becomes clear that this can cause all sorts of problems in practice. The compiler and the language itself provide very little enforcement of where an object variable is pointing to because in the end, it's just another C pointer and C isn't designed to be restrictive of the operations you can perform on a pointer. To many, this is considered a feature and operations such as pointer arithmitic are first-class citizens in the C world. Indexing your C arrays using subscripting, myCString[42]
, is more or less shorthand for the pointer arithmitic equivalent: *(myCString+42)
.
Here's another example:
int numbers[] = { 1, 2, 3, 4, 5 };
printf("%d\n", numbers[3]); // Prints: 4
printf("%d\n", *(&numbers[0] + 3)); // Prints: 4
By supporting these sorts of operations, Objective-C sacrifices safety for flexibility. And in many cases, the flexibility is unnecessary. We don't need access to pointers more often than we do. Worse even, we must now keep track of the validity of our objects 100% of the time, even when we as programmers intend on an object to ever be in one state, because we have no way for the compiler to enforce this for us.p
For example, let's take a look at one of the constructors of the NSAttributedString
class.
- (instancetype)initWithString:(NSString *)string attributes:(NSDictionary *)attributes;
From this declaration alone, we cannot be sure what would happen if we pass nil
to either of the arguments of this function. In Objective-C, it's fairly common for this to happen unintentionally because their is no way to distinguish between an object pointer than can be nil
and one that cannot.
And, as many of you probably are aware, this constructor in particular will crash should you pass it nil
to the string
parameter. That's not what we want at all!
Swift provides a solution to these sorts of issues in the form of Optional values. Optionals are a state machine that either represents a value, or nothing. This is fundamentally different from the concept of nil
in Objective-C where it is just another location in memory, albeit an often invalid location. In Swift, optionals provide an abstraction that does not conflate the concept of values existing with low level details like memory addresses. They allow you to write code that mirrors your logic; "does this value exist?" not "is this pointer pointing to a valid location in memory and therefore can I consider this object to exist?". It's simpler.
This also differs from Objective-C because it does not allow nil messaging
. In Objective-C, the runtime will happily accept nil
as the receiver of a message, do nothing, and return 0. There are valid scenarios in which nil messaging
is a useful feature but in general it causes more trouble than it's worth. An entire class of bugs can be attributed to Objective-C allowing nil messaging
. You'll find many Objective-C developers championing nil messaging
and claiming it's exclusion from Swift a step backwards. This is more of a side effect of years spent tailoring the solutions to their problems around the quirks of Objective-C than any inherit correctness in supporting nil messaging
.
Swift's Optional values are somewhat similar to the adhoc Objective-C class, NSNull, whose purpose is denote the absense of a value, just like a Optional. However, not being a language feature and instead part of the Foundation framework, it is incredibly cumbersome and frustrating to use. NSNull is just another Objective-C object and by using it we're forced to now reason about two entirely different concepts of a null value, an unnecessary complication of our code. Victims of the NSJSONSerialization class can surely attest to this. In Swift, we promote the purpose of NSNull to a first-class language citizen.
Let's take a look at the syntax.
Optional value types are denoted by a Type, followed immediately by a question mark:
Int?
String?
The combination of the two parts as a whole are a new type and can be used in a variable declaration just like any non-optional type.
var anIntOrNothing: Int? = 42
in the case above, anIntOrNothing
is an Optional value, that wraps an Int whose value is 42
.
var anIntOrNothing: Int? = nil
Here, anIntOrNothing
does not have a value.
We can test to see if an Optional value has a value by comparing it to nil
.
if anIntOrNothing != nil {
// anIntOrNothing has a value
}
Or we can use the Conditional Let syntax to test if it has a value and, if it does, bind that value to a non-optional variable.
if let anInt = anIntOrNothing {
println("The value is \(anInt)")
} else {
println("anIntOrNothing does not have a value")
}
This can be considered syntatic sugar for the following:
if anIntOrNothing != nil {
let anInt = anIntOrNothing!
println("The value is \(anInt)")
} else {
println("anIntOrNothing does not have a value")
}
The bang operator, !
is used to "unwrap" an Optional value. That is, if the Optional represents a value, then it will return the value. If the Optional value represents the absense of a value, doing so will throw an exception. It's important to always make sure an optional value is not nil
before attempting to unwrap it. It is generally preferable to favor the conditional let syntax described above.
Optionals can also be chained using the ?
operator. For example, the UIViewController
property presentedViewController
is of type UIViewController?
. We can access its properties using optional chaining; the properties are automatically wrapped in an optional themselves in the process.
If we were to access the view
property of the presentedViewController
without optional chaining, first we need to get a valid reference to the view controller itself, if it exists. Then if it does, we can access its view.
if let presentedViewController = self.presentedViewController {
let view: UIView = presentedViewController.view
}
With optional chaining, we can combine these into one step. However, notice the view
types are different. In this case it returns a UIView
wrapped in an optional.
let view: UIView? = self.presentedViewController?.view
This works for property setters as well. If both presentedViewController
and its view
property are valid references, then the view's background color would be set to white as expected. If either optional were nil
, it would immediately stop at the first nil
reference and nothing would happen. This can be seen as a bit of a safer, typed approach to nil messaging
.
self.presentedViewController?.view?.backgroundColor = UIColor.whiteColor()
Swift also supports the concept of Implicitly Unwrapped Optionals. They're denoted with the following type annotation:
TypeName!
And in a variable declaration:
var alwaysAnInt: Int! = nil
You should use an implicitly unwrapped optional when you cannot initialize the value of an optional when whoever owns the variable is created, but during it's use it will never, ever be nil
. The latter is very important because attempting to use the variable without ever assigning it a value will throw an exception.
At first glance, the implicity unwrapped optional may seem to reject the entire purpose of an optional value, and in theory this is true.
In practice, as Mac OS X or iOS developers, the use of implicity unwrapped optionals was included as a necessity. When loading a nib file, the property references aren't set until after the view is loaded, which is no way coupled to when the instance of the owner class is initialized. If the @IBOutlet properties were not optionals, there would be no way to initialize them when the instance is created because the view has not been loaded yet. And if they were regular optionals, we'd have to unwrap them manually everytime we reference them, even though we intended on the connections to always be valid. Implicity unwrapped optionals are a convenience for cases such as these and in your day to day programming in Swift, this is the only time you should use them.
Here's a concrete example
class ViewController : UIViewController {
...
@IBOutlet var textLabel: UILabel! // Connected in ViewController.xib
...
override func viewDidLoad() {
super.viewDidLoad()
self.textLabel.text = NSLocalizedString("Hello World!", comment: "")
// Instead of (if textLabel was not implicity unwrapped):
// self.textLabel!.text = NSLocalizedString("Hello World!", comment: "")
}
}
This also demonstrates an additional benefit of the implicity unwrapped optional: if your forget to make a connection in Interface Builder your app will crash instantly when trying to access one of the unititialized outlets. Despite the nasty word "crash" this is really fantastic in comparison to Objective-C, where many of you have undoubtedly pulled your hair out trying to figure out why a label's text
property silently refuses to change only to realize your forgot to connect the label to its respective property in IB. nil messaging
is a nuisance.
In most other scenarios you should prefer that your instance variables be declared with let
or private var
and be passed as a parameter to an init
constructor. This will come up often when declaring an optional delegate property in Swift.
protocol ControllerDelegate : class {
func controllerDidPerformSomeAction(controller: Controller)
}
class Controller {
private weak var delegate: ControllerDelegate?
init(delegate: ControllerDelegate?) {
self.delegate = delegate
}
func performSomeAction() {
...
self.delegate?.controllerDidPerformSomeAction(self)
}
}
let controller = Controller(delegate: self)
or
let controller = Controller(delegate: nil)
This is preferable to declaring the instance variable as a public var
and assigning it after the fact, as it guarantees the state and validity of the delegate property and ensures it cannot be changed without the controller knowing.
Last, one of the biggest benefits of supporting Optionals is much more straightforward and simple. So simple it can be easily overlooked.
By explicity typing your Optional values, you are implying that every single other non-optional type must always have a value and therefore be valid. Duh, your thinking. But this is serious improvement in comparison to Objective-C and C. Because this implication is built into the language itself, the compiler is able to tell us when we're wrong and enforce this religiously.
Let's revisit the NSAttributedString
example. In Swift, the declaration looks like this:
class NSAttributedString : NSObject {
init(string aString: String, attributes attributes: [NSObject : AnyObject]?)
}
Using this declaration of the NSAttributedString
constructor, we now must pass in a valid string. Passing nil or a String? (optional String type) will result in a compiler error and fail well before the runtime exception we would have gotten in Objective-C.
This is incredibly powerful. A large part of being a programmer is being able to maintain and reason about the state of your code at any given time. In Objective-C that includes having to reason about the validity of every single object in your code. In Swift, that facet is handled by the compiler, which has a far better memory than you do.
Swift's optional values absorb a significant amount of work that is required of every Objective-C programmer. And while the langauge itself isn't quite ready for production use, iOS and Mac developers should be excited about a future that includes such powerful features built into their primary language.
Next time we'll discuss Generics and Typed Collections in Swift. Check back soon.
I would mention optional chaining when talking about nil messaging. Optional chaining is in essence a safer version of nil messaging. In Objective-C
cell.backgroundView.frame
could return a real rectangle or a zeroed out struct. In Swiftcell.backgroundView?.frame
returns either a real rectangle or nil and you can better act on it. It also makes some flows easier like completion callbacks:failure?(error)
instead ofif (failure) { failure(error); }
In you Controller example you should probably use a
weak
and that should have been anif let
, although it could also have beendelegate?.controllerDidPerformSomeAction(self)
😄Swift 1.1 has a new annotation for that last initializer which probably better proves your point on the usefulness: