欢迎加入QQ讨论群258996829
Denise 头像
苹果2袋
2
Denise

腾讯Bugly干货分享:浅谈Swift在实际项目中的应用

发布时间:2015-07-16 23:24  回复:0  查看:4027   最后回复:2015-07-16 23:24  

简介


Swift语言从WWDC2014发布开始,到现在已经发展了一年多时间,越来越多的开发者也开始学习和使用这门语言。但就我所了解的情况来看,在实际项目中Swift的应用还是比较少。开发者给它的评价也是褒贬不一,有的说它的安全性高,有的说它的特性多,有的说它的学习成本高,还有的说它是一个玩具语言不适合工程。其实这都很正常,因为一千个人眼中有一千个哈姆雷特,语言的喜好本身就是一件很主观的事情。具体这个语言怎么样,适不适合工程,需要每个人实践之后才能得出自己结论。

Swift的特点


支持Unicode


代码原生支持Unicode字符。不仅在字符串中,甚至变量名、函数名等都能直接使用Unicode字符。虽然看上去很强大,但似乎并没有什么用,应该没人喜欢在编程时不停的切换输入法吧?

安全的类型

采用严格的类型,并去掉了隐式类型转换:

 


隐式类型转换一直是一把双刃剑,虽然使用便利,但是可能引入一些很难调试的BUG,不容忽视。把隐式类型转换摘除,利大于弊。

从类型层面将空值nil隔离,使用时要求对空值进行处理:


严格的语义逻辑

 


Swift对C语系一些常见的语义逻辑漏洞进行了修改,比如if等条件限定为Bool类型,赋值“=”操作不再有返回值等(其实有,是Void,即空元组“()”)。虽然使用上没有之前那么方便和灵活,但这种改变能杜绝很大一部分的手误BUG,比如“==”写成“=”,还能避免一些偷懒所引入的很隐秘的坑,对程序的稳定性和程序员好习惯的培养有很大帮助。

易用性

沿用并完善了Objective-C的函数中缀调用方式,参数有了真正的名字,调用时带上参数名能让函数接口更容易理解,可读性更好:


优化了可变参数定义和使用方式:

 


在C语系中定义可变参数还需要va_list、va_start等,一段时间不用根本想不起来怎么写,还得上网查,而在Swift中只需要遍历一个数组就能取到所有参数,非常方便。

优化了控制流的使用:

 


Switch的case可以连写,而且加入了很多实用的匹配模式,比如匹配范围、元组、条件等,还可以自定义匹配模式,十分强大。另外分支默认是break方式,不像在C语言中,明知道90%的case都是要break的,还要强制写上。还有一个很好的优化就是加入了跳转标签,在多重循环间控制转移的时候更灵活了。

加入了很多实用语法糖,仅仅一个闭包就有这么多简写方式:

 


这些语法糖能节省大量的开发时间和代码量,使用得当也能让代码更清晰,可读性更好。当然如果滥用的话可能起反作用。

丰富的语言特性

Swift支持类、协议、继承、多态等面向对象的语言特性:

 


也有高级函数、闭包等函数式编程特性:

 


还有泛类型、泛函数、泛协议等泛型编程特性以及操作符自定义等新特性:

 


总之Swift加入了大量的流行语言特性,功能灵活、强大,但是语法点也增加了很多,导致语法学习难度增大,各位可以按需要进行有针对性的学习。

Swift的一些重要概念


值与引用

值与引用类型在某些情况下与我们的编程习惯可能会有些冲突,是Swift初学常遇到的一个坑。先看一个例子,定义两个数组arr1和arr2,arr2用arr1赋值:


修改arr2[0]后,发现arr1[0]并没有修改。由此可知在Swift中,Array类型是值类型。再来看看Array的实现方式。按住Command键点击Array类型,进入到Swift库,可以看到如下定义:

  

 

Array和Dictionary,包括Int、Set、Double等基本内建数据类型都是由struct及其实现的一组协议构成。由于struct类型是值类型,所以Swift中的基本内建类型都是值类型。由于是值类型,所以每次赋值或者传参的时候都会有个拷贝的过程。我们先来做个实验:

 

