如果你还不知道Swift的话,你应该回头复习一下苹果的WWDC 2014发布会了。WWDC 2014上苹果出人意料地宣布了一种从Objective-C进化而来的全新语言——Swift,这在我所知的苹果发布会历史上,还是头一遭介绍这么一个外人摸不着头脑的东西。好在是WWDC,台下坐着的基本都是开发者,从基调演讲的现场反应来看,大家对Swift的反响是出乎意料的强。另一方面,从今年WWDC的会议日程来看,除去几乎每天都有“动手做环节“——Swift Lab之外,光是含Swift字样的讲座就不下8个。有面向小白的“Introduction to Swift”,面向中级者的“Intermediate Swift”,还有上级者向的“Advanced Swift Debugging in LLDB”、“Swift Interoperability In Depth”等。这不,已经有公司发出招聘启事:招聘Swift开发人员,要求Swift编程经验不少于1天。一时间似乎全世界都在讨论Swift。
苹果还火上浇油,在《The Swift Programming Language》里开门见山地宣称[1]
Swift融合了最佳的现代编程语言思想和最强力的苹果工程学文化。Swift语言在优化开发流程的同时,却没有在性能上进行任何妥协。
现在面对我们的问题是:投资时间研究它究竟有没有价值,还是纯粹浪费时间?如果你读到现在还没有果断右上角,我想答案已经很明确了。现在就让我们来近距离观察一下这只轻巧灵活的雨燕吧!简洁的语法糖
基本类型和容器
先来看看普通类型和容器好了。字符串处理的好坏,决定了一个现代语言的第一印象。在这里我们看到Swift做的似乎还不错,列表容器的定义和使用也和诸多现代语言接近,在这一点上让开发者对Swift平添了不少好感。
// let关键字做常量定义 let famouseDeveloper = "Hideo Kojima" // var关键字创建变量,下面是一个列表容器 var bigHandPublishers = ["EA", "Activision", "Tencent"] bigHandPublishers[2] = "UBI Soft" //有点丢人还是换了吧 for publisher in bigHandPublishers { // 字符串格式化 println("\(famouseDeveloper) once worked at \(publisher).") } // 认真你就输了 // Hideo Kojima once worked at EA // Hideo Kojima once worked at Activision // Hideo Kojima once worked at UBI Soft函数和闭包
现代语言大抵会把函数和闭包作为第一公民处理,即享受基本类型的同等待遇,有些情况下待遇甚至更高。Swift也不例外。在闭包的使用上,由于抛弃了Objective-C里繁琐的语法,不再是block那种张牙舞爪的样子,变成了下面的小清新状:
// 函数定义 func foobar() { println("Hello World") } // 带返回值定义的函数 func httpResponse() -> (Int, String) { return (200, "Succeeded") // 支持多重返回值 } /* * 带闭包参数的函数 * 注意task参数声明为一个不带任何参数和返回值的闭包 */ func repeat(count: Int, task: () -> ()) { for i in 0..count // 注意这里的range,是[0, count) { task() } } // 如何调用看下面 repeat(2) { println("Hello World!") } // 打印结果: // Hello World! // Hello World!严肃对待类型
Swift是一门类型安全的语言,它支持duck typing,但还是严肃地对待类型,编译期间会做类型检查。这对于使用动态语言的粗心程序员来说无疑是一种福利。于是像这样,Swift可以声明变量的类型:var i:Int = 30也可以声明optional类型,也就是在类型后面加问号:var optionalInteger: Int?
// 嗯,很多独立游戏 var groundBreakingGames = [ "2D Boys": "World of Goo", "notch" : "Minecraft", "Simogo": "Year Walk", "Coconut Island Games": "iDragPaper" ] // 字典定义 // 我觉得Simogo的Device 6更具代表性 groundBreakingGames["Simogo"] = "Device 6" // 修改字典 // 自己的游戏好像还没出呢:optional string let myGame: String? = groundBreakingGames["Beacon Labs"] // 这是普通青年的写法 if myGame { // 感叹号操作符被称为forced unwrapping let myRealGame = myGame! println("Hey check out this awesome game: \(myRealGame)") } // 这是文艺青年的写法,不需要显式的unwrapping if let myRealGame = myGame { println("Hey check out this awesome game: \(myRealGame)") }Optional Chaining
Optional value看似稍有点麻烦,但带来了更多的安全性,相信也是苹果在设计这门语言中做出的取舍。Optional的概念还带来了所谓的optional chaining,用更少的判断来鼓励更少的错误。以发布会的例子来说
//这是原Objective-C的写法 if (myDelegate != nil) { if ([myDelegate respondsToSelector: @selector(scrollViewDidScroll:)]) { [myDelegate scrollViewDidScroll:myScrollView]; } } // 这是Swift的写法 myDelegate?.scrollViewDidScroll?(myScrollView)对上面的例子可以这么理解:当optional value为nil的时候,运行时(runtime)不进行unwrap,并且直接跳过后续表达式的计算。是不是碉堡了?
可能你会说Swift像某某语言。没错,一千个人眼里有一千个哈姆雷特,Python程序员觉得Swift像Python,Ruby程序员觉得像Ruby,还有人觉得Swift像是拿Go改的一版Objective-C。这正是苹果棋高一着的地方,在让你感受新语言的简洁高效之外,还不让你觉得它太陌生,顺便还能向你的朋友们炫耀一下你的博学多才。
据说,现在github上Swift语言的开源项目已多达300多个了,而我们却没有太多时间关心这当下最火的语言。给我们一点时间,下次来聊一聊对Swift的一些更深刻的认识与不满,这样也对得起标题里的“不”字。主题不仅仅限于:Objective-C的包袱、苹果的封闭平台、继承和扩展、对象生命周期管理。
注:[1] 苹果宣称iBooks中该文档在第一天内被下载了37万次
Swift很讨巧,给人以一种简洁、有力的第一观感。看着其他小伙伴已经一日千里地奔着Swift去了,你已经跃跃欲试了?别急,今天来给大家降降温。须知硬币都有正反面,Swift在给予开发者便利的同时,也精心准备了诸多深坑供大家玩耍。
~我们已经可以扔掉Objective-C了么?~
Optional:安全与便利的悖论
Swift以带问号的语法引入了optional类型,主责是将未初始化变量以一种主动的形式迫使开发者知晓。而开发者若想使用optional,即“允许未初始化”的变量时,必须使用感叹号来进行取值,也就是所谓的unwrap。
var illegalString :String = nil // 编译错误 var legalString :String? = nil func genString(bar :Int) -> String? { if bar > 30 { return "abc" } else { // 如果可以返回nil,必须强制返回类型为optional return nil } } var a: String = genString(50)! // 必须进行取值操作,否则类型不匹配 var b: String? = genString(0)苹果在设计Swift的时候,将安全摆在了一个相当重度的位置,从而牺牲了语言的随性。寄希望于Swift能更像脚本语言那样随意编写的同学,可能会在optional这里感受到来自语言设计者的恶意。
第二参数名:另一个名字还是历史的包袱?
这个问题还算比较良性。我们通常写带参数的函数是这样
//这样很好,与世无争 func foobar(a: Int, b: String) -> String {...}但是,皇上您还记得大明湖畔的夏雨荷么?
- (instancetype)initWithBytes:(const void *)bytes length:(NSUInteger)length encoding:(NSStringEncoding)encoding多么令人怀念的老时光。Swift为了要兼容Objective-C/Cocoa,也搞出了类似的东西,称之为“外部参数名”:
class Matrix { func doMultiplication(str :String, fromData data1 :Int[], withData data2: Int[]) { // do something } } let m = Matrix() m.doMultiplication("My Matrix", fromData:[1,1,0,1], withData:[1,1,2,3])从第二个参数开始,每个参数还有内外两个名字?这种丑到爆的语法真是历(nan)史(yi)沉(tu)疴(cao)啊╮(╯▽╰)╭
初始化:安全第一,好用第二
毫无意外地,Swift是一门(带少量函数式编程特性的)面向对象语言。同样毫无意外地,Swift类必须安装有初始化函数init。秉承前面optional带来的安全理念,开发者对于init的编写也有诸多规定,也就演化出了特定初始化函数和便利初始化函数,以及随之而来的convenience关键字。
class Item { let name :String init(givenName name:String) { self.name = name } } var item = Item(givenName: "GeneralItem") class Grenade : Item { let explosive :Int init(givenName name: String, withExplosive explosive: Int) { self.explosive = explosive // 必须在子类初始化完毕所有成员后,再调用 // 基类的构造;否则编译错 super.init(givenName: name) } convenience init(explosive: Int) { // 所谓的便利构造函数只能调用子类的特定构造函数, // 并不能涉及到基类构造;否则编译错 self.init(givenName:"MyGrenade", withExplosive:explosive) } } let grenade1 = Grenade(givenName: "MyGrenade", withExplosive: 500) let grenade2 = Grenade(explosive: 300)写个初始化函数这么麻烦?为了安全,就得这么麻烦!其实不只是初始化,为了安全,连简单的switch结构也必须写default,这安全与好用的功与过,就全凭使用者来评判了。
ARC:自动引用计数带来的烦恼
苹果从Objective-C时代就引入了辅助对象生命期管理的自动引用计数(ARC)机制。Swift也一并继承了ARC。虽然ARC有轻量化、行为可预测、没有垃圾回收机制卡顿的优势,但也带来了很要命的问题:无法自行解决循环引用问题——从而引起内存泄露。
内存泄露难以跟踪、忽隐忽现又破坏力极大,它无疑是程序员最恐怖的梦魇。无奈之下,Swift引入了两个关键字weak和unowned,修饰不会自动增加引用计数的变量,来解决循环引用问题。
class Person { let name: String var buff: Buff? init(name: String) { self.name = name } } // 施加在人物身上的buff技能 class Buff { let id: Int weak var target: Person? init(id: Int) { self.id = id } } var me = Person(name: "Jon Blow") var buff = Buff(id: 42) me.buff = buff然而游戏中buff是跟着人走的。没有人就谈不上buff,这就意味着Buff类中target设置为optional是不合适的。更好的做法是使用unowned。
class Person { let name: String var buff: BetterBuff? init(name: String) { self.name = name } } class BetterBuff { let id: Int unowned let target: Person init(id: Int, withTarget target: Person) { self.id = id self.target = target } } var me = Person(name: "Jon Blow") me.buff = BetterBuff(id: 42, withTarget:me)这里友情提醒一句,解决普通对象之间的循环引用只是战斗的开始。Swift是门现代语言,函数和闭包都是一等公民,所以后面还有闭包之间的循环引用等着勇敢的程序员们。
其他
另外还有一些语言特性看上去新奇,但基本上只能算是Objective-C的翻版,例如protocol和extension,基本上是复制了Objective-C的protocol和category,并没有为解放程序员的双手带来更多好消息。 值得一提的倒是外围特性:Playgrounds。Playgrounds并不是Swift语言自带的功能,而是Xcode 6中一种新工程形态。如果不是发布会上那惊鸿一瞥,Playgrounds恐怕会湮没在众多新特性的海洋之中。Playgrounds是促进苹果整个生态系统的一个巨大进步,它为Swift配置了动态语言的专利——交互式环境。而且还像matlab这类软件一样,Playgrounds提供数值视觉化效果,这类难能可贵的“所见即所得”,让游戏的细调的工作变得如捏橡皮泥搬简单。虽然Playgrounds之后会否发展成为一个单独的编辑器,我们不得而知,但Swift+Playgrounds+Sprite Kit毫无疑问已经向Corona这类二线游戏引擎产生了致命的冲击——一个飞速发展的生态圈内,是容不下弱者的。
说起来,Xcode 6 beta中的Playgrounds还非常地不稳定,没有足够的勇气不要轻易尝试哦~
未来
讲了这么多Swift的坏话,回过头来我们还是要认真评估一下它的未来:尽管Swift有着这样那样的妥协、不足,但它仍将是2014年增长速度最快的语言,没有之一,就像沉默十几年的Objective-C在iPhone开发热潮兴起之时,一瞬间成为了世界的宠儿。且不说,Swift所依赖的开源编译系统LLVM,也是目前编译器界最受瞩目的项目。他的创造者Chris Lattener,也就是那个WWDC上演示Playgrounds的默默无闻的天才少年,已经多次被ACM协会授予编译器、语言、系统方面的奖项。他在编译器、语言生态系统的远见卓识,让LLVM在原先强大的业界标准——GCC编译系统面前迅速崛起。不用猜,目前我们Xcode使用的编译系统正是LLVM。
行文至此,我们可以初步得出结论:Swift是一门脱胎自Objective-C的语言,带有浓厚的Objective-C/Cocoa的痕迹,也遵循了前辈的一些设计哲学,但同时拥有强大的现代语言特性。在权衡了易用性和安全性的前提下,做到了一定的动态语言的功能——不能说是尽善尽美。它的底层由强大且开放的LLVM支撑,外围又有Xcode所带来的Playgrounds让开发者立即上手。凭借苹果的开发生态圈,Swift必将以极快的速度取代Objective-C成为苹果平台开发的首选。至于Swift是否有望扩展到其他平台,WWDC并没有给予我们答案。参考苹果之前的作风,恐怕其底层库Cocoa移植的可能性不大。但由于LLVM的存在,Swift以开源编译器的形态,舍弃Cocoa而出现在各家平台上的可能性还是很大的。
评论