Next article: Friday Q&A 2014-07-04: Secrets of Swift's Speed
Previous article: Friday Q&A 2014-06-06: Secrets of dispatch_once
Tags: fridayqna swift
Roughly six hundred million people have asked me to write something about Swift. I'm hoping to write a series of articles about various aspects of it. For today, I want to start things off by exploring a few Swift features that are interesting and unusual from the perspective of Objective-C, and why I think they're going to be good to have.
Explicit Optionals
Swift makes optional values a first-class language construct, and requires that any optional type be explicitly marked as such. For example:
var a: Int
var b: Int?
Here, a
is a plain Int
and it always contains some integer value. b
is an optional Int
and it either contains an integer value, or it contains nothing.
That example is not so special, but it gets more interesting with other types:
var c: String
var d: String?
Just like above, c
is a plain String
and it always contains some string value. d
is an optional String
and it can either contain a string value or it can contain nothing.
Let's look at the equivalent Objective-C declarations:
int e;
NSString *f;
Here, e
is a plain int
, and it always contains some integer value. f
is an NSString *
, which means it can either contain a pointer to an NSString
, or it can contain nil
, which generally means "nothing".
Note how e
is a non-optional type, while f
is effectively optional. The existence of NULL
in C (which is the same as nil
in Objective-C) means that all pointer types in the language are implicitly optional types. If you have a NSString *
, it means "pointer to NSString
, or nil
". If you have a char *
, it means "pointer to char
, or NULL
".
The existence of NULL
in C blows apart the entire static type system when it comes to pointers. Every pointer type is a half truth. Whenever you see a type "pointer to X", there's always an implied "...or NULL
."
NULL
behaves totally differently. You can dereference a pointer, but you can't dereference NULL
. You can send it messages in Objective-C, but the return value is always 0
, even when that makes no sense:
[nil isEqual: nil] // returns NO!
Log a nil
string, and you get (null)
. [nil arrayByAppendingArray: obj]
does not produce a one-element array containing obj
, but rather nil
. Everything has two different behaviors: the regular one, and the nil
or NULL
one.
This is a major problem because our code doesn't behave that way. Sometimes we write our code for "pointer to X or NULL
," but sometimes we just write it for "pointer to X." But there is no way to express this latter type in the language.
To illustrate the problem this poses, consider the following two questions:
- Is
nil
a legal argument to pass toisEqual:
? - is
nil
a legal argument to pass toisEqualToString:
?
I'll give you a moment while you check the documentation.
The answer to the first question is "yes". The docs say: "May be nil
, in which case this method returns NO
."
The answer to the second question is... no? Maybe? The docs don't say. It's generally best to assume that if nil
isn't explicitly allowed, then it's not legal.
Method parameters can sometimes be nil
, and sometimes not. Return values are the same way. Some methods never return nil
, and some do. If a method returns nil
and you pass the return value to a method that doesn't accept nil
, trouble ensues. You have to add checks, and the compiler can't help you with this because, as far as it knows, everything accepts nil
. Even if you can keep on top of everything, the documentation often glosses over the handling of nil
.
Here's another illustrative question: is this code legal?
free(NULL);
In my experience, about 99% of C and Objective-C programmers will say "no". Checks like these are extremely common:
if(ptr)
free(ptr);
But if you check the man page, it turns out to be fine. "The free()
function deallocates the memory allocation pointed to by ptr
. If ptr
is a NULL
pointer, no operation is performed."
If people frequently get something as simple as this wrong, what hope do we have in more complex situations?
Swift approaches this in a consistent manner by making all types non-optional, and allowing any type to be made optional with an additional annotation.
For types that would be pointers in Objective-C, this solves the inconsistencies caused by nil
and NULL
. If a parameter can accept nil
, then it will be declared as an optional type. If it can't, it won't. The compiler can easily check your code to make sure it's doing the right thing. Likewise, if a return value can be nil
, it will be an optional type, making it obvious. In the common case, your types will be non-optional, making it clear that they always hold something.
Because any type can be made optional, it solves some other problems as well. A lot of methods in the frameworks return special sentinel values to indicate the absence of a return value. For example, indexOfObject:
returns NSNotFound
, which is just an alias for NSIntegerMax
. It's easy to forget about this:
NSUInteger index = [array indexOfObject: obj];
[array removeObjectAtIndex: index]; // oops
The equivalent in Swift would return an optional type:
func indexOfObject(obj: T) -> Int?
Not finding the object would be indicated by not returning an integer at all, and the fact that optionals are a different type means the compiler can check everything for you.
Multiple Return Values
Swift allows a function to return multiple values. Or if you look at it in a different way, Swift functions always return one value, but that value can be a tuple which is easily unpacked at the call site.
C and Objective-C support this in two ways, neither of which is great. Functions/methods can return a struct
or a class containing multiple values, or they can use out parameters. Returning a struct
is so unwieldy that it's basically never used except in cases where the struct
is conceptually a single unit, like frame
returning a NSRect
or CGRect
. Out parameters get a lot of use in Cocoa, though, especially for error handling.
The NSError
class and the corresponding NSError **
pattern showed up late in the 10.2 era and quickly became pervasive. Where some languages would throw an exception, Cocoa indicates errors by returning an NSError *
to the caller through a parameter. Code like this is common:
NSError *error;
NSData *data = [NSData dataWithContentsOfFile: file options: 0 error: &error];
if(data == nil) {
// handle error
}
This can get annoying, and since the error is always optional, a lot of code ends up looking like this instead:
NSData *data = [NSData dataWithContentsOfFile: file options: 0 error: NULL];
This is bad, but temptation strikes us all.
Multiple return values present another option besides exceptions and out pointers. In Python with PyObjC, for example, this code would look like:
data, error = NSData.dataWithContentsOfFile_options_error_(file, 0, None)
if not data:
# handle error
It's a bit odd due to the bridging, where you still have to pass None
for the out parameter even though it's being translated into a second return value. A native Python call might look like:
data, error = Data.dataWithContentsOfFile(file, 0)
A Swift version would look almost identical:
let (data, error) = Data.dataWithContentsOfFile(file, 0)
This is ultimately a small thing, but NSError
returns are so common in Cocoa that anything which makes it nicer is welcome. The problem has bothered me enough that I've committed preprocessor crimes against humanity trying to build multiple return values in Objective-C, and I'm glad I don't have to anymore.
(I think a good case could be made that an even better way to handle error returns would be with an either type. In any case, I look forward to exploring the options.)
Generics
This is a huge one. Swift supports generic classes and functions.
This opens up a lot of possibilities. A big one is that container classes can now have a static type which declares, at compile time, what kind of objects they contain. In Objective-C, you lose static type information as soon as objects go into a container:
NSArray *array = @[ @"a", @"b", @"c" ];
We can deal with this, but it's not the best. For one thing, it completely breaks dot syntax:
NSLog(@"%@", array[0].uppercaseString);
This fails to compile because the type of array[0]
is id
and that doesn't work with dot syntax.
For another, it takes some effort to keep track of what's in the thing. This isn't a big deal for local variables where you set up and use it right away. It can be a bit more of a pain for instance variables, and can be really annoying for parameters and return values. When you fail and get the type wrong, the result is a runtime error.
It gets worse for dictionaries, where there are two types to worry about. I have a lot of instance variables with names like _idStringToHamSandwichMap
to help me remember that the keys are NSString
instances and the values are HamSandwich
instances.
In Swift, the types are not Array
and Dictionary
, but Array<String>
and Dictionary<String, HamSandwich>
. When you extract an element from the array, the result is not id
(or Any
/AnyObject
in Swift) but String
. It chains nicely too, so if you ask Dictionary<String, HamSandwich>
for a list of all of its values, the result is a collection type that knows it contains HamSandwich
instances.
Functions can be generic as well. This lets you write functions that operate on any type without losing type information when you go through them. For example, you might write a small helper function in Objective-C to provide a default value for a value when the value is nil
:
id DefaultNil(id first, id fallback) {
if(first) return first;
return fallback;
}
(Let's ignore for a moment the nonstandard ?:
operator which provides this exact behavior.)
This works great, but loses type information, so that this doesn't work:
NSLog(@"%@", DefaultNil(myName, genericName).uppercaseString);
It's possible to make this better by using macros and the nonstandard __typeof__
operator, but it gets frightening fast.
The rough equivalent in Swift would look like:
func DefaultNil(first: AnyObject?, fallback: AnyObject) -> AnyObject {
if first { return first! }
return fallback
}
Swift's stricter static typing would make this a pain to use, much more so than Objective-C, which loses the type info but basically trusts us when we tell it what it is. However, with generics we can solve the problem easily:
func DefaultNil<T>(first: T?, fallback: T) -> T {
if first { return first! }
return fallback
}
This preserves the type so that the return value has the same type of the arguments. Type inference means that you don't even have to tell it which type to use, it just knows from looking at the type of the arguments.
Generics are another example of my preprocessor crimes against humanity trying to hack them into Objective-C, and I'm really glad to have them for real.
Type Inference
Static typing is nice, but it can lead to painful redundancy in code:
NSString *string = @"hello";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle...];
The compiler knows the type of these expressions, so why do we have to repeat the information every time? With Swift, we don't:
let string = "hello"
let alert = UIAlertView(...)
Despite the superficial appearances to languages like JavaScript, don't be fooled: these are statically typed variables, the type just isn't written out as part of the declaration. If there's ever a case where you really want to make sure that a variable is a certain type (and generate a compiler error if the initializer doesn't match), you can still declare it:
let string: String = ThisMustReturnAString()
Verbosity in programming can be good to an extent, to help readability. But Objective-C and Cocoa can take it to an absurd extreme in many cases, and it will be great to have the option to cut down on that.
Class-like Structs
Like Objective-C, Swift has both classes and struct
s. Unlike Objective-C, Swift struct
s can contain methods. Instead of a massive crowd of functions for operating on a struct
, a Swift struct
can just contain that code as methods. CGRectGetMaxX(rect)
could turn into rect.maxX
.
The language also provides a default initializer method if you don't provide one, which saves you the trouble of writing any code for that purpose, and gets rid of ugly functions like CGRectMake
.
Furthermore, struct
s are better integrated into the language. In Objective-C, there's an ugly divide between them. The declaration syntax is different, you can't use them with Cocoa collections without boxing them, and with ARC it becomes extremely inconvenient to have Objective-C objects as struct
members.
In Swift, the declaration syntax is the same. Because the collections use generic types, they have no problem containing struct
s. Object members work without difficulty. They basically become pass-by-value objects rather than a totally different entity.
The result is that small model classes become a lot nicer in Swift. How many times have you used an NSMutableDictionary
with a few fixed keys to represent a bag of related data that really should have been a class? If you're like me, the answer is "too many". That's because making a simple Objective-C class is a bit of a pain. This was especially true pre-ARC, but even with ARC it's annoying to make it nice. You can just toss properties into a class and call it a day, of course:
@interface Person : NSObject
@property (copy) NSString *name;
@property NSUInteger age;
@property (copy) NSString *phoneNumber;
@end
@implementation Person
@end
But now it's mutable, so you have to hope nobody decides to start changing them. And there's no initializer, so you have to set up instances over several lines of code and hope you didn't forget any. If you want a nice immutable model class, you have to manually write out an initializer and keep it in sync with the properties.
With Swift, it's easy:
struct Person {
let name: String
let age: Int
let phoneNumber: String
}
This automatically produces an initializer and instances are all immutable. As a bonus, it should be more efficient as well, since struct
s can potentially be allocated inline rather than requiring allocation on the heap.
Trailing Closures
This is a nifty feature that's simultaneously pointless and really nice.
When the last parameter of a function or method is a closure (i.e. a block), Swift allows you to write the block after the call, outside the parentheses. These two calls are equivalent:
dispatch_async(queue, {
println("dispatch!")
})
dispatch_async(queue) {
println("dispatch!")
}
If the closure is the only parameter, then you don't even need the parentheses:
func MainThread(block: Void -> Void) {
dispatch_async(dispatch_get_main_queue(), block)
}
MainThread {
println("hello from the main thread")
}
This is interesting because it knocks down a visual barrier separating language constructs from library calls. As I discussed way back in 2008, blocks are a great addition to Objective-C because they allow you to write your own control constructs. This showed up in a huge way with GCD, which could provide a ton of useful asynchronous calls that worked a lot like language constructs, but were implemented as library calls. However, with Objective-C, there's still a syntactic difference in how you write calls with blocks compared to how you use built-in language constructs.
For example, Swift (currently, at least) lacks anything like Objective-C's @synchronized
statement. Trailing closures mean that you could implement your own and make it look exactly the way it would look if it were built in:
synchronized(object) {
...
}
If you were doing this in Objective-C, the parentheses couldn't go in the right place and you'd end up with something a little more awkward:
synchronized(object, ^{
...
});
This is ultimately a really small thing, because this still works just fine and isn't difficult to read at all. But at the same time, it's great to be able to arrange the code in a more natural way.
Operator Overloading
I imagine this one will be controversial. Unfortunately, there's a huge group of programmers who have been scarred for life by being exposed to C++'s terrible and terrifying operator overloading culture. When the language designer thinks it's a fine idea to overload the <<
and >>
bitshifting operators for IO, then you know you're in real trouble. I can't blame people for being scared away.
However, I think that if you look beyond C++, the situation is much better. Operator overloading exists in a lot of other languages and doesn't see the kind of crazy abuse that's so common in C++.
Operator overloading is terrible when people use it just so they can have arbitrary symbols instead of words. It's great when people use it so that the traditional operators can be used for their traditional purposes on new types.
Take a simple Objective-C example:
NSNumber *count = ...;
count = @([count intValue] + 1);
You might write code like this if count
is being stored in a dictionary or an array somewhere. Dealing with boxing/unboxing just to increment the number is a real pain. It's also somewhat dangerous, since the NSNumber
value could potentially be beyond what could be stored in an int
. You could write a method to handle that, but it's still not great:
count = [count add: @(1)];
With operator overloading, you can implement +
, +=
, and ++
to do the same thing they would do with a regular int
. The code would then become:
NSNumber *count = ...;
count++;
Of course, generics in Swift mean that NSNumber
itself is much less commonly needed, but there are many other potential good uses for operator overloading. A big (pun intended) one would be a bignum type. Swift has no built-in type for integers of arbitrary size, but you could implement one yourself that handles all the standard arithmetic operators.
One really interesting example is creating autolayout constraints, as demonstrated by SwiftAutoLayout. This uses operator overloading to turn expressions like view1.al_left() == view2.al_right() * 2.0 + 10.0
into NSLayoutConstraint
instances. This is way better than Apple's cool but crazy visual format language.
In addition to overloading existing operators, Swift allows you to define entirely new operators from a limited character set. If you really want to, you can create an operator called <~^~>
and make it do what you want. It will be interesting to see what people do with this, and we'll all have to be really careful not to let this power go to our heads.
Conclusion
Swift is an interesting new language that I'm looking forward to using for real work. It has a lot of features that may seem foreign to someone coming from Objective-C, but I think they ultimately make a lot of sense and will be nice to use.
That's it for today. Come back next time for more goodies. I plan to continue talking about Swift for a while, but topic suggestions are always welcome. If you have something about Swift you'd like to see, great! If it's about something else, I can keep it on file for an appropriate time. Either way, if you have a topic you'd like to see covered, please send it in!
Comments:
But, I think it's the Obj-C dev culture that one is supposed to despise C++ and like Obj-C even if as a language it was obsolete 10 years ago, nevermind in 2014.
This shows in the operating overloading section, where a minor point (iostreams using >> and <<) is turned into a tirade against operator overloading in C++ while ignoring the majority of the cases where it is actually used for "traditional purposes".
Still, it's nice that Apple platforms have access to a blessed modern language. It would have been even nicer for them to recommend a standardised, cross-platform language, but as mentioned before, the cognitive dissonance might have been too great for the developer community, and one transition à la PowerPC to Intel is enough for a lifetime.
Why not go one step further and mention enum's ability to act as Either:
enum Result {
case Success
case Error(NSError)
}
Even so, those particular choices had nothing to do with it being C++ and everything to do with the experience (or lack thereof) of the people making those choices. All of those same mistakes could still be made in Swift.
That said, I absolutely love the fact that you can actually create new operator tokens in Swift.
Here's my attempt to do something similar in C++ (yay macro abuse!) https://github.com/cogwheel/IdOp
If we had that, Swift could also almost be described as C++ without the C.
Peter: I'm not sure I understand what you're after, exactly. Can't you enforce immutability by simply using
let
with all your ivars (or whatever we're calling them in Swift)? Or are you talking about being able to use the same class for as both mutable and immutable?The "OVERLOAD ALL THE THINGS!!" attitude programmers had early on in C++ no longer remains pervasive. Bad uses of operator overloading are only "so common in C++" in the sense that the C++ language and old (but still heavily used) libraries enshrined them long ago. It's not newly overloaded operators that are the problem, it's the fact that people still use old code (again, back to the backwards compatibility issue).
I really don't think it's fair to assert that the C++ community is still somehow in conflict over this. Yes, there are people who are scarred for life, but life has moved on since then.
Sorry for obsessing on this, but it really bothers me to see questionable generalizations trotted out as self-evident truths. I'll leave it alone now :)
autoreleasepool {
// code
}
I do have one nitpick though. (This is the internet after all.) Your example of how a lack of generics can break dot syntax in Obj-C doesn't really work as
- uppercaseString
is a instance method on NSString and not a synthesized property or accessor method. autoreleasepool
was already in there. I was thinking I'd have to build it.
Jake Smith: Interesting! In C++, the two complaints (at least for me) are that the operators already had a long history doing something unrelated, and they have the wrong precedence for IO, meaning that code like
cout << x & y
needs unfortunate parentheses. Perhaps that's not the case in Ruby? Or, again, a different language culture.func synchronized(object: AnyObject, closure: () -> ()) {
objc_sync_enter(object)
closure()
objc_sync_exit(object)
}
Looking forward to posts about the Swift implementation details, Mike! Thanks.
unless
:
func unless<T: LogicValue>(test: T, clause: () -> ()) {
if (!test) { clause() }
}
unless (false) {
println("success!")
}
You mentioned the Python with PyObjC code for multiple returns looks like:
data, error = NSData.dataWithContentsOfFile_options_error_(file, 0, None)
if not data:
# handle error
with the native Python version looking like:
data, error = Data.dataWithContentsOfFile(file, 0)
I'd expect the Python version to be:
try:
data = Data.dataWithContentsOfFile(file, 0)
except IOError as error:
# Handle the error
Python took the C++/Java path of class-based exceptions, and Objective-C took the C path of error codes and string errors. It looks like that will be continued with Swift, unless the libraries start diverging.
A more Pythonic example of multi-return is the Django ORM function to get an existing item or create a new one:
obj, was_created = Blog.objects.get_or_create(title='Arguing about Error Handling')
This can trip up developers, who first write it as:
obj = Blog.objects.get_or_create(title='Arguing about Error Handling')
# obj is now a tuple (Blog object, boolean)
Does the Swift compiler force you to unpack multiple return items?
None
parameter and such. But yes, it's still not entirely natural. And no, Swift does not force you to unpack multiple return items. You'll get the exact same thing in Swift as you do in Python with your example, with the slight improvement that it should be caught at compile time and the error message might (might) help you realize what's gone wrong.
Preston: I don't doubt we'll see plenty of abuse in random third party libraries, but I'm hoping it won't become common in the popular ones, and that it will generally be understood as something to avoid. Think of macro abuse and such in Objective-C now: people do it, sometimes because it's needed, sometimes for no good reason, but it's generally understood as something to avoid if you can.
Did you mean nonstandard here? The ternary operator has been a standard part of C for as long as I remember. It's even in the 2nd edition of the K&R book.
x ?: y
being the same as x ? x : y
except that x
is only evaluated once.In Ruby "<<" always means add to the end unless contextually it looks like numbers are being bit shifted.
Also in Ruby "<<" isn't an operator, it's just a method that quite a few classes implement.
Documentation says that this is for performance reasons, but there's already some overhead inherent in a Swift array, I don't see how putting restrictions on setting elements would cause significant additional overhead.
let
and var
, but that made sense once I understood that they were struct
s and what that entailed.
The business where constant arrays still have mutable elements is crazy. Fortunately, I'm pretty sure I heard somewhere that this will be changed.
While you can access most things from the surrounding lexical environment inside the closure, you can't access loop or labeled loops to use "break" or "continue". In the closure body you are not in a loop.
There is another gotcha from using the same syntax for syntax as for closures. Unlike in C/C++/ObjC you can't just casually write a set of braces to delimit a scope for a temporary variable, or just to make code clearer:
let t = ...
...
foo()
...
{
let x = t + 10
if x < 100 {
...
}
}
If you do this in Swift you get something like "closure not used".
But if you do something like...
do {
let x = t + 10
if x < 100 {
...
}
} while false
.. then it is happy with you. Because *those* { } are just syntax, not a closure.
{
...
}()
I am curious why we can't call Obj-C++ from Swift, is there a technical reason against it?
Will this be added/fixed later on?
(Sure we can write wrappers, but I'd really like to know why it is not supported)
Clemens Gruber: There are really two different kinds of "call Obj-C++ from Swift": 1) accessing Objective-C classes, extern "C" functions, constants, etc. that just happen to live within an Objective-C++ file, and 2) accessing C++ classes, functions, templates, etc.
Taking the second one first, the C++ object model is so complex that it's impractical to automatically bridge. Objective-C++ has a ton of dark corner cases because of C++, and this comes from bolting on an extremely simple set of extensions to the language and otherwise leveraging the existing compiler. Getting Swift to deal with it would be a huge task, tantamount to implementing a C++ compiler within the Swift compiler. For a concrete example, consider
std::vector
, which is implemented entirely in the header file because it's a templated class and templated classes in C++ are often frightening like this. In order to instantiate a std::vector
, you have to parse all of its code, specialize it for the type you want it to contain, then turn that all into machine code. Which is to say, you need a C++ compiler. Compare with something like instantiating an NSArray
, which exists entirely in a library with a simple interface, so it's just a few calls to objc_msgSend
with some obvious parameters. You could skip over anything with header implementations and only allow bridging to C++ classes with prototypes in the headers, but given how common these header-implementations are in C++, you'd severely limit your universe in strange and confusing ways, like not being able to call any function that takes a std::vector
or any other STL container.
For the first one, bridging to stuff with C linkage compiled in Objective-C++ files... I actually expect that works now, as long as your headers are compatible with plain Objective-C. Swift shouldn't care about where your implementation lives or how it's built, it'll only care about the headers. It won't be able to deal with C++ constructs in the headers, because that opens the whole can of worms above, but as long as you can present a clean Objective-C interface, an Objective-C++ implementation should be fine. If it doesn't work now for some reason, I would expect it to be made to work before too long, as there shouldn't be any fundamental obstacle.
mike: I've been trying to blog some about interop with C (the docs are extremely thin on that), see below. Perhaps an overview of the various CMutableVoidPointer/COpaquePointer/UnsafePointer/etc would be good?
http://www.russbishop.net/swift-manual-retain-release
http://www.russbishop.net/swift-withunsafepointertoelements
http://www.russbishop.net/swift-pointers-everywhere
Why do all blog platforms suck?
Mike thanks for the article, much appreciated. I'm struggling with the concept of... enums with methods. Sounds very, very odd.
enum
for more than just defining integer constants, and how best to use them.
func synchronized<T>(object: AnyObject, closure: () -> T) -> T {
objc_sync_enter(object)
let val: T = closure()
objc_sync_exit(object)
return val
}
func synchronizedWith<A: AnyObject, T>(object: A, closure: (value: A) -> T) -> T {
objc_sync_enter(object)
let val: T = closure(value: object)
objc_sync_exit(object)
return val
}
http://ijoshsmith.com/2014/07/05/custom-threading-operator-in-swift/
Thanks!
Nicely explain new feature like optional, multi value.
i would like if you share more article about swift.
we don't do if(ptr) free(ptr); because we are afraid it might be NULL and break something; Usually AFTER freeing an element you set it to NULL so you don't free it again by mistake later on. The issue is not freeing a NULL pointer but double-freeing a valid one. Objects alleviate this immensely.
At last I understood this optionals stuff.
Comments RSS feed for this page
Add your thoughts, post a comment:
Spam and off-topic posts will be deleted without notice. Culprits may be publicly humiliated at my sole discretion.