运行以上代码可以发现,在修改了arr[0]的情况下,赋值的时间是1783ms(模拟器下),而仅是读取的情况,赋值时间是0ms。这个结果说明值传递使用了写时拷贝(copy on write)技术,也就是说只要不修改存储的数据,副本和原值共享内存区域。因此我们在使用这些值类型的时候一般有一些原则:


  1. 尽量限制数据规模;
  2. 如果数据规模较大,尽量不用作赋值和传参;
  3. 如果需要赋值和传参,尽量定义为常量或后续不修改数据;
  4. 如果以上都不能避免,可以使用引用类型代替值类型,如 NSMutableArray等;


Optional

Optional可选类型是Swift的一个重要概念,也是初学者比较难理解的地方。我们经常能在阅读代码时看到下面这样的代码:


有问号有感叹号,放的位置不一样作用也不一样,用的时候搞不清什么时候该用哪个,只好跟着xcode提示走,掉进坑里也不知道。要搞清楚这个问题先要明白Optional是什么。在Swift库中可以找到Optional的定义:


Optional其实是个泛型枚举,定义时的类型比如 Int? 等价于 Optional<Int>。它实现了NilLiteralConvertible协议,所以能被nil字面量赋值。当可选类型用nil赋值时,nil被转化成None,当用非nil赋值时,转化为Some(T)。Some(T)这种形式叫做相关值(Associated Values),可以这么理解,这是一种名叫Some,能存放T类型的盒子,我给它赋的值就放在这个盒子里保存。

可选类型相当于对nil或实际的值做了封装,所以使用Optional时需要将实际的值从Some(T)中取出,这个过程就叫解可选(unwrap)。

先来看看问号定义的可选类型:

 


问号定义的可选类型就是普通的Optional,常用的解可选方式有三种,如上图所示。


  1. 先对opt进行判空,若不为空则用 opt! 进行强制解可选。“!”在这里的意思就是,我确定该值不为空,请直接取出实际值;
  2. if let unwrap = opt 来解可选。这是Swift为解可选做的语法糖,叫做可选绑定。如果opt为nil则进入else分支,若不为nil则将实际值提取到unwrap,然后进入if分支;
  3. 利用opt ?? 0解可选。“??”叫空合运算符(Nil Coalescing Operator),作用就是,如果opt为nil,表达式值取后面的默认值,否则表达式返回opt的实际值;


再来看看感叹号定义的可选类型:

 


感叹号定义的可选类型叫做隐式解可选类型。在概念上与问号定义的可选类型并没有什么区别,只是在使用形式上有些不同。

隐式解可选类型在用作右值时并不需要人为进行解可选,它默认由编译器进行强制解可选,相当于编译器自动在后面添加一个感叹号。这样在使用的时候就不用强制对空值进行处理,更方便一点,但是却十分危险,因为必须由人工保证opt_f在用到的地方必须不为nil,否则程序会crash。而人脑总不如机器保险,因此一般情况不建议使用隐式可选类型。

问号还有一种使用场景叫做可选链。先来看看这么一种情况:

 


我定义了一个Person类,类有father属性表示他的父亲,而孤儿可能不知道父亲是谁,因此father是个可选类型。现在来了一个需求,需要获取某个人曾祖父的名字,我们可能会写出如下代码:

 

看上去很不美观,写起来更费劲。针对这种情景,Swift做了优化,加入了可选链这种语法糖,上面的代码就能改写为:


用问号把方法调用或属性获取等连接起来,组成一条调用链。意思就是我不关心中间过程,只关心最后结果,如果中途任何环节返回了nil,就直接返回nil,否则返回最后结果。这样就能节省很多工作量,让代码看上去更美观,结构更清晰。需要注意的一点是,虽然name是确定的String类型,但是由于可选链上任一环节都可能返回nil,最后得到的是一个String?可选类型,所以还需要做一次解可选操作。

Swift的应用


与Cocoa交互

要用Swift写App首先需要了解的就是UI怎么写、系统功能怎么调用。由于Swift并没有重写系统功能库,只是对Cocoa进行了桥接,所以要调用系统功能就要与Cocoa的交互。Swift与Cocoa交互的细节非常多,但是并没有太大难度,因为Cocoa的使用与用Objective-C开发时没有太大不同,一般来说跟着Xcode的提示走基本都没有问题。需要注意的几点:


  1. Swift没有发消息这种方式,所有方法调用和属性访问都是用点操作, alloc、init桥接成了swift中的构造器,使用时直接用class实例化的方式即可。
  2. 一些常用类型在swift中都有对应,可以相互转换,通常情况建议使用swift的类型。
  3. Object-C中有些机制比如 KVO等在swift中不支持,如果要使用,需要带上@objc、dynamic等标记。






