@wrlqwe
2016-05-23T03:07:33.000000Z
字数 12381
阅读 933
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 None
case 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 = 100
var rightVal = 200
exchangeValue(&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 ItemType
mutating 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: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer: C1, anotherContainer: C2) -> Bool {
// check that both containers contain the same number of items
if someContainer.count != anotherContainer.count {
return false
}
// check each pair of items to see if they are equivalent
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// all items match, so return true
return 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 bricks
robot.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: String
init(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: String
var words = "hello"
init(name: String) {
self.name = name
}
}
class Robot: Sayable, Workable {
var name: String
var words = "..."
init(name: String) {
self.name = name
}
}
class Cat: Sayable, Sleepable {
var name: String
var words = "meow~"
init(name: String) {
self.name = name
}
}
...
let tiezhu = Worker(name: Tiezhu)
tiezhu.work() // Tiezhu is carrying bricks
let robot = Robot(name: T1000)
robot.work() // T1000 is working
let 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) -> Bool
public 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) -> Bool
public 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 = 23
let 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 = 5
switch 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: Int
let x: Int
let 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 in
return num + 1
}).filter({ num in
return num % 2 == 1
}).reduce(0, combine: { lastResult, newValue in
return lastResult + newValue
})
无副作用是函数式编程重要的特点之一,它提倡每个函数都有返回值,并且函数不要对全局变量进行修改,保证它的行为单一可控,更易于重用。
在这个例子里,map、filter和reduce都是无副作用的,他们每一步都计算出了一个结果,然后依赖这个结果进行下一步的计算。
柯里化也是函数式编程中的一个重要概念,它可以轻易把多参数函数map到单参数函数上,组成所有参数后最终惰性求值。
柯里化的方法可以把参数的传递提供给外界,而不用关心具体赋参数的过程,有助于减少代码间的耦合性。
func plus(first first: Int) -> (second: Int) -> (third: Int) -> Int {
return { second -> Int -> Int in
{ third in
return first + second + third
}
}
}
let sum0 = plus(first: 10)
//somewhere
let sum1 = sum0(second: 11)
//some another where
let 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了。