2014年WWDC大会,苹果在毫无预兆的情况下,发布了Swift编程语言。这个语言刚发布就引起了广泛关注。我认为抛开Objective-C单独谈论Swift是不准确的。因此我们先从Objective-C谈起。
Objective-C语言
Objective-C语言是编写iOS/Mac程序的主要语言。编写iOS/Mac程序,除了Objective-C,还可以混合使用C/C++,另外也可以嵌入一些脚本语言。但在UI部分,使用Objective-C最为直接自然。
Objective-C和C++都是C语言的扩展。C++十多年前就已名满天下,而Objective-C虽然也诞生了二十多年,但在iPhone发布之前,一直不温不火。
这两种语言都支持面向对象,设计理念却十分不同。C++的面向对象部分更强调类型,需要知道对象的类型才能调用对象的函数。而Objective-C则更强调消息的发送。另外,C++很看重运行速度,运行时基本不包含类型信息,整个运行环境是静态的。而Objective-C则牺牲了一点运行速度,换来更加灵活、完全动态的运行时环境。
Objective-C的缺点
初学iOS开发,第一个门槛就是Objective-C的语法,它的语法跟主流的语言十分不同。有些人甚至觉得它的语法丑陋得无法忍受。另外,Objective-C的代码看起来比较啰嗦。如同一个人,看起来怪怪的,不太合群,熟悉之后又显得太过唠叨。
Objective-C缺少一些语言的保护机制,成员函数不区分公有私有,且没有命名空间。模块与模块之间需要加前缀来区分,比如Foundation模块类前面都加上NS前缀,表示NextStep。
Objective-C兼容C语言。因此C语言很多设计不合理的地方也被引入了Objective-C中。另外,Objective-C没有采用GC(垃圾回收),它的内存管理采用引用计数的方式,后期引入ARC (自动引用计数),这种内存管理方式相比GC而言,对程序员的要求较高。
当然,Objective-C也有很多优点。甚至可以说Objective-C的某些缺点,从另一角度看,恰恰是它的优点。
Objective-C的优点
在Objective-C中,如下语法并非单纯的函数调用,而是向某对象发送消息。
Objective-C的运行环境是动态的,可以在运行时判断出某对象能否响应某种消息,而不管此对象是什么类型。
UI程序大框架就是消息处理。系统接受到某种消息(如程序启动了、列表被选中了、手指在屏幕上移动了)之后交给可以响应这个消息的对象来处理,而不关心这个对象到底是什么类型。
Objective-C中的C扩展部分,使用符号@开头。比如@class、@interface、@"Hello,World"。而它的消息发送语法则是使用中括号而不是圆括号。
这种独特而另类的语法使得Objective-C的代码,就算跟C++代码混在一起,也很容易被识别出来。此外,将文件扩展名修改成mm就可以在同一文件中混合使用Objective-C和C++。两者直接相互调用,不用经过任何间接层。在需要大量使用C++的场合(比如游戏),这种混编特性很有用。
Objective-C编译后是机器原生指令,运行时环境也小而紧凑。它采用引用计数的内存管理方式,并引入ARC。ARC比GC更容易引起编程错误,但却比GC快。而在性能很重要的场合,Objective-C也很容易直接调用C/C++代码。相对于其他使用虚拟机、采用GC以及间接调用C/C++的移动平台,速度优势非常明显。
Swift和Objective-C的联系
苹果一直在改进Objective-C,默认编译器由GCC换成了LLVM,并先后加入literal、block、ARC、Module等特性。我认为最大的两点改进为以下两点。
- ARC+弱引用,本质还是引用计数。但从人手调用retain、release,转成编译器自动插入代码是个质的飞跃。
- block+GCD,block引入函数式风格的代码块,而GCD则大大简化了异步代码编写方式。
WWDC之后,我开始思考一个问题,苹果为什么不继续改进Objective-C,而发布Swift这门新语言呢?这个问题只有苹果自己知道,其他人只能猜测。
可能的原因有以下几个方面。
- Swift表面看起来很简单,语法跟流行的C#、JavaScript、C++等语言相似,可以吸引更多的开发者。
- Objective-C因为需要兼容C,所以限制了它的改进。而Swift没有历史包袱,可以自由采用最新的语言设计研究成果。
- 设计者的个人品位,Chris Lattner不习惯Objective-C的语法,就去设计了一个新的。当然,这是玩笑话,不要太当真。
Swift虽然是新语言,却融合了Objective-C的很多特性。读Swift的文档会发现,Objective-C与Swift的联系十分密切。Objective-C使用的很多底层技术,被应用到Swift中。
Swift与Objective-C共用同一套运行时环境
我们编写程序,让程序运行起来,被机器执行的代码并非全部是由我们自己来编写的。需要同时运行很多预先写好的支持性的代码,才能让我们自己的代码运行起来。程序并非单独存在的,运行时处在一定的环境当中。我总联想到很多小蚂蚁在泥土上面爬,而我自己写的程序只是其中的一只。
Swift跟Objective-C编译出的程序代码运行在同一套运行环境上面。Swift的类型可以桥接到Objective-C的类型,反之亦然。Swift编写的代码可以调用Objective-C编写的代码,反之也一样。
Objective-C之前积累下来的大量类库,实现不用改写Swift就可以直接调用。
同一个工程,可以同时使用Swift和Objective-C
Objective-C在一端,Swift在另一端,两端经中间文件进行桥接。桥接文件包含Objective-C的头文件,编译时自动转成Swift可以识别的形式。Swift就可以使用Objective-C的类和它的函数。
在Swift的类中,加上@objc(类名)的字样,Objective-C也可以使用Swift编写的类。但Swift跟C++的相互调用,需要Objective-C来封装。
总的来说,共同使用Swift和Objective-C/C++还算方便,但已不能将几种语言的代码,混写在同一文件。大概是因为Swfit的语法不像Objective-C那样独特,混写难以将Swift的代码识别出来。
Swift骨子里大多与Objective-C一样
Objective-C出现过的绝大多数概念,比如引用记数、ARC、属性、协议、接口、初始化、扩展类、命名参数、匿名函数等,在Swift中继续有效(可能只是换了个术语)。我自己将Swift看成是Objective-C的一块大大的语法糖,其他人可能有不同感受。
Swift大多数概念与Objective-C一样,也有些概念在Objective-C找不到对应,比如泛型。Swift中将那种操作写一次就可以作用多个类型的语法叫做Generics(泛型)。
编程语言和语法
新语言出来时,总会有种声音认为语言只是工具,他们会觉得重要的是思想,而看轻工具的作用。认为编程语言是工具本身并没有大错,但语言并非普通的工具,而是思维工具,不能忽视语言对思维的影响。编程语言就如同数学符号,数学符号也是种思维工具,好的数学符号会帮助使用者思考,更奇妙的是似乎符号本身也会思考。用0~9这些阿拉伯数字进行计算时会感到一切都很自然,似乎数字本身在计算,而不是人在做计算。
尽管编程语言的语法很重要,但语法背后的概念更重要。当明白语法背后的概念后,从一门语言切换到另一门有着相同概念的语言会很容易。但语法会影响表达,理论上每门语言都可以表达任何概念。不过当某种概念在某门语言中很难表达出来时,我们就会倾向于不使用它,这种概念在那门语言的社区就难以被人熟知。
编程语言用来表达思路,表达起来是否自然是很重要的。最理想的是不多不少、刚刚好,写起来自然,读起来更需要自然。并非功能越多的语言越好,而是应该越能帮助思考,越容易表达思路的语言越好。
Swift背后的概念大多跟Objective-C差不多。但Swift吸收了很多其他语言的语法,同一个概念,写起来比Objective-C简洁得多、自然得多。从表达的角度来说,Swift比Objective-C优秀。
Swift的语法
苹果出了本很不错的语法教程来详细描述Swift的语法。在这里,我们只挑一些有意思的点来讨论。
简洁
Swift的语法给人第一感觉就是简洁干净。如果省略掉也不引起歧义,那符号基本上可以被省略。
我们来看下面这个例子:
Swift写起来,有点像脚本语言。这里没有出现类型定义,有人觉得它就是脚本语言,是解释执行的。而实际上Swift是真正的编译语言,通过类型推导,变量的类型可以确定下来。既然省略掉类型也不引起歧义,也就可以省略了。上面的例子中,传统的句末分号可以省略;循环中的()也可以省略;循环1是C语言中的传统写法;i作为计数变量;循环2是循环1的等价写法,因为根本不用关心计数变量,因此可以直接写成“_”。
不再兼容C语言,修正Objective-C中容易出错的地方
例子1
在C/Objective-C中,if、while、for之后的判断式并不需要一定传入布尔类型。也可以传入整型、指针等类型,只要非0就为真,并且赋值是有副作用的。比如:
上面代码返回a的数值,这样就有可能出现将判断:
错写成:
为避免这个问题,有种变通写法:
这种写法被称为Yoda表达式,因为《星球大战》中的Yoda大师喜欢使用这样奇特的倒装句子。
Swift强制要求if、while、for后面判断式子一定需要传入布尔类型,并且赋值没有副作用。因此写成以下这种判断就会编译错误。Yoda表达式这种变通写法再也没有必要。
例子2
还是拿判断来说。在C/Objective-C中,if、while、for之后的语句假如只有一行,是可以省略掉大括号的。比如:
当后面的人修改代码,或多人修改同一代码再合并时,可能会在if后面直接插入一行,这样就一定会goto fail了:
在Swift中,强制要求if、while、for之后一定需要写大括号,不管是不是只有一行。这样就避免了上述问题。
不难看出,Swift的语法设计使一些C/Objective-C常见错误不可能再出现。
常用类型集成到语言里面,而非以库的形式提供
有几种类型,几乎每个项目中都使用到了,分别是:整数、浮点数、布尔型、空值;字符串;数组、字典。
对这些类型的支持,每个语言都有所不同。有些老旧的语言不支持浮点;有些语言中,布尔和空值是整数的一种;有些语言中,整数和浮点数合并成一类,统称为Number;有些语言中,并没有字符串、字典、数组;有些语言中,字典和数组归成一类。
语言不提供的类型多数以库的形式提供。比如在C++中,语言内部并没有提供字符串、字典、数组,但这些类型却出现在了标准的库中。使用库的形式来提供常用类型,优点是减少语言自身的复杂度,另外库可以自由替换。而采用语言直接支持的方式,使用起来会很简洁流畅。Swift中的常用类型集成到了语言内部。 数组写成:
字典写成:
这种写法在原来的Objective-C中就已出现,现在融入到了Swift中。
此外,Swift还支持另外两种类型:option和tuple。option表示有值或根本无值;tuple则表示多个值,它可以从函数中返回多个值,或一行代码交换两个值。
在C++中,这两种类型也采用库的方式来提供。std::tie与std::tuple结合,可以达到返回多重值的效果,但显得语法噪音多、不够方便,所以也比较少用。
支持多种编程泛式
将Swift分拆开,可大致分成以下部分:基本类型、控制流、函数定义和调用、函数式编程、面向对象、泛型。
实际上,Swift的内容包含很多,也可以说很复杂,并非表面上看起来那样简单。Swift从语法上直接支持现代流行的编程泛式,包括:结构化编程、函数式编程、面向对象、泛型。每个部分Swift都有些闪光点,在此无法一一述说。
值得一提的是,我特别喜欢它的函数式编程部分。接下来我们直接拿文档的一个排序例子,看看它是怎么简化编程的。
最开始版本,传递函数
使用闭包
推导出参数类型,参数类型省略
推导出返回类型,return省略
省略掉闭包里面变量
假如闭包是函数的最后一个类型,可以将{}提到外面
使用操作符函数,最后版本
很好地支持内部DSL
有一种编程风格不太好归类,就是将程序拆分成“描述+解释”。解释部分写一次,其他地方使用描述式的语句,而不是命令式的语句。
内部DSL,通常利用主语言的语法特性创造出一套写法来写一些描述性的语句。这些语句组合起来就像一门新语言似的。举个从Ruby那里借过来的例子。假如计算几小时之后的秒数,C语言中大致会写成:
而现在Swift中定义了扩展:
因此可以写成(分别是3小时后的秒数和3小时前的秒数):
同理,也可以写成:
这种写法看起来跟原来的命令式写法完全不同。这些语句是描述性的,而原来的Objective-C做不到这一点。估计Swift以后会冒出大量这样风格的库。至于这种风格到底好不好,要看具体情况。据我所知,比较方便定义内部DSL的语言,原来有C++、Ruby、Lisp,而现在又多了Swift。
Swift的语法还有很多有意思的地方。比如属性的中get、set写法;switch case中的模式匹配;泛型中的类型约束等。这里不一一细说了。
Swift的争议
下面列出一些Swift争议性较大的地方:
- 东抄西抄一些语法、大杂烩,没有自己的原创;
- 没有一些private、protected之类的权限控制;
- 没有采用GC(垃圾回收),却采用ARC(自动引用计数);
- 没有异常处理;
- 是否可以跳过Objective-C,直接学习Swift。
对这些争论,我们很难作出客观的评价,更不可能武断地下结论。我感觉Swift这门语言还是比较有意思的(有些人写程序只是单纯为了好玩)。
这里特别提一下异常处理。以往我一直觉得异常处理是很好的特性,现在觉得异处理常并非一定是好的。异常处理的问题在于发生异常的地方与处理异常的地方分离开来,真正处理时难以收集到足够的信息。异常也容易滥用,很多初学者的Java代码,只抛出异常而不处理,或干脆在最外层接收所有异常,再打印出异常信息。这样也就失去了异常处理的意义。Objective-C中也有异常处理的功能,但实际上几乎没有人使用。对于Swift,采用多返回值加上option的方式来处理错误可能会更好。
可视化编程
WWDC发布会上演示了Swift的Playground功能。敲入代码,立即有反馈。当我们每一步操作都得到实时的反馈时,我们的做法会有很多不同,做出的东西也会有不同。可视化编程放到最后,并非是表示它不重要,而是它实在太重要了,只是目前有能力评说的人还不多。关于这部分内容, Bret Victor有几个演讲视频和文章,建议大家看看。
Swift还有很多主题,只能靠大家自己研究了。最后拿出本次WWDC的Slogan与大家分享:Write the code,Change the world。 |