多线程

Swift语言本身并没有多线程支持,因此要使用多线程还是要调用NSThread、GCD等:


此外,Object-C中一个很方便的代码互斥方式@synchronized在swift中不支持,因此要实现代码互斥需要自己使用锁。当然也可以模仿@synchronized自定义一个synchronized方法,利用尾部闭包造出原来的感觉:


单例

单例模式是移动开发中常见的一种模式,其一般要求是:


  1. 只初始化一次;
  2. 线程安全;


在Object-C中单例通常是用dispatch_once来实现的,在Swift中同样可以这么做。但是由于Swift中支持全局变量、常量的lazy初始化,我们可以简化单例的实现:


通过let定义单例实例常量来保证线程安全,通过全局常量的lazy初始化来保证单例只初始化一次。

ARC

Swift中的ARC原理与Object-C中一样,只不过在使用形式上有所不同。Swift中所有引用默认都是强引用,另外加入了两个ARC标记:


  • weak:类似Object-C的weak,弱引用,引用失效后引用自动赋值为nil;
  • unowned:类似Object-C中的unsafe_unretain,弱引用,引用失效后保留引用本身;


由于weak引用在使用过程中可能变为nil,所以weak引用必须是可选类型(Optional)。另外Swift对闭包捕获的变量的标记做了优化:


在Object-C中这种情况需要在闭包之外定义一个weak self,然后闭包捕获weak self。Swift中只需要把weak self写在闭包参数表之前的中括号中即可完成同样功能,这种方式代码结构更清晰,可读性更好。需要注意的是在闭包内self是weak的,是个可选类型,因此使用self时需要解可选。

Delegate

代理模式是开发中很常见的一种模式,它的实现原理与Object-C类似,也是通过协议来确定代理接口,用实现协议的实例来充当代理:


不同的地方在于,Object-C中delegate是id类型的,而swift中协议是一种基本类型,delegate可以直接定义为协议类型,这样能让代理的职责更明确。要注意的地方是delegate是weak的,所以必须使用可选类型,而且weak标记的必须是引用,所以protocol必须打上class标记,表示该协议只能被class实现,这样协议类型才能当做引用类型使用。

自定义操作符

Swift中可以自定义操作符,自定义一个操作符需要两个要素:


  1. 操作符特性描述,包括:操作符位置 prefix、infix、postfix等,操作符的结合性left、right以及操作符的优先级;
  2. 操作符功能函数,参数和返回值需要与操作符特性一致,比如 prefix的操作符接受一个参数,返回一个值;


比如我们可以定义一个笑脸操作符,他在字符串后面添加一个笑脸(^_^):


函数式编程

在Swift中,函数是first-class,也就是说函数可以作为参数传入也可以作为返回值返回,也可以给变量赋值。函数式编程是一种很强大、很灵活的思想,在Object-C中也有block等函数式编程思想,但并不明显,在Swift中这一点得到了强化。

比如说可以实现一个函数工厂:

 


泛型编程

Swift中增加了泛型这个概念,泛型是一种很好的代码复用机制,用于将类型不同但实现相同的代码统一起来。泛型在Swift中应用非常广泛,你只要跳转到Swift的库文件就会发现类型、操作符、全局函数的定义中大量应用了泛型,因此不再做详细介绍。这里有一个小例子,利用操作符自定义、函数式和泛型在代码中实现管道机制:


总结


Swift有着更严格的类型和更规范的语义,针对容易造成BUG的点进行了优化,又加入了很多不错的语法糖,代码更简洁、更安全。它吸收了大量其他语言的优秀特性,可以实现一些强大灵活的设计模式,但也造成了其语法繁杂的缺点,增加了学习的难度。Swift本身的设计目标是高效、灵巧,但由于需要兼容Cocoa,导致其设计受到牵制,引入了一些有隐患的设计。由于这些特点,编写Swift代码其实很依赖IDE的辅助,但目前为止xcode的表现还不稳定,经常提示错误,再加上几乎每次xcode更新都伴随着Swift语法修改,Swift想要广泛应用于实际工程还有一段路要走,而Swift2.0的发布和Swift开源化,无疑将加快这个进程。

参考书籍

  • The Swift Programming Language, Apple Inc.
  • Using Swift with Cocoa and Objective-C, Apple Inc.
  • Swifter - 100个Swift必备Tips,王巍

您还未登录,请先登录

热门帖子

最新帖子