Swift Tips
来源:互联网 发布:windows 10没远程桌面 编辑:程序博客网 时间:2024/06/06 12:57
转载:转自猫神100个Swift必备Tips,onevcat
Selector
@selector
是 Objective-C 时代的一个关键字,它可以将一个方法转换并赋值给一个 SEL
类型,它的表现很类似一个动态的函数指针。在 Objective-C 时 selector 非常常用,从设定 target-action,到自举询问是否响应某个方法,再到指定接受通知时需要调用的方法等等,都是由 selector 来负责的。在 Objective-C 里生成一个 selector 的方法一般是这个样子的:
-
(
void
)
callMe
{
//...
}
-
(
void
)
callMeWithParam:
(
id
)
obj
{
//...
}
SEL
someMethod
=
@selector
(
callMe
);
SEL
anotherMethod
=
@selector
(
callMeWithParam
:
);
// 或者也可以使用 NSSelectorFromString
// SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:");
一般为了方便,很多人会选择使用 @selector
,但是如果要追求灵活的话,可能会更愿意使用 NSSelectorFromString
的版本 – 因为我们可以在运行时动态生成字符串,从而通过方法的名字来调用到对应的方法。
在 Swift 中没有 @selector
了,我们要生成一个 selector 的话现在只能使用字符串。Swift 里对应原来 SEL
的类型是一个叫做Selector
的结构体,它提供了一个接受字符串的初始化方法。像上面的两个例子在 Swift 中等效的写法是:
func
callMe
()
{
//...
}
func
callMeWithParam
(
obj
:
AnyObject
!
)
{
//...
}
let
someMethod
=
Selector
(
"callMe"
)
let
anotherMethod
=
Selector
(
"callMeWithParam:"
)
和 Objective-C 时一样,记得在 callMeWithParam
后面加上冒号 (:),这才是完整的方法名字。多个参数的方法名也和原来类似,是这个样子:
func
turnByAngle
(
theAngle
:
Int
,
speed
:
Float
)
{
//...
}
let
method
=
Selector
(
"turnByAngle:speed:"
)
另外,因为 Selector
类型实现了 StringLiteralConvertible
,因此我们甚至可以不使用它的初始化方法,而直接用一个字符串进行赋值,就可以完成创建了。
最后需要注意的是,selector 其实是 Objective-C runtime 的概念,如果你的 selector 对应的方法只在 Swift 中可见的话 (也就是说它是一个 Swift 中的 private 方法),在调用这个 selector 时你会遇到一个 unrecognized selector 错误:
这是错误代码
private
func
callMe
()
{
//...
}
NSTimer
.
scheduledTimerWithTimeInterval
(
1
,
target
:
self
,
selector
:
"callMe"
,
userInfo
:
nil
,
repeats
:
true
)
正确的做法是在 private
前面加上 @objc
关键字,这样运行时就能找到对应的方法了。
@
objc
private
func
callMe
()
{
//...
}
NSTimer
.
scheduledTimerWithTimeInterval
(
1
,
target
:
self
,
selector
:
"callMe"
,
userInfo
:
nil
,
repeats
:
true
)
另外,如果方法的第一个参数有外部变量的话,在通过字符串生成 Selector
时还有一个约定,那就是在方法名和第一个外部参数之间加上 with
:
func
aMethod
(
external
paramName
:
AnyObject
!
)
{
...
}
想获取对应的获取 Selector
,应该这么写:
let
s
=
Selector
(
"aMethodWithExternal:"
)
Sequence
Swift 的 for...in
可以用在所有实现了 SequenceType
的类型上,而为了实现 SequenceType
你首先需要实现一个 GeneratorType
。比如一个实现了反向的 generator
和 sequence
可以这么写:
// 先定义一个实现了 GeneratorType protocol 的类型
// GeneratorType 需要指定一个 typealias Element
// 以及提供一个返回 Element? 的方法 next()
class
ReverseGenerator:
GeneratorType
{
typealias
Element
=
Int
var
counter:
Element
init
<
T
>
(
array:
[
T
])
{
self
.
counter
=
array
.
count
-
1
}
init
(
start:
Int
)
{
self
.
counter
=
start
}
func
next
()
->
Element
?
{
return
self
.
counter
<
0
?
nil
:
counter
--
}
}
// 然后我们来定义 SequenceType
// 和 GeneratorType 很类似,不过换成指定一个 typealias Generator
// 以及提供一个返回 Generator? 的方法 generate()
struct
ReverseSequence
<
T
>:
SequenceType
{
var
array:
[
T
]
init
(
array:
[
T
])
{
self
.
array
=
array
}
typealias
Generator
=
ReverseGenerator
func
generate
()
->
Generator
{
return
ReverseGenerator
(
array:
array
)
}
}
let
arr
=
[
0
,
1
,
2
,
3
,
4
]
// 对 SequenceType 可以使用 for...in 来循环访问
for
i
in
ReverseSequence
(
array:
arr
)
{
println
(
"Index \(i) is \(arr[i])"
)
}
输出为
Index
4
is
4
Index
3
is
3
Index
2
is
2
Index
1
is
1
Index
0
is
0
如果我们想要深究 for...in
这样的方法到底做了什么的话,如果我们将其展开,大概会是下面这个样子:
var
g
=
array
.
generate
()
while
let
obj
=
g
.
next
()
{
println
(
obj
)
}
顺便你可以免费得到的收益是你可以使用像 map
, filter
和 reduce
这些方法,因为它们都有对应 SequenceType
的版本:
@autoclosure 和 ??
Apple 为了推广和介绍 Swift,破天荒地为这门语言开设了一个博客(当然我觉着是因为 Swift 坑太多需要一个地方来集中解释)。其中有一篇提到了一个叫做 @autoclosure
的关键词。
@autoclosure
可以说是 Apple 的一个非常神奇的创造,因为这更多地是像在 “hack” 这门语言。简单说,@autoclosure
做的事情就是把一句表达式自动地封装成一个闭包 (closure)。这样有时候在语法上看起来就会非常漂亮。
比如我们有一个方法接受一个闭包,当闭包执行的结果为 true
的时候进行打印:
func
logIfTrue
(
predicate
:
()
->
Bool
)
{
if
predicate
()
{
println
(
"True"
)
}
}
在调用的时候,我们需要写这样的代码
logIfTrue
({
return
2
>
1
})
当然,在 Swift 中对闭包的用法可以进行一些简化,在这种情况下我们可以省略掉 return
,写成:
logIfTrue
({
2
>
1
})
还可以更近一步,因为这个闭包是最后一个参数,所以可以使用尾随闭包 (trailing closure) 的方式把大括号拿出来,然后省略括号,变成:
logIfTrue
{
2
>
1
}
但是不管那种方式,要么是书写起来十分麻烦,要么是表达上不太清晰,看起来都让人生气。于是 @autoclosure
登场了。我们可以改换方法参数,在参数名前面加上 @autoclosure
关键字:
func
logIfTrue
(
@
autoclosure
predicate
:
()
->
Bool
)
{
if
predicate
()
{
println
(
"True"
)
}
}
这时候我们就可以直接写:
logIfTrue
(
2
>
1
)
来进行调用了,Swift 将会吧 2 > 1
这个表达式自动转换为 () -> Bool
。这样我们就得到了一个写法简单,表意清楚的式子。
在 Swift 中,有一个非常有用的操作符,可以用来快速地对 nil
进行条件判断,那就是 ??
。这个操作符可以判断输入并在当左侧的值是非 nil
的 Optional 值时返回其 value,当左侧是 nil
时返回右侧的值,比如:
var
level
:
Int
?
var
startLevel
=
1
var
currentLevel
=
level
??
startLevel
在这个例子中我们没有设置过 level
,因此最后 startLevel
被赋值给了 currentLevel
。如果我们充满好奇心地点进 ??
的定义,可以看到??
有两种版本:
func
??<
T
>
(
optional
:
T
?
,
@
autoclosure
defaultValue
:
()
->
T
?
)
->
T
?
func
??<
T
>
(
optional
:
T
?
,
@
autoclosure
defaultValue
:
()
->
T
)
->
T
在这里我们的输入满足的是后者,虽然表面上看 startLevel
只是一个 Int
,但是其实在使用时它被自动封装成了一个 () -> Int
,有了这个提示,我们不妨来猜测一下 ??
的实现吧:
func
??<
T
>
(
optional
:
T
?
,
@
autoclosure
defaultValue
:
()
->
T
)
->
T
{
switch
optional
{
case
.
Some
(
let
value
):
return
value
case
.
None
:
return
defaultValue
()
}
}
可能你会有疑问,为什么这里要使用 autoclosure
,直接接受 T
作为参数并返回不行么?这正是 autoclosure
的一个最值得称赞的地方。如果我们直接使用 T
,那么就意味着在 ??
操作符真正取值之前,我们就必须准备好一个默认值,这个默认值的准备和计算是会消耗性能的。但是其实要是 optional
不是 nil
的话,我们是完全不需要这个默认值,而会直接返回 optional
解包后的值。这样一来,默认值就白白准备了,这样的开销是完全可以避免的,方法就是将默认值的计算推迟到 optional
判定为 nil
之后。
就这样,我们可以巧妙地绕过条件判断和强制转换,以很优雅的写法处理对 Optional
及默认值的取值了。最后要提一句的是,@autoclosure
并不支持带有输入参数的写法,也就是说只有形如 () -> T
的参数才能使用这个特性进行简化。另外因为调用者往往很容易忽视 @autoclosure
这个特性,所以在写接受 @autoclosure
的方法时还请特别小心,如果在容易产生歧义或者误解的时候,还是使用完整的闭包写法会比较好。
练习
在 Swift 中,其实 &&
和 ||
这两个操作符里也用到了 @autoclosure
。作为练习,不妨打开 Playground,试试看怎么实现这两个操作符吧?
Optional Chaining
使用 Optional Chaining 可以让我们摆脱很多不必要的判断和取值,但是在使用的时候需要小心陷阱。
因为 Optional Chaining 是随时都可能提前返回 nil
的,所以使用 Optional Chaining 所得到的东西其实都是 Optional 的。比如有下面的一段代码:
class
Toy
{
let
name
:
String
init
(
name
:
String
)
{
self
.
name
=
name
}
}
class
Pet
{
var
toy
:
Toy
?
}
class
Child
{
var
pet
:
Pet
?
}
在实际使用中,我们想要知道小明的宠物的玩具的名字的时候,可以通过下面的 Optional Chaining 拿到:
let
toyName
=
xiaoming
.
pet
?
.
toy
?
.
name
注意虽然我们最后访问的是 name
,并且在 Toy
的定义中 name
是被定义为一个确定的 String
而非 String?
的,但是我们拿到的toyName
其实还是一个 String?
的类型。这是由于在 Optional Chaining 中我们在任意一个 ?.
的时候都可能遇到 nil
而提前返回,这个时候当然就只能拿到 nil
了。
在实际的使用中,我们大多数情况下可能更希望使用 Optional Binding 来直接取值的这样的代码:
if
let
toyName
=
xiaoming
.
pet
?
.
toy
?
.
name
{
// 太好了,小明既有宠物,而且宠物还正好有个玩具
}
可能单独拿出来看会很清楚,但是只要稍微和其他特性结合一下,事情就会变得复杂起来。来看看下面的例子:
extension
Toy
{
func
play
()
{
//...
}
}
我们为 Toy
定义了一个扩展,以及一个玩玩具的 play()
方法。还是拿小明举例子,要是有玩具的话,就玩之:
xiaoming
.
pet
?
.
toy
?
.
play
()
除了小明也许我们还有小红小李小张等等..在这种时候我们会想要把这一串调用抽象出来,做一个闭包方便使用。传入一个 Child
对象,如果小朋友有宠物并且宠物有玩具的话,就去玩。于是很可能你会写出这样的代码:
这是错误代码
let
playClosure
=
{(
child
:
Child
)
->
()
in
child
.
pet
?
.
toy
?
.
play
()}
你会发现这么表意清晰的代码居然无法编译!
问题在于对于 play()
的调用上。定义的时候我们没有写 play()
的返回,这表示这个方法返回 Void
(或者写作一对小括号 ()
,它们是等价的)。但是正如上所说,经过 Optional Chaining 以后我们得到的是一个 Optional 的结果。也就是说,我们最后得到的应该是这样一个 closure:
let
playClosure
=
{(
child
:
Child
)
->
()
?
in
child
.
pet
?
.
toy
?
.
play
()}
这样调用的返回将是一个 ()?
(或者写成 Void?
会更清楚一些),虽然看起来挺奇怪的,但这就是事实。使用的时候我们可以通过 Optional Binding 来判定方法是否调用成功:
if
let
result
:
()
=
playClosure
(
xiaoming
)
{
println
(
"好开心~"
)
}
else
{
println
(
"没有玩具可以玩 :("
)
}
func 的参数修饰
在声明一个 Swift 的方法的时候,我们一般不去指定参数前面的修饰符,而是直接声明参数:
func
incrementor
(
variable
:
Int
)
->
Int
{
return
variable
+
1
}
这个方法接受一个 Int
的输入,然后通过将这个输入加 1,返回一个新的比输入大 1 的 Int
。嘛,就是一个简单的 +1器。
有些同学在大学的 C 程序设计里可能学过像 ++
这样的“自增”运算符,再加上做了不少关于“判断一个数被各种前置 ++
和后置 ++
折磨后的输出是什么”的考试题,所以之后写代码时也会不自觉地喜欢带上这种风格。于是同样的功能可能会写出类似这样的方法:
这是错误代码
func
incrementor
(
variable
:
Int
)
->
Int
{
return
++
variable
}
残念..编译错误。为什么在 Swift 里这样都不行呢?答案是因为 Swift 其实是一门讨厌变化的语言。所有有可能的地方,都被默认认为是不可变的,也就是用 let
进行声明的。这样不仅可以确保安全,也能在编译器的性能优化上更有作为。在方法的参数上也是如此,我们不写修饰符的话,默认情况下所有参数都是 let
的,上面的代码等效为:
func
incrementor
(
let
variable
:
Int
)
->
Int
{
return
++
variable
}
let
的参数,不能重新赋值这是理所当然的。要让这个方法正确编译,我们需要做的改动是将 let
改为 var
:
func
incrementor
(
var
variable
:
Int
)
->
Int
{
return
++
variable
}
现在我们的 +1器 又可以正确工作了:
var
luckyNumber
=
7
let
newNumber
=
incrementor
(
luckyNumber
)
// newNumber = 8
println
(
luckyNumber
)
// luckyNumber 还是 7
正如上面的例子,我们将参数写作 var
后,通过调用返回的值是正确的,而 luckyNumber
还是保持了原来的值。这说明 var
只是在方法内部作用,而不直接影响输入的值。有些时候我们会希望在方法内部直接修改输入的值,这时候我们可以使用 inout
来对参数进行修饰:
func
incrementor
(
inout
variable
:
Int
)
{
++
variable
}
因为在函数内部就更改了值,所以也不需要返回了。调用也要改变为相应的形式,在前面加上 &
符号:
var
luckyNumber
=
7
incrementor
(
&
luckyNumber
)
println
(
luckyNumber
)
// luckyNumber = 8
最后,要注意的是参数的修饰是具有传递限制的,就是说对于跨越层级的调用,我们需要保证同一参数的修饰是统一的。举个例子,比如我们想扩展一下上面的方法,实现一个可以累加任意数字的 +N器 的话,可以写成这样:
func
makeIncrementor
(
addNumber
:
Int
)
->
((
inout
Int
)
->
())
{
func
incrementor
(
inout
variable
:
Int
)
->
()
{
variable
+=
addNumber
;
}
return
incrementor
;
}
外层的 makeIncrementor
的返回里也需要在参数的类型前面明确指出修饰词,以符合内部的定义,否则将无法编译通过。
Selector
@selector
是 Objective-C 时代的一个关键字,它可以将一个方法转换并赋值给一个 SEL
类型,它的表现很类似一个动态的函数指针。在 Objective-C 时 selector 非常常用,从设定 target-action,到自举询问是否响应某个方法,再到指定接受通知时需要调用的方法等等,都是由 selector 来负责的。在 Objective-C 里生成一个 selector 的方法一般是这个样子的:
-
(
void
)
callMe
{
//...
}
-
(
void
)
callMeWithParam:
(
id
)
obj
{
//...
}
SEL
someMethod
=
@selector
(
callMe
);
SEL
anotherMethod
=
@selector
(
callMeWithParam
:
);
// 或者也可以使用 NSSelectorFromString
// SEL someMethod = NSSelectorFromString(@"callMe");
// SEL anotherMethod = NSSelectorFromString(@"callMeWithParam:");
一般为了方便,很多人会选择使用 @selector
,但是如果要追求灵活的话,可能会更愿意使用 NSSelectorFromString
的版本 – 因为我们可以在运行时动态生成字符串,从而通过方法的名字来调用到对应的方法。
在 Swift 中没有 @selector
了,我们要生成一个 selector 的话现在只能使用字符串。Swift 里对应原来 SEL
的类型是一个叫做Selector
的结构体,它提供了一个接受字符串的初始化方法。像上面的两个例子在 Swift 中等效的写法是:
func
callMe
()
{
//...
}
func
callMeWithParam
(
obj
:
AnyObject
!
)
{
//...
}
let
someMethod
=
Selector
(
"callMe"
)
let
anotherMethod
=
Selector
(
"callMeWithParam:"
)
和 Objective-C 时一样,记得在 callMeWithParam
后面加上冒号 (:),这才是完整的方法名字。多个参数的方法名也和原来类似,是这个样子:
func
turnByAngle
(
theAngle
:
Int
,
speed
:
Float
)
{
//...
}
let
method
=
Selector
(
"turnByAngle:speed:"
)
另外,因为 Selector
类型实现了 StringLiteralConvertible
,因此我们甚至可以不使用它的初始化方法,而直接用一个字符串进行赋值,就可以完成创建了。
最后需要注意的是,selector 其实是 Objective-C runtime 的概念,如果你的 selector 对应的方法只在 Swift 中可见的话 (也就是说它是一个 Swift 中的 private 方法),在调用这个 selector 时你会遇到一个 unrecognized selector 错误:
这是错误代码
private
func
callMe
()
{
//...
}
NSTimer
.
scheduledTimerWithTimeInterval
(
1
,
target
:
self
,
selector
:
"callMe"
,
userInfo
:
nil
,
repeats
:
true
)
正确的做法是在 private
前面加上 @objc
关键字,这样运行时就能找到对应的方法了。
@
objc
private
func
callMe
()
{
//...
}
NSTimer
.
scheduledTimerWithTimeInterval
(
1
,
target
:
self
,
selector
:
"callMe"
,
userInfo
:
nil
,
repeats
:
true
)
另外,如果方法的第一个参数有外部变量的话,在通过字符串生成 Selector
时还有一个约定,那就是在方法名和第一个外部参数之间加上 with
:
func
aMethod
(
external
paramName
:
AnyObject
!
)
{
...
}
想获取对应的获取 Selector
,应该这么写:
let
s
=
Selector
(
"aMethodWithExternal:"
)
Sequence
Swift 的 for...in
可以用在所有实现了 SequenceType
的类型上,而为了实现 SequenceType
你首先需要实现一个 GeneratorType
。比如一个实现了反向的 generator
和 sequence
可以这么写:
// 先定义一个实现了 GeneratorType protocol 的类型
// GeneratorType 需要指定一个 typealias Element
// 以及提供一个返回 Element? 的方法 next()
class
ReverseGenerator:
GeneratorType
{
typealias
Element
=
Int
var
counter:
Element
init
<
T
>
(
array:
[
T
])
{
self
.
counter
=
array
.
count
-
1
}
init
(
start:
Int
)
{
self
.
counter
=
start
}
func
next
()
->
Element
?
{
return
self
.
counter
<
0
?
nil
:
counter
--
}
}
// 然后我们来定义 SequenceType
// 和 GeneratorType 很类似,不过换成指定一个 typealias Generator
// 以及提供一个返回 Generator? 的方法 generate()
struct
ReverseSequence
<
T
>:
SequenceType
{
var
array:
[
T
]
init
(
array:
[
T
])
{
self
.
array
=
array
}
typealias
Generator
=
ReverseGenerator
func
generate
()
->
Generator
{
return
ReverseGenerator
(
array:
array
)
}
}
let
arr
=
[
0
,
1
,
2
,
3
,
4
]
// 对 SequenceType 可以使用 for...in 来循环访问
for
i
in
ReverseSequence
(
array:
arr
)
{
println
(
"Index \(i) is \(arr[i])"
)
}
输出为
Index
4
is
4
Index
3
is
3
Index
2
is
2
Index
1
is
1
Index
0
is
0
如果我们想要深究 for...in
这样的方法到底做了什么的话,如果我们将其展开,大概会是下面这个样子:
var
g
=
array
.
generate
()
while
let
obj
=
g
.
next
()
{
println
(
obj
)
}
顺便你可以免费得到的收益是你可以使用像 map
, filter
和 reduce
这些方法,因为它们都有对应 SequenceType
的版本:
@autoclosure 和 ??
Apple 为了推广和介绍 Swift,破天荒地为这门语言开设了一个博客(当然我觉着是因为 Swift 坑太多需要一个地方来集中解释)。其中有一篇提到了一个叫做 @autoclosure
的关键词。
@autoclosure
可以说是 Apple 的一个非常神奇的创造,因为这更多地是像在 “hack” 这门语言。简单说,@autoclosure
做的事情就是把一句表达式自动地封装成一个闭包 (closure)。这样有时候在语法上看起来就会非常漂亮。
比如我们有一个方法接受一个闭包,当闭包执行的结果为 true
的时候进行打印:
func
logIfTrue
(
predicate
:
()
->
Bool
)
{
if
predicate
()
{
println
(
"True"
)
}
}
在调用的时候,我们需要写这样的代码
logIfTrue
({
return
2
>
1
})
当然,在 Swift 中对闭包的用法可以进行一些简化,在这种情况下我们可以省略掉 return
,写成:
logIfTrue
({
2
>
1
})
还可以更近一步,因为这个闭包是最后一个参数,所以可以使用尾随闭包 (trailing closure) 的方式把大括号拿出来,然后省略括号,变成:
logIfTrue
{
2
>
1
}
但是不管那种方式,要么是书写起来十分麻烦,要么是表达上不太清晰,看起来都让人生气。于是 @autoclosure
登场了。我们可以改换方法参数,在参数名前面加上 @autoclosure
关键字:
func
logIfTrue
(
@
autoclosure
predicate
:
()
->
Bool
)
{
if
predicate
()
{
println
(
"True"
)
}
}
这时候我们就可以直接写:
logIfTrue
(
2
>
1
)
来进行调用了,Swift 将会吧 2 > 1
这个表达式自动转换为 () -> Bool
。这样我们就得到了一个写法简单,表意清楚的式子。
在 Swift 中,有一个非常有用的操作符,可以用来快速地对 nil
进行条件判断,那就是 ??
。这个操作符可以判断输入并在当左侧的值是非 nil
的 Optional 值时返回其 value,当左侧是 nil
时返回右侧的值,比如:
var
level
:
Int
?
var
startLevel
=
1
var
currentLevel
=
level
??
startLevel
在这个例子中我们没有设置过 level
,因此最后 startLevel
被赋值给了 currentLevel
。如果我们充满好奇心地点进 ??
的定义,可以看到??
有两种版本:
func
??<
T
>
(
optional
:
T
?
,
@
autoclosure
defaultValue
:
()
->
T
?
)
->
T
?
func
??<
T
>
(
optional
:
T
?
,
@
autoclosure
defaultValue
:
()
->
T
)
->
T
在这里我们的输入满足的是后者,虽然表面上看 startLevel
只是一个 Int
,但是其实在使用时它被自动封装成了一个 () -> Int
,有了这个提示,我们不妨来猜测一下 ??
的实现吧:
func
??<
T
>
(
optional
:
T
?
,
@
autoclosure
defaultValue
:
()
->
T
)
->
T
{
switch
optional
{
case
.
Some
(
let
value
):
return
value
case
.
None
:
return
defaultValue
()
}
}
可能你会有疑问,为什么这里要使用 autoclosure
,直接接受 T
作为参数并返回不行么?这正是 autoclosure
的一个最值得称赞的地方。如果我们直接使用 T
,那么就意味着在 ??
操作符真正取值之前,我们就必须准备好一个默认值,这个默认值的准备和计算是会消耗性能的。但是其实要是 optional
不是 nil
的话,我们是完全不需要这个默认值,而会直接返回 optional
解包后的值。这样一来,默认值就白白准备了,这样的开销是完全可以避免的,方法就是将默认值的计算推迟到 optional
判定为 nil
之后。
就这样,我们可以巧妙地绕过条件判断和强制转换,以很优雅的写法处理对 Optional
及默认值的取值了。最后要提一句的是,@autoclosure
并不支持带有输入参数的写法,也就是说只有形如 () -> T
的参数才能使用这个特性进行简化。另外因为调用者往往很容易忽视 @autoclosure
这个特性,所以在写接受 @autoclosure
的方法时还请特别小心,如果在容易产生歧义或者误解的时候,还是使用完整的闭包写法会比较好。
练习
在 Swift 中,其实 &&
和 ||
这两个操作符里也用到了 @autoclosure
。作为练习,不妨打开 Playground,试试看怎么实现这两个操作符吧?
Optional Chaining
使用 Optional Chaining 可以让我们摆脱很多不必要的判断和取值,但是在使用的时候需要小心陷阱。
因为 Optional Chaining 是随时都可能提前返回 nil
的,所以使用 Optional Chaining 所得到的东西其实都是 Optional 的。比如有下面的一段代码:
class
Toy
{
let
name
:
String
init
(
name
:
String
)
{
self
.
name
=
name
}
}
class
Pet
{
var
toy
:
Toy
?
}
class
Child
{
var
pet
:
Pet
?
}
在实际使用中,我们想要知道小明的宠物的玩具的名字的时候,可以通过下面的 Optional Chaining 拿到:
let
toyName
=
xiaoming
.
pet
?
.
toy
?
.
name
注意虽然我们最后访问的是 name
,并且在 Toy
的定义中 name
是被定义为一个确定的 String
而非 String?
的,但是我们拿到的toyName
其实还是一个 String?
的类型。这是由于在 Optional Chaining 中我们在任意一个 ?.
的时候都可能遇到 nil
而提前返回,这个时候当然就只能拿到 nil
了。
在实际的使用中,我们大多数情况下可能更希望使用 Optional Binding 来直接取值的这样的代码:
if
let
toyName
=
xiaoming
.
pet
?
.
toy
?
.
name
{
// 太好了,小明既有宠物,而且宠物还正好有个玩具
}
可能单独拿出来看会很清楚,但是只要稍微和其他特性结合一下,事情就会变得复杂起来。来看看下面的例子:
extension
Toy
{
func
play
()
{
//...
}
}
我们为 Toy
定义了一个扩展,以及一个玩玩具的 play()
方法。还是拿小明举例子,要是有玩具的话,就玩之:
xiaoming
.
pet
?
.
toy
?
.
play
()
除了小明也许我们还有小红小李小张等等..在这种时候我们会想要把这一串调用抽象出来,做一个闭包方便使用。传入一个 Child
对象,如果小朋友有宠物并且宠物有玩具的话,就去玩。于是很可能你会写出这样的代码:
这是错误代码
let
playClosure
=
{(
child
:
Child
)
->
()
in
child
.
pet
?
.
toy
?
.
play
()}
你会发现这么表意清晰的代码居然无法编译!
问题在于对于 play()
的调用上。定义的时候我们没有写 play()
的返回,这表示这个方法返回 Void
(或者写作一对小括号 ()
,它们是等价的)。但是正如上所说,经过 Optional Chaining 以后我们得到的是一个 Optional 的结果。也就是说,我们最后得到的应该是这样一个 closure:
let
playClosure
=
{(
child
:
Child
)
->
()
?
in
child
.
pet
?
.
toy
?
.
play
()}
这样调用的返回将是一个 ()?
(或者写成 Void?
会更清楚一些),虽然看起来挺奇怪的,但这就是事实。使用的时候我们可以通过 Optional Binding 来判定方法是否调用成功:
if
let
result
:
()
=
playClosure
(
xiaoming
)
{
println
(
"好开心~"
)
}
else
{
println
(
"没有玩具可以玩 :("
)
}
func 的参数修饰
在声明一个 Swift 的方法的时候,我们一般不去指定参数前面的修饰符,而是直接声明参数:
func
incrementor
(
variable
:
Int
)
->
Int
{
return
variable
+
1
}
这个方法接受一个 Int
的输入,然后通过将这个输入加 1,返回一个新的比输入大 1 的 Int
。嘛,就是一个简单的 +1器。
有些同学在大学的 C 程序设计里可能学过像 ++
这样的“自增”运算符,再加上做了不少关于“判断一个数被各种前置 ++
和后置 ++
折磨后的输出是什么”的考试题,所以之后写代码时也会不自觉地喜欢带上这种风格。于是同样的功能可能会写出类似这样的方法:
这是错误代码
func
incrementor
(
variable
:
Int
)
->
Int
{
return
++
variable
}
残念..编译错误。为什么在 Swift 里这样都不行呢?答案是因为 Swift 其实是一门讨厌变化的语言。所有有可能的地方,都被默认认为是不可变的,也就是用 let
进行声明的。这样不仅可以确保安全,也能在编译器的性能优化上更有作为。在方法的参数上也是如此,我们不写修饰符的话,默认情况下所有参数都是 let
的,上面的代码等效为:
func
incrementor
(
let
variable
:
Int
)
->
Int
{
return
++
variable
}
let
的参数,不能重新赋值这是理所当然的。要让这个方法正确编译,我们需要做的改动是将 let
改为 var
:
func
incrementor
(
var
variable
:
Int
)
->
Int
{
return
++
variable
}
现在我们的 +1器 又可以正确工作了:
var
luckyNumber
=
7
let
newNumber
=
incrementor
(
luckyNumber
)
// newNumber = 8
println
(
luckyNumber
)
// luckyNumber 还是 7
正如上面的例子,我们将参数写作 var
后,通过调用返回的值是正确的,而 luckyNumber
还是保持了原来的值。这说明 var
只是在方法内部作用,而不直接影响输入的值。有些时候我们会希望在方法内部直接修改输入的值,这时候我们可以使用 inout
来对参数进行修饰:
func
incrementor
(
inout
variable
:
Int
)
{
++
variable
}
因为在函数内部就更改了值,所以也不需要返回了。调用也要改变为相应的形式,在前面加上 &
符号:
var
luckyNumber
=
7
incrementor
(
&
luckyNumber
)
println
(
luckyNumber
)
// luckyNumber = 8
最后,要注意的是参数的修饰是具有传递限制的,就是说对于跨越层级的调用,我们需要保证同一参数的修饰是统一的。举个例子,比如我们想扩展一下上面的方法,实现一个可以累加任意数字的 +N器 的话,可以写成这样:
func
makeIncrementor
(
addNumber
:
Int
)
->
((
inout
Int
)
->
())
{
func
incrementor
(
inout
variable
:
Int
)
->
()
{
variable
+=
addNumber
;
}
return
incrementor
;
}
外层的 makeIncrementor
的返回里也需要在参数的类型前面明确指出修饰词,以符合内部的定义,否则将无法编译通过。
- Swift Tips
- Swift Tips - Array 类型
- Swift开发tips
- swift 闭包 tips
- swift Tips(version1.2 xcode6.4)
- swift Tips(version2.0+ xcode7.0+)
- swift Tips(version3.0+ xcode8.0+)
- Tips
- Tips
- Tips
- Tips
- > tips
- Tips
- Tips
- Tips
- Tips
- Tips
- Tips
- html+css 常用的知识点
- 动态链接库是如何建立的(2)
- WebBasic03-CSS
- 图片导航轮播
- Spring MVC工程结构及配置整理
- Swift Tips
- C#代码直接调用WCF服务
- Java常见问题-高亮
- Linux下cpu信息查看
- Qt学习之路_4(Qt UDP的初步使用)
- mysql循环存储
- NYOJ - 街区最短路径问题
- POJ 3468 A Simple Problem with Integers
- 剑指offer(55):正则表达式匹配