@wrlqwe
2016-05-23T03:07:33.000000Z
字数 12381
阅读 1069
Swift iOS
两年前的6月3日,WWDC上,Apple发布了全新的Swift语言用于未来的苹果软件开发,刚刚推出时,虽然有着丰富而强大的语法特性,但是并不成熟。现在Swift3.0即将发布,从1.0到2.0到2.2,Swift已经渐渐稳定,API和语法上的更改逐渐变少。
不用Obj-C的理由越来越多,不用Swift的理由越来越少,现在,我将尝试在下面几个方面,
类型体系
扩展能力
语法
函数式
向你介绍Swift的强大之处~
Swift兼容绝大部分的Obj-C的类型和C的类型,与此同时,它引入了全新的类型体系。
Swift的类型分为值类型和引用类型,与Obj-C不同的是:Obj-C的对象全部都是引用类型,struct和基本类型都是C的特性,而在Swift当中,除了struct,还有enum和元组,所有的基础数据类型也都归为了struct,平时常用的数据类型:String、Array、Dictionary,也都是struct类型。
不同于引用类型,对于值类型,声明一个新的值类型就是初始化一个新的实例:
struct S { var data: Int = -1 }var a = S()var b = a // b是a的拷贝a.data = 42 //更改a的数据,b的不受影响print("\(a.data), \(b.data)") //42, -1
这往往能帮助理清代码,因为不用担心实例会被逻辑外的其他代码改动。同时,在多线程环境下,使用值类型可以安全的传递变量,不需要特地同步。
由于值类型的安全性,struct被广泛用在Swift语言中,包括基础的String和容器的Array和Dictionary,都被设计为struct。这些类型在保持了对传统OC对象API的兼容性之外,还做出了丰富的扩展,在OC和Swift中间,它们可以自由转换,作为容器的Array和Dictionary甚至无需包装可以直接存取值类型。
enum也是程序设计中不可或缺的元素,在Swift中,enum的值不但可以是Int类型,同时还能支持String、Float、Bool类型:
enum Months: String {case Jan, Feb}
enum还可以包裹一个值,初学者很头疼的Optional类型就是一个enum:
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {case Nonecase Some(Wrapped)}
另外,enum在模式匹配中也扮演着重要的角色。
Swift是一门类型安全的语言,类型安全语言需要代码里值的类型非常明确。如果你的代码中有部分值需要String类型,你就不能错误地传递Int。
鉴于Swift的类型安全,编译代码时,Swift会执行类型检查并将任何类型不匹配的地方标记为错误,使你在开发当中尽可能早的捕获并修正错误。
类型检查有助于你在操作不同值的类型时避免犯错。但这并不意味着你必须在声明每一个常量或变量时去检查类型,如果你不检查所需值的类型,Swift会执行类型推断来计算出相应地类型。
类型推断让编译器在编译代码时,根据你提供的值,自动推测出特定表达式的类型。
基于类型推断,Swift对类型声明的需要远比C或Objective-C语言要少得多。常量与变量仍然有明确的类型,但明确指定类型的工作已经由编译器代你完成。
比如,初始化一个变量时,如果不指定具体的类型,数字12会被推断为Int:
let monthsOfYear = 12 //Int类型
对于集合类型,编译器可以根据元素自动推断出合适的类型:
let arr0 = [1, 2] //[Int]let arr1 = [1, 2, 3.0] //[Double]let arr2 = [1, 2, true] //[Number]let arr3 = [1, 2, "3"] //[NSObject]
编译器的类型推断在保证了类型安全的同时,减少了繁琐的声明,在实际使用中,完全可以不假思索的写出 let variable = value 的代码,非常省心省力。
泛型是现代编程语言的重要特征,相对于Obj-C,基于Swift的泛型特性,你能够写出扩展性更强、复用性更强的方法、类型,它可以让你尽可能避免重复代码,用一种清晰和抽象的方式来表达代码的意图。
下面是一个典型的泛型:
func exchangeValue<T>(inout left: T, inout right: T) {(left, right) = (right, left)}var leftVal = 100var rightVal = 200exchangeValue(&leftVal, right: &rightVal)print("\(leftVal), \(rightVal)") // 200, 100
这个例子里,我们通过inout定义了可以改变参数值的函数, 然后利用元组,实现了值交换的逻辑。当然,他们要针对同一个泛型T的实例。
在Swift中,泛型所能做的事远远不止这些。
接下来,我们通过泛型特性,实现一个典型的Stack构造。
Stack是程序设计里一个典型的数据结构,它跟Array类似,但是功能上与Array并不想吐,它存储一个队列,遵循先进后出的原则存储数据。

