Vandad Nahavandipoor
http://www.oreilly.com/pub/au/4596
Email: [email protected]
Blog: http://vandadnp.wordpress.com
Skype: vandad.np
So we are all familiar with these keywords, but how do they work and why would we need them? It's not enough, imho, to know that a keyword exists, but one should also know when and how to use them, or sometimes, when not to use them. Let's have a look at these keywords now and i'll give you examples on when and how to use 'em. FYI, i'm on swift 1.2 and the example codes in this edition of swift-weekly may not work if you have an older version of Xcode than the following:
xcrun xcodebuild -version
Xcode 6.3
Build version 6D532l
Let's start by quoting the documentation:
You can apply the autoclosure attribute to a function type that has a parameter type of () and that returns the type of an expression (see Type Attributes). An autoclosure function captures an implicit closure over the specified expression, instead of the expression itself.
Assume that you have a function that takes in a function of type () -> Bool
as a parameter:
func show(#msg: String, ifTrue: () -> Bool){
if ifTrue(){
println(msg)
}
}
and you go ahead and call it:
func example1(){
let age = 200
show(msg: "You are too old") { () -> Bool in
age > 140
}
}
the second parameter is obviously then turned into a closure. can we make this shorter? sure!
func example1(){
let age = 200
show(msg: "You are too old") {age > 140}
}
what if the second parameter could still be a closure but without us having to write the curly brackets around it? that's what @autoclosure
is for:
show(#msg: String, @autoclosure ifTrue: () -> Bool){
if ifTrue(){
println(msg)
}
}
func example1(){
let age = 200
show(msg: "You are too old", ifTrue: age > 140)
}
you might ask: why would i even do all of this instead of changing the parameter type of the show
function from () -> Bool
to Bool
? that's a good question. the answer is that you sometimes might want to allow the programmer to write a closure for it, but, if you find that the closures that the programmers who use your code write usually ends up being a one liner, then you want to add the @autoclosure
keyword to your parameter as to save programmers from having to write the syntax for a closure every time they call your function(s).
Also note that the @autoclosure
keyword applies to a parameter that follows these rules:
- is a clousre
- takes in no parameters
- must return a value that is not a tuple
an example of @autoclosure
in Apple's code for Swift is on the ||
operator on type Bool
:
@inline(__always) func ||<T : BooleanType, U : BooleanType>(lhs: T, rhs: @autoclosure () -> U) -> Bool
quite clever, don't you think?
rules for using @autoclosure
:
- use it to make it easier for programmers to pass in a 1-liner closure with no parameter to your functions
- don't use it if you believe it adds more complexity to your code than it does any good
This one is a gem. suppose that you have a very complex function that has tons of code in it. sometimes, as we've seen in other swift-weekly issues, swift tends to inline functions that may not be even short. this little keyword is to disallow or force that. if you want to force inlining of your functions, you would prefix them with @inline(__always)
and if you want to disallow inlining of your functions, you would prefix the function with @inline(never)
. there is no syntax for inlining sometimes because that's the default behavior:
let's see an example:
func randomInt() -> Int{
return Int(arc4random_uniform(UInt32.max))
}
func example2(){
println(randomInt())
}
if we look at the disassembly of example2()
we will see this:
-[_TtC12swift_weekly14ViewController example2]:
0000000100001550 push rbp ; Objective C Implementation defined at 0x100004308 (instance)
0000000100001551 mov rbp, rsp
0000000100001554 push r14
0000000100001556 push rbx
0000000100001557 sub rsp, 0x10
000000010000155b mov rbx, rdi
000000010000155e mov rax, qword [ds:imp___got__swift_isaMask] ; imp___got__swift_isaMask
0000000100001565 mov rax, qword [ds:rax]
0000000100001568 and rax, qword [ds:rbx]
000000010000156b lea rcx, qword [ds:_OBJC_CLASS_$__TtC12swift_weekly14ViewController] ; _OBJC_CLASS_$__TtC12swift_weekly14ViewController
0000000100001572 xor edi, edi
0000000100001574 cmp rax, rcx
0000000100001577 cmove rdi, rbx
000000010000157b test rdi, rdi
000000010000157e je 0x100001593
0000000100001585 mov edi, 0xffffffff ; argument "upper_bound" for method imp___stubs__arc4random_uniform
000000010000158a call imp___stubs__arc4random_uniform
000000010000158f mov eax, eax
0000000100001591 jmp 0x1000015ad
0000000100001593 mov r14, qword [ds:rax+0x58] ; XREF=-[_TtC12swift_weekly14ViewController example2]+46
00000001000015a7 mov rdi, rbx
00000001000015aa call r14
00000001000015ad mov qword [ss:rbp+var_18], rax ; XREF=-[_TtC12swift_weekly14ViewController example2]+65
00000001000015b1 mov rsi, qword [ds:imp___got___TMdSi] ; imp___got___TMdSi
00000001000015b8 add rsi, 0x8
00000001000015bc lea rdi, qword [ss:rbp+var_18]
00000001000015c0 call imp___stubs___TFSs7printlnU__FQ_T_
00000001000015cd add rsp, 0x10
00000001000015d1 pop rbx
00000001000015d2 pop r14
00000001000015d4 pop rbp
00000001000015d5 ret
Do you see that the code for example2()
includes a call to the internal function imp___stubs__arc4random_uniform
? but that function was in the randomInt()
function, you said with rage! well, swift noticed that the randomInt()
function is not long enough to be a separate entity on its own and it's only been called once in the code so it inlined it to save calling it every time it needs to use it.
now what if we prefix that function with @inline(never)
?
@inline(never) func randomInt() -> Int{
return Int(arc4random_uniform(UInt32.max))
}
let's disassemble it:
-[_TtC12swift_weekly14ViewController example2]:
0000000100001580 push rbp ; Objective C Implementation defined at 0x100004308 (instance)
0000000100001581 mov rbp, rsp
0000000100001584 push r14
0000000100001586 push rbx
0000000100001587 sub rsp, 0x10
000000010000158b mov rbx, rdi
000000010000158e mov rax, qword [ds:imp___got__swift_isaMask] ; imp___got__swift_isaMask
0000000100001595 mov rax, qword [ds:rax]
0000000100001598 and rax, qword [ds:rbx]
000000010000159b lea rcx, qword [ds:_OBJC_CLASS_$__TtC12swift_weekly14ViewController] ; _OBJC_CLASS_$__TtC12swift_weekly14ViewController
00000001000015a2 xor edi, edi
00000001000015a4 cmp rax, rcx
00000001000015a7 cmove rdi, rbx
00000001000015ab test rdi, rdi
00000001000015ae je 0x1000015bc
00000001000015b5 call __TTSf4g___TFC12swift_weekly14ViewController9randomIntfS0_FT_Si
00000001000015ba jmp 0x1000015d6
00000001000015bc mov r14, qword [ds:rax+0x58] ; XREF=-[_TtC12swift_weekly14ViewController example2]+46
00000001000015d0 mov rdi, rbx
00000001000015d3 call r14
00000001000015d6 mov qword [ss:rbp+var_18], rax ; XREF=-[_TtC12swift_weekly14ViewController example2]+58
00000001000015da mov rsi, qword [ds:imp___got___TMdSi] ; imp___got___TMdSi
00000001000015e1 add rsi, 0x8
00000001000015e5 lea rdi, qword [ss:rbp+var_18]
00000001000015e9 call imp___stubs___TFSs7printlnU__FQ_T_
00000001000015f6 add rsp, 0x10
00000001000015fa pop rbx
00000001000015fb pop r14
00000001000015fd pop rbp
00000001000015fe ret
Do you see the call to the __TTSf4g___TFC12swift_weekly14ViewController9randomIntfS0_FT_Si
function? that's the randomInt()
function which we disallowed inlining for. it's working!
rules for using @inline
:
- use
@inline(never)
if your function is quite long and you want to avoid increasing your code segment size (use@inline(never)
) - use
@inline(__always)
if your function is rather small and you would prefer your app ran faster (note: it doesn't make that much of a differenence really) - don't use this keyword if you don't know what inlining of code actually means. read this first.
this keyword is used to indicate that a closure, which is passed to another function, will not live past the host function's lifetime and guarantees that the closure will not be captured by another closure that is not marked with @noescape
. holy hell. so assume this situation:
- i have a closure
- i pass that closure to your function
- your function will then use
dispatch_asynch
and does some network stuff - when the dispatch is finished, it will call my closure asynchronously
that means that the closure that I will pass to you will be used asynchronously somewhere and that it should capture the state of its local variables as to avoid having them getting deallocated under its feet. so @noescape
comes to rescue to guarantee that a noescape
closure that is passed to any function will not be used past the lifetime of the host function.
another advantage of noescape
closures is that they need not use self
to communicate with their host class/struct's member variables or functions, only when the closure is of type () -> ()
. here is an example:
func findSingleCharacterNamesInArray(a: [String], callback: (s: String) -> Void){
for s in a.filter({count($0.utf16) == 1}){
callback(s: s)
}
}
this function goes through an array of strings and for every string of length 1, calls a given closure. we can then use this function like so:
let msg = "Found a single character string"
func example3(){
let names = ["Vandad", "x", "Sara", "Leif", "Y", "Ulla"]
findSingleCharacterNamesInArray(names, { (s) -> Void in
println(self.msg)
})
}
you see how we are forced to use self
in the closure? we can now use @noescape
to avoid having to do that:
func findSingleCharacterNamesInArray(a: [String], @noescape callback: () -> ()){
for s in a.filter({count($0.utf16) == 1}){
callback()
}
}
note: i changed the type of the callback
parameter to () -> ()
to ensure that the caller doesn't have to use self
in the closure to refer to itself.
and then we will use it like so:
let msg = "Found a single character string"
func example3(){
let names = ["Vandad", "x", "Sara", "Leif", "Y", "Ulla"]
findSingleCharacterNamesInArray(names){
println(msg)
}
}
rules for using @noescape
:
- you want to ensure the caller to your method that the closure that they pass to you need not live past the lifetime of your function.
- you want to make it easier for the caller to access their variables in their structs and classes while inside the closure.
- you want the compiler to apply some other small optimizations on your code (god knows what they are!)
We use this keyword before method signatures whose sole purpose is to terminate the execution of the program. this keyword indicates that a method that is marked as noreturn will not return to its caller and the only case where that is true is for a method that throws an exception unconditionally or aborts the execution of the program such as an assertion. here is an example:
@noreturn func example4(){
fatalError("I am a terrible method")
}
override func viewDidLoad() {
super.viewDidLoad()
example4()
if view.alpha == 0xabcdefa{ //this line gets a warning saying "Will never be executed"
}
}
it's interesting that we get the warning "Will never be executed" after our call to a @noreturn
function but is that really what the compiler is doing? I'd expect the compiler not to even compile the code after that call to example4()
so let's disassemble this:
0000000100002e80 push rbp ; Objective C Implementation defined at 0x100008480 (instance)
0000000100002e81 mov rbp, rsp
0000000100002e84 push r14
0000000100002e86 push rbx
0000000100002e87 sub rsp, 0x10
0000000100002e8b mov r14, rdi
0000000100002e8e mov rbx, qword [ds:__TMLC12swift_weekly14ViewController] ; __TMLC12swift_weekly14ViewController
0000000100002ea2 test rbx, rbx
0000000100002ea5 jne 0x100002ebd
0000000100002ea7 lea rdi, qword [ds:_OBJC_CLASS_$__TtC12swift_weekly14ViewController] ; _OBJC_CLASS_$__TtC12swift_weekly14ViewController
0000000100002eae call imp___stubs__swift_getInitializedObjCClass
0000000100002eb3 mov rbx, rax
0000000100002eb6 mov qword [ds:__TMLC12swift_weekly14ViewController], rbx ; __TMLC12swift_weekly14ViewController
0000000100002ebd mov qword [ss:rbp+var_20], r14 ; XREF=-[_TtC12swift_weekly14ViewController viewDidLoad]+37
0000000100002ec1 mov qword [ss:rbp+var_18], rbx
0000000100002ec5 mov rsi, qword [ds:0x1000090a0] ; @selector(viewDidLoad), argument "selector" for method imp___stubs__objc_msgSendSuper2
0000000100002ecc lea rdi, qword [ss:rbp+var_20] ; argument "super" for method imp___stubs__objc_msgSendSuper2
0000000100002ed0 call imp___stubs__objc_msgSendSuper2
0000000100002ed5 mov rax, qword [ds:imp___got__swift_isaMask] ; imp___got__swift_isaMask
0000000100002edc mov rax, qword [ds:rax]
0000000100002edf and rax, qword [ds:r14]
0000000100002ee2 lea rcx, qword [ds:_OBJC_CLASS_$__TtC12swift_weekly14ViewController] ; _OBJC_CLASS_$__TtC12swift_weekly14ViewController
0000000100002ee9 cmp rax, rcx
0000000100002eec jne 0x100002ef8
0000000100002eee test r14, r14
0000000100002ef1 je 0x100002ef8
0000000100002ef3 call __TTSf4d___TFC12swift_weekly14ViewController8example4fS0_FT_T_
0000000100002ef8 mov rdi, r14 ; XREF=-[_TtC12swift_weekly14ViewController viewDidLoad]+108, -[_TtC12swift_weekly14ViewController viewDidLoad]+113
0000000100002efb call qword [ds:rax+0x78]
0000000100002efe nop
do you see the call to __TTSf4d___TFC12swift_weekly14ViewController8example4fS0_FT_T_
? well, it turns out after that, nothing was compiled. good, gooooood!
rules for using @noreturn
:
- use it for methods that unconditionally do an assert that will always fail!
- use it for methods that will certainly not return to their caller, such as those that call the
fatalError()
function. - do not use it for methods that do their work asynchronously or conditionally assert or abort the execution of the program.