公众号推荐
微信公众号搜"智元新知" 关注 微信扫一扫可直接关注哦!
Swift Tips
转载:转自猫神100个Swift必备Tips,onevcat
Selector
@selector
是 Objective-C 时代的一个 关键字,它可以将一个 方法 转换并赋值给一个 SEL
类型,它的表现很类似一个 动态的函数 指针。在 Objective-C 时 selector 非常常用,从设定 target-action,到自举询问是否响应某个方法 ,再到指定接受通知 时需要调用 的方法 等等,都是由 selector 来负责的。在 Objective-C 里生成 一个 selector 的方法 一般是这个样子的:
-(void)callMe{//...}-(void)callMeWithPara m:(id)obj{//...}SELsomeMethod=@selector(callMe);SELanotherMethod=@selector(callMeWithPara m:);// 或者也可以使用 NSSelectorFromString// SEL someMethod = NSSelectorFromString(@"callMe");// SEL anotherMethod = NSSelectorFromString(@"callMeWithPara m:");
一般为了方便,很多人会选择使用@selector
,但是如果要追求灵活的话,可能会更愿意使用NSSelectorFromString
的版本 – 因为我们可以在运行时动态生成字符串 ,从而通过方法 的名字来调用 到对应的方法 。
在 Swift 中没有@selector
了,我们要生成 一个 selector 的话现在只能使用字符串。Swift 里对应原来SEL
的类型是一个 叫做Selector
的结构体,它提供了一个 接受字符串的初始化方法 。像上面的两个例子在 Swift 中等效的写法是:
funccallMe(){//...}funccallMeWithP
ara m(obj:AnyObject!){//...}letsomeMethod=Selector("callMe")letanotherMethod=Selector("callMeWithP
ara m:")
和 Objective-C 时一样,记得在callMeWithPara m
后面加上 冒号 (:),这才是完整的方法 名字。多个参数的方法 名也和原来类似,是这个样子:
functurnByAngle(theAngle:Int,speed:Float){//...}letmethod=Selector("turnByAngle:speed:")
另外,因为Selector
类型实现了StringLitera lConvertible
,因此我们甚至可以不使用它的初始化方法 ,而直接用一个 字符串进行赋值,就可以完成创建了。
最后需要注意的是,selector 其实是 Objective-C runtime 的概念,如果你的 selector 对应的方法 只在 Swift 中可见的话 (也就是说它是一个 Swift 中的 private 方法 ),在调用 这个 selector 时你会遇到一个 unrecognized selector 错误 :
正确的做法是在private
前面加上 @objc
关键字,这样运行时就能找到对应的方法 了。
@objcprivatefunccallMe(){//...}NSTimer.scheduledTimerWithTimeInterval(1,repeats:true)
另外,如果方法 的第一个 参数有外部变量的话,在通过字符串生成 Selector
时还有一个 约定,那就是在方法 名和第一个 外部参数之间加上 with
:
funcaMethod(externalp
ara mName:AnyObject!){...}
想获取 对应的获取 Selector
,应该这么写:
lets=Selector("aMethodWithExternal:")
Sequence
Swift 的for...in
可以用在所有实现了SequenceType
的类型上,而为了实现SequenceType
你首先需要实现一个 GeneratorType
。比如一个 实现了反向的generator
和sequence
可以这么写:
// 先定义
一个 实现了 GeneratorType protocol 的类型// GeneratorType 需要指定
一个 typealias Element// 以及提供
一个 返回 Element? 的
方法 next()classReverseGenerator:GeneratorType{typealiasElement=In
tvar counter:Elemen
tini t<T>(array:[T]){self.counter=array.count-1}init(start:Int){self.counter=start}funcnext()->Element?{returnself.counter<0?nil:counter--}}// 然后我们来定义 SequenceType// 和 GeneratorType 很类似,不过换成指定
一个 typealias Generator// 以及提供
一个 返回 Generator? 的
方法 generate()
structr everseSequence<T>:SequenceType{v
ara rray:[T]init(array:[T]){self.array=array}typealiasGenerator=ReverseGeneratorfuncgenerate()->Generator{returnReverseGenerator(array:array)}}letarr=[0,1,2,3,4]// 对 SequenceType 可以使用 for...in 来循环访问foriinReverseSequence(array:arr){println("Index \(i) is \(arr[i])")}
输出 为
Index4is4Index3is3Index2is2Index1is1Index0is0
如果我们想要深究for...in
这样的方法 到底做了什么的话,如果我们将其展开,大概会是下面这个样子:
varg=array.generate()whilel
eto bj=g.next(){println(obj)}
顺便你可以免费得到的收益是你可以使用像map
,filter
和reduce
这些方法 ,因为它们都有对应SequenceType
的版本:
@autoclosure 和 ??
Apple 为了推广和介绍 Swift,破天荒地为这门语言开设了一个 博客 (当然我觉着是因为 Swift 坑太多需要一个 地方来集中解释)。其中有一篇 提到了一个 叫做@autoclosure
的关键词。
@autoclosure
可以说是 Apple 的一个 非常神奇的创造,因为这更多地是像在 “hack” 这门语言。简单说,@autoclosure
做的事情就是把一句表达式自动 地封装 成一个 闭包 (closure)。这样有时候在语法上看起来就会非常漂亮。
比如我们有一个 方法 接受一个 闭包,当闭包执行的结果为true
的时候进行打印:
funclogIfTrue(predicate:()->Bool){ifpredicate(){println("True")}}
在调用 的时候,我们需要写这样的代码
logIfTrue({return2>1})
当然,在 Swift 中对闭包的用法 可以进行一些简化,在这 种情况下我们可以省略掉return
,写成:
logIfTrue({2>1})
还可以更近一步,因为这个闭包是最后一个 参数,所以可以使用尾随闭包 (trailing closure) 的方式把大括号拿出来,然后省略括号,变成:
logIfTrue{2>1}
但是不管那种方式,要么是书写起来十分麻烦,要么是表达上不太清晰,看起来都让人生气。于是@autoclosure
登场了。我们可以改换方法 参数,在参数名前面加上 @autoclosure
关键字:
funclogIfTrue(@autoclosurepredicate:()->Bool){ifpredicate(){println("True")}}
这时候我们就可以直接写:
logIfTrue(2>1)
来进行调用 了,Swift 将会吧2 > 1
这个表达式自动 转换为() -> Bool
。这样我们就得到了一个 写法简单,表意清楚的式子。
在 Swift 中,有一个 非常有用的操作符,可以用来快速 地对nil
进行条件判断 ,那就是??
。这个操作符可以判断输入并在当左侧的值是非nil
的 Optional 值时返回其 value,当左侧是nil
时返回右侧的值,比如:
varlevel:Int?varstartLevel=1varcurrentLevel=level??startLevel
在这 个例子中我们没有设置过level
,因此最后startLevel
被赋值给了currentLevel
。如果我们充满好奇心地点进??
的定义,可以看到??
有两种版本:
func??<T>(optional:T?,@autoclosuredefaultValue:()->T?)->T?func??<T>(optional:T?,@autoclosuredefaultValue:()->T)->T
在这里 我们的输入满足的是后者,虽然表面上看startLevel
只是一个 Int
,但是其实在使用时它被自动 封装成了一个 () -> Int
,有了这个提示 ,我们不妨来猜测一下??
的实现吧:
func??<T>(optional:T?,@autoclosuredefaultValue:()->T)->T{switchoptional{case
.so me(letvalue):returnvaluecase.None:returndefaultValue()}}
可能你会有疑问,为什么这里要使用autoclosure
,直接接受T
作为参数并返回不行么?这正是autoclosure
的一个 最值得称赞的地方。如果我们直接使用T
,那么就意味着在??
操作符真正取值之前,我们就必须准备好一个 默 认值,这个默 认值的准备和计算是会消耗性能 的。但是其实要是optional
不是nil
的话,我们是完全不需要这个默 认值,而会直接返回optional
解包后的值。这样一来,默 认值就白白准备了,这样的开销是完全可以避免的,方法 就是将默 认值的计算推迟到optional
判定为nil
之后。
就这样,我们可以巧妙地绕过条件判断 和强制转换,以很优雅的写法处理对Optional
及默 认值的取值了。最后要提一句的是,@autoclosure
并不支持 带有输入参数的写法,也就是说只有形如() -> T
的参数才能使用这个特性进行简化。另外因为调用 者往往很容易忽视@autoclosure
这个特性,所以在写接受@autoclosure
的方法 时还请特别小心,如果在容易产生歧义或者误解的时候,还是使用完整的闭包写法会比较好。
Optional Chaining
使用 Optional Chaining 可以让我们摆脱很多不必要的判断和取值,但是在使用的时候需要小心陷阱。
因为 Optional Chaining 是随时都可能提前返回nil
的,所以使用 Optional Chaining 所得到的东西其实都是 Optional 的。比如有下面的一段代码 :
cla
sst oy{letname:Stringinit(name:String){self.name=name}}cla
ssp et{vartoy:Toy?}classChild{varpet:Pet?}
在实际使用中,我们想要知道小明的宠物的玩具的名字的时候,可以通过下面的 Optional Chaining 拿到:
lettoyName=xiaoming.pet?.toy?.name
注意虽然我们最后访问的是name
,并且在Toy
的定义中name
是被定义为一个 确定的String
而非String?
的,但是我们拿到的toyName
其实还是一个 String?
的类型。这是由于在 Optional Chaining 中我们在任意一个 ?.
的时候都可能遇到nil
而提前返回,这个时候当然就只能拿到nil
了。
在实际的使用中,我们大多数情况下可能更希望使用 Optional Binding 来直接取值的这样的代码 :
iflettoyName=xiaoming.pet?.toy?.name{// 太好了,小明既有宠物,而且宠物还正好有个玩具}
可能单独拿出来看会很清楚,但是只要稍微和其他特性结合一下,事情就会变得复杂起来。来看看下面的例子:
extensionToy{funcplay(){//...}}
我们为Toy
定义了一个 扩展,以及一个 玩玩具的play()
方法 。还是拿小明举例子,要是有玩具的话,就玩之:
xiaoming.pet?.toy?.play()
除了小明也许我们还有小红小李小张等等..在这 种时候我们会想要把这一串调用 抽象出来,做一个 闭包方便使用。传入一个 Child
对象,如果小朋友有宠物并且宠物有玩具的话,就去玩。于是很可能你会写出这样的代码 :
你会发现这么表意清晰的代码 居然无法编译!
问题在于对于play()
的调用 上。定义的时候我们没有写play()
的返回,这表示这个方法 返回Void
(或者写作一对小括号()
,它们是等价的)。但是正如上所说,经过 Optional Chaining 以后我们得到的是一个 Optional 的结果。也就是说,我们最后得到的应该是这样一个 closure:
letplayClosure={(child:Child)->()?inchild.pet?.toy?.play()}
这样调用 的返回将是一个 ()?
(或者写成Void?
会更清楚一些),虽然看起来挺奇怪的,但这就是事实。使用的时候我们可以通过 Optional Binding 来判定方法 是否调用 成功:
ifletresult:()=playClosure(xiaoming){println("好开心~")}else{println("没有玩具可以玩 :(")}
func 的参数修饰
在声明一个 Swift 的方法 的时候,我们一般不去指定参数前面的修饰符,而是直接声明参数:
funcincrementor(variable:Int)->Int{returnvariable+1}
这个方法 接受一个 Int
的输入,然后通过将这个输入加 1,返回一个 新的比输入大 1 的Int
。嘛,就是一个 简单的+1器 。
有些同学在大学的 C 程序设计里可能学过像++
这样的“自增”运算符,再加上 做了不少关于“判断一个 数被各种前置++
和后置++
折磨后的输出 是什么”的考试题,所以之后写代码 时也会不自觉地喜欢带上这种风格。于是同样的功能 可能会写出类似这样的方法 :
残念..编译错误 。为什么在 Swift 里这样都不行呢?答案是因为 Swift 其实是一门讨厌变化的语言。所有有可能的地方,都被默 认认为是不可变的,也就是用let
进行声明的。这样不仅可以确保安全,也能在编译器的性能 优化上更有作为。在方法 的参数上也是如此,我们不写修饰符的话,默 认情况下所有参数都是let
的,上面的代码 等效为:
funcincrementor(le
tvar iable:Int)->Int{return++variable}
let
的参数,不能重新赋值这是理所当然的。要让这个方法 正确编译,我们需要做 的改动是将let
改为var
:
funcincrementor(varvariable:Int)->Int{return++variable}
现在我们的+1器 又可以正确工作了:
varluckyNumber=7letnewNumber=incrementor(luckyNumber)// newNumber = 8println(luckyNumber)// luckyNumber 还是 7
正如上面的例子,我们将参数写作var
后,通过调用返回 的值是正确的,而luckyNumber
还是保持了原来的值。这说明var
只是在方法 内部作用,而不直接影响输入的值。有些时候我们会希望在方法 内部直接 修改 输入的值,这时候我们可以使用inout
来对参数进行修饰:
funcincrementor(inou
tvar iable:Int){++variable}
因为在函数 内部就更改了值,所以也不需要返回了。调用 也要改变为相应的形式,在前面加上 &
符号:
varluckyNumber=7incrementor(&luckyNumber)println(luckyNumber)// luckyNumber = 8
最后,要注意的是参数的修饰是具有传递限制的,就是说对于跨越层级的调用 ,我们需要保证同一参数的修饰是统一的。举个例子,比如我们想扩展一下上面的方法 ,实现一个 可以累加任意数字的+N器 的话,可以写成这样:
funcmakeIncrementor(addNumber:Int)->((inoutInt)->()){funcincrementor(inou
tvar iable:Int)->(){variable+=addNumber;}returnincrementor;}
外层的makeIncrementor
的返回里也需要在参数的类型前面明确指出修饰词,以符合内部的定义,否则将无法编译通过。
外层的makeIncrementor
的返回里也需要在参数的类型前面明确指出修饰词,以符合内部的定义,否则将无法编译通过。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。