↓
除了泛型函数,Swift还允许你定义自己的泛型类型。通过自定泛型类型,我们可以很容易扩展类型的适用范围,更好地复用代码。
struct Stack<T> {var items = [T]()mutating func push(item: T) {items.append(item)}mutating func pop() -> T {return items.removeLast()}}
之前阐述的protocol里的所有类型都是确切的,Swift允许你在protocol中使用类似于泛型函数、泛型类型的泛类型,这就是关联类型(Associated Types)。
protocol Container {associatedtype ItemTypemutating func append(item: ItemType)var count: Int { get }subscript(i: Int) -> ItemType { get }}
Container协议定义了三个任何容器必须支持的兼容要求:
这个Container协议没有指定容器中的元素是如何存储的,也没有指定容器可以存储的元素类型,但是限制了append方法的形参类型必须和subscript返回值类型一致。这种限制构成所谓的关联类型。
接下来,我们为Stack实现Container协议:
struct Stack<T>: Container {var items = [T]()mutating func push(item: T) {items.append(item)}mutating func pop() -> T {return items.removeLast()}mutating func append(item: T) {items.append(item)}var count: Int {return items.count}subscript(i: Int) -> T {return items[i]}}
类型约束使得Swift的泛型更加强大,但是还不够灵活。想象一个应用场景。某个函数接受两个参数,这两个参数都要求遵循Container协议,除此之外,还都要求这两个参数(集合类型)的元素类型相同,如何实现?单纯的类型约束是办不到的,好在Swift为我们带来了where语句。
where语句的目的很直接,增强了「泛型」的威力。根据我的理解,where应该属于那种约束少、灵活大的语言特性,关于它的使用想必非常繁杂。
下面举个例子引出where的应用场景。定义一个名为allItemsMatch的泛型函数,顾名思义,该函数用来检查两个Container是否包含相同顺序的相同元素,如果所有元素顺序相同且值相同,则返回true,否则返回false,如下:
func allItemsMatch<C1: Container, C2: Containerwhere C1.ItemType == C2.ItemType, C1.ItemType: Equatable>(someContainer: C1, anotherContainer: C2) -> Bool {// check that both containers contain the same number of itemsif someContainer.count != anotherContainer.count {return false}// check each pair of items to see if they are equivalentfor i in 0..<someContainer.count {if someContainer[i] != anotherContainer[i] {return false}}// all items match, so return truereturn true}
泛型函数allItemsMatch头部信息告诉我们:
其中,后两部分内容定义在Where语句中,作为C1和C2的约束。
allItemsMatch(_:_:)方法用起来是这样的:
var stackOfStrings = Stack<String>()stackOfStrings.push("uno")stackOfStrings.push("dos")stackOfStrings.push("tres")var arrayOfStrings = ["uno", "dos", "tres"]if allItemsMatch(stackOfStrings, arrayOfStrings) {print("All items match.")} else {print("Not all items match.")}
在Obj-C中,protocol支持约束NSObject的子类,Swift在全部支持Obj-C的基础上,扩展了自己的类型。
在Swift中,protocol不但可以使用在class里,还能为struct和enum等几乎所有类型扩展方法,这使protocol的作用被放大了数倍,不再仅仅担任接口定义的角色。
更激动人心的是: Swift中的基本类型例如Int、Float都是以struct实现,所以你甚至可以为基本数据类型添加方法和属性(当然是计算属性),说Swift是一个完全面向协议的语言毫不为过。
我们接下来为Int添加扩展,使它可以根据自己的值执行N次操作:
extension Int {func times(@noescape action: () -> ()) {for _ in 0..<self {action()}}}3.times {print("test")}
在这里,我们定义了times方法,使其可以执行N次action。
其中@noescape修饰闭包告诉编译器,这次闭包的调用在函数返回前就已经结束了,在闭包里引用self是安全的,不必声明为unowned或者weak。
同样的,我们也可以为dispatch_queue_t实现扩展:
extension dispatch_queue_t{final func async(block: dispatch_block_t) {dispatch_async(self, block)}final func sync(block: dispatch_block_t) {dispatch_sync(self, block)}}dispatch_get_main_queue().async {}
在开发工作中,继承总是用来在多个类之间共享代码。
设想一个场景,一个人类,可以说话和睡觉,而一个Worker,除了上述功能,还可以工作。
解决方案很简单,我们可以 Person 和 Worker 之间建立继承关系:
class Person {func say() {print("hello")}func sleep() {print("I'm sleeping")}}class Worker: Person {func work() {print("I'm carrying bricks")}}
在随后的开发中,可能一个新的类型
Robot,也可以工作了,这个时候我们不能使用 Worker 了,因为很明显, Robot不可能同时是一个Person。
解决这个问题,我们可能尝试组合的方式,通过公共类管理 work 行为:
class WorkManager {func work() {print("I'm carrying bricks")}}class Worker: Person {let workManager = WorkManager()func work() {workManager.work()}}class Robot: Machine {let workManager = WorkManager()func work() {workManager.work()}}
这样做的缺点也显而易见,代码虽然复用了,可是类结构也变的臃肿,每次都要引用 WorkManager 。
在Swift2.0里在定义一个协议protocol时,还能使用extension给它的某些方法做默认实现:
protocol Workable {func work()}extension Workable {func work() {print("I'm carrying bricks")}}
有了上面的代码,当你创建一个遵从 Workable 协议的类或者是结构体时,就能获得 work() 方法。
这只是一个默认的实现方式。因此你可以在需要的时候重新定义这个方法;如果不重新定义的话,会使用这个默认方法。
使用这种方式,可以大大简化我们的代码,我们甚至什么都不需做,指定继承关系就完成了工作:
class Worker: Person, Workable {}class Robot: Machine, Workable {}...let worker = Worker()let robot = Robot()worker.work() // I'm carrying bricksrobot.work() // I'm carrying bricks
当然我们也可以设定协议依赖的数据,比如work依赖对象的名字:
protocol Workable {var name: String { get }func work()}extension Workable {func work() {print("\(name) is carrying bricks")}}class Worker: Person, Workable {var name: Stringinit(name: String) {self.name = name}}...let worker = Worker("Tiezhu")worker.work() // Tiezhu is carrying bricks
现在可以按照功能重新划分protocol,并开启积木模式了, 首先拆分 Person :
protocol Sayable {var words: String { get }func say()}extension Sayable {func say() {print("\(words)")}}protocol Sleepable {var name: String { get }func sleep()}extension Sleepable {func sleep() {print("\(name) is sleeping")}}
enjoy it :
class Person: Sayable, Sleepable {}class Worker: Person, Workable {var name: Stringvar words = "hello"init(name: String) {self.name = name}}class Robot: Sayable, Workable {var name: Stringvar words = "..."init(name: String) {self.name = name}}class Cat: Sayable, Sleepable {var name: Stringvar words = "meow~"init(name: String) {self.name = name}}...let tiezhu = Worker(name: Tiezhu)tiezhu.work() // Tiezhu is carrying brickslet robot = Robot(name: T1000)robot.work() // T1000 is workinglet feifei = Cat(name: "feifei")feifei.say() // meow~
翻开Swift类型的AppleDoc,我们可以看到对各个类型数不胜数的扩展和协议实现, 包括基础类型和集合、String等类型,每一个都有各式各样的扩展:
public struct Int : SignedIntegerType, Comparable, Equatable {...}extension Int : RandomAccessIndexType {...}
甚至连==这种操作符都由protocol定义,配合上泛型和类型推断,这些共同打造出了类型安全、功能强大的Swift。
模式匹配是指定一种模式,然后尝试识别符合模式的变量,常用来过滤数据,能够避免深层次的if else或者swich判断,提高代码的可维护性。
在Swift中,使用 ~= 来表示模式匹配的操作符。如果我们看看 API 的话,可以看到这个操作符有下面几种版本:
public func ~=<T : Equatable>(a: T, b: T) -> Boolpublic func ~=<I : ForwardIndexType where I : Comparable>(pattern: Range<I>, value: I) -> Bool/// Returns `true` iff `pattern` contains `value`.public func ~=<I : IntervalType>(pattern: I, value: I.Bound) -> Boolpublic func ~=<T>(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool
在实际操作中,我们通常使用switch case来应用模式匹配:
最基本的用法就是解构,使用switch case 帮助将enum或者元组里包裹的内容解析出来:
let aTrade = Trades.Sell(stock: "GOOG", amount: 100, stockPrice: 666.0, type: TraderType.Company)switch aTrade {case let .Buy(stock, amount, _, TraderType.SingleGuy):processSlow(stock, amount, 5.0)case let .Sell(stock, amount, _, TraderType.SingleGuy):processSlow(stock, -1 * amount, 5.0)case let .Buy(stock, amount, _, TraderType.Company):processFast(stock, amount, 2.0)case let .Sell(stock, amount, _, TraderType.Company):processFast(stock, -1 * amount, 2.0)}
switch (4, 5) {case let (x, y): print("\(x) \(y)")}
我们也可以为匹配添加条件,以便更精确的匹配:
let aTrade = Trades.Buy(stock: "GOOG", amount: 1000, stockPrice: 666.0, type: TraderType.SingleGuy)switch aTrade {case let .Sell(stock, amount, price, TraderType.SingleGuy)where price*Float(amount) > 1000000:processFast(stock, -1 * amount, 5.0)}
let age = 23let job: String? = "Operator"let payload: AnyObject = NSDictionary()switch (age, job, payload) {case (let age, _?, _ as NSDictionary):print(age)default: ()}
值得注意的是: 模式匹配在与switch共用时,只能匹配第一个模式,并不能像一般的switch case语句,同时case多条。
let a: Any = 5switch a {case is Int:print (a as! Int + 1)case let n as Int:print (n + 1)default: ()}
switch 5 {case 0..10: print("In range 0-10")}
对于一个类型
struct Soldier {let hp: Intlet x: Intlet y: Int}
重写~=运算符
func ~= (pattern: Int, value: Soldier) -> Bool {return pattern == value.hp}
可以这样匹配
let soldier = Soldier(hp: 99, x: 10, y: 10)switch soldier {case 0: print("dead soldier")default: ()}
如果仅仅做一次匹配,则可以不使用switch case语法,直接用for case或者guard case。
for case let Entity.Entry(t, x, y, _) in gameEntities()where x > 0 && y > 0 {drawEntity(t, x, y)}
对于多个条件的判断,模式匹配可以发挥巨大的作用。
假如我们有一个url,想要校验匹配它们的全部path部分,除了繁琐的判断,使用模式匹配,它的代码可以很简单:
guard let url = NSURL(string: "https://yeasy.gitbooks.io/docker_practice/content/container/daemon.html") else {print("wrong url")return}guard let relativePath = url.relativePath else {print("no relativePath")return}let paths = relativePath.componentsSeparatedByString("/")var generator = paths.generate()if case (""?, let second, "content"?, let fourth as String, "daemon.html"?) = (generator.next(), generator.next(), generator.next(), generator.next(), generator.next()) {print("matched second: \(second!) fourth: \(fourth)")}
在这里, 我们使用generator来帮助我们枚举paths的各个部分, 仅仅用了一个 if case, 就解决了需要多次判断的问题。
通过前边Soldier的例子,我们已经熟悉了~=运算符,现在我们可以对它进行重写,帮助我们实现正则表达式的匹配:
func ~=(pattern: NSRegularExpression, input: String) -> Bool {return pattern.numberOfMatchesInString(input,options: [],range: NSRange(location: 0, length: input.characters.count)) > 0}if let pattern = try? NSRegularExpression(pattern: "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$", options: []) {let email = "email@rulin.me"if case pattern = email {print("binggo")}}
Swift对函数式编程也提供了良好的支持,函数式编程强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。
在Swift中,函数可以是first-calss function, 它可以被赋给一个变量,在开发中,函数和闭包还可以自由的互相转换。
let numbers = [7: 10, 9: 2, 8: 3, 4: 1, 5: 4]let newNumbers = numbers.map(+)print(newNumbers) //[9, 17, 11, 11, 5]
在这个例子中,我们把+函数传递给map方法,此方法把Dictionary的每一对key-value相加,得出新的值,以此组成了数组,因为Dictionary没有顺序,所以最终的array并没有与一开始声明Dictionary的顺序一致。
这里+方法是第一等公民,所以它可以被当做参数传递,而map则是函数式编程中的重要方法,它把老的item映射为新的item,返回一个数组,map方法广泛存在于swift的哥哥类型和协议中。
下面一个例子:
let sum = (0...100).map({ num inreturn num + 1}).filter({ num inreturn num % 2 == 1}).reduce(0, combine: { lastResult, newValue inreturn lastResult + newValue})
无副作用是函数式编程重要的特点之一,它提倡每个函数都有返回值,并且函数不要对全局变量进行修改,保证它的行为单一可控,更易于重用。
在这个例子里,map、filter和reduce都是无副作用的,他们每一步都计算出了一个结果,然后依赖这个结果进行下一步的计算。
柯里化也是函数式编程中的一个重要概念,它可以轻易把多参数函数map到单参数函数上,组成所有参数后最终惰性求值。
柯里化的方法可以把参数的传递提供给外界,而不用关心具体赋参数的过程,有助于减少代码间的耦合性。
func plus(first first: Int) -> (second: Int) -> (third: Int) -> Int {return { second -> Int -> Int in{ third inreturn first + second + third}}}let sum0 = plus(first: 10)//somewherelet sum1 = sum0(second: 11)//some another wherelet sum2 = sum1(third: 100)print(sum2) // print: 121
相比于Obj-C,Swift 在于方法调用上的优化使它有了更快的执行速度。在 Obj-C 中,所有的对于 NSObject 的方法调用在编译时会被转为 objc_msgSend 方法进行动态派发,这种方式虽然提供了强大的灵活性,但是确实会对程序运行速度有影响。
Swift 因为使用了更安全和严格的类型,如果我们在编写代码中指明了某个实际的类型的话,我们就可以向编译器保证在运行时该对象一定属于被声明的类型。这对编译器进行代码优化来说是非常有帮助的,因为有了更多更明确的类型信息,编译器就可以在类型中处理多态时建立虚函数表 (vtable),这是一个带有索引的保存了方法所在位置的数组。在方法调用时,与原来动态派发和查找方法不同,现在只需要通过索引就可以直接拿到方法并进行调用了,这是实实在在的性能提升。
当然如果使用NSObject的子类,对性能提升的帮助就不大了。
对于当前已有的项目,Swift的集成非常容易,添加Swift文件时XCode自动引导集成。
Obj-C和Swift之间的互相调用和API转换也已经相当成熟,可以完全可以无缝集成,你可以在新的ViewController里自由的使用Swift,不担心对原有代码的破坏。
需要注意的是,C里的宏定义并不支持转换。
Swift可以轻易地使用在脚本里,如果你对Shell脚本并不熟悉,不妨使用熟悉的方式进行编写~
目前Swift已经开源,并且登录了Linux平台,甚至连Android平台也有移植成功的案例。
作为Obj-C的继任,Swift值得更多关注。
Objective-C是一门古老的语言,推出至今已经超过30年了,虽然几经修缮,但是该落伍的迟早要落幕,而初生的Swift现在也慢慢成熟,是时候完全抛弃Obj-C了。