Skip to content

Latest commit

 

History

History
685 lines (468 loc) · 22.5 KB

ThinkingInSwift.md

File metadata and controls

685 lines (468 loc) · 22.5 KB

#Thinking in Swift

从Objective-C到Swift,我们往往脑袋里还带着旧的一套编程套路。为了利用Swift写出更优雅,更健壮的代码。让我们用初心者的心态来学习新的编程范式,新的可能。

目录:

1.拥抱Optional,远离Crash

Swift引进了一个新的概念Optional,以及相关的一系列语法:

  • ?!                声明可选类型
  • if let                 可选绑定(Optional binding)
  • guard                   提前退出(Early Exit)
  • as?as!       向下转型(Downcasting)
  • ??                         空合运算符(Nil Coalescing Operator)

####什么是可选(Optional)类型?
可选类型代表的概念是要么这里有一个值,要么这里什么都没有。

实际上,Optional是一个枚举类型。Swift开源后,我们能看到Optional的实现

public enum Optional<Wrapped> : NilLiteralConvertible {
  case None
  case Some(Wrapped)

  public init() { self = .None }
  public init(_ some: Wrapped) { self = .Some(some) }
}

声明一个Optional的语法:

var strValue: String?   //?相当于下面这种写法的语法糖
var strValue: Optional<String>

因此声明一个Optional类型,实际上是声明一个枚举类型,这个枚举类型包括两种情况,一个是None,另一个是你声明的类型。

正因如此,我们在使用可选类型时,需要先判定这个Optional是属于哪种case。这个时候就需要用到可选绑定(Optional binding)这个语法来unwarp,然后再使用。

var strValue:String?
if let str = strValue {
    print(str)
}

####为什么需要可选类型?

Optionals are an example of the fact that Swift is a type safe language. ———《The Swift Programming Language》

基本上所有语言(C,C++,Objective-C,C#...)的类型系统都包括Null这个概念,但都没有相应正确的处理机制。连创建这个概念的Tony Hoare也把这个错误称为billion dollar mistake。因为它导致的编程错误累计起来造成了极大的损失。

比如在Objective-C中,虽然在OC中对空对象发送消息不会导致crash。但是有许多情况下对空对象操作会导致Crash,比如向数组插入空对象等许多操作操作。(详情查阅:Crash in Cocoa

如果一个用户量巨大的应用出现了相关的Bug,的确会造成不少直接经济损失,而Debug等流程也意味着时间成本的支出。而Swift的Optional机制正是要避免Null这个概念所导致的错误。

Swift处理空值的理念就是: 一个值要么有值,要么就是optional类型。而optional类型要么有值,要么没有值,对optional类型操作前一定要判断有值之后才能操作,如果没有判断,则编译器会报错。

在Swift的世界里,如果我们不声明一个对象为Optional,则它一定是有值的。这一点是非常有价值的,避免了我们对空对象进行操作。

更重要的是,我们更希望达到一种状态,就是我操作对象和数据的时候,我能够确信它不为空。这种哲学避免了许多冗杂无用的判断Nil操作,同时也大大减少了忘记判断nil导致的crash。

####如何使用可选类型?

那Swift究竟通过什么途径来保证一个对象如果不是Optional,他就一定有值呢?

  1. 如果我们使用了没被初始化的变量和常量,编译器会抛出error。

    var testString:String
    print(testString)
    //error: variable 'testString' used before being initialized
  2. 类和结构体的实例在创建时,一定要为所有存储型属性设置初始值。我们可以在initializer或是声明属性的时候为其设置默认值,否则编译器会抛出error.

    //error:return from initializer without initializing all stored properties
    class testClass {
    var a:String
        init(){ 
        }
    }
    
    //pass
    class testClass {
    var a:String = ""
    init(){}
    }
    
    //pass
    class testClass {
    var a:String
    init(){
        self.a = ""
    }}

那我们如何在Swift中使用Optional呢?

  • ?!                声明可选类型

     var optionalInt:Int?

    首先,在Objective-C中,nil仅针对对象,对于结构体,枚举类型等类型来说,是没有nil的。我们不能直接判断它们是否有值。在OC中,nil是一个指向不存在对象的指针,在Swift中,nil不是指针,它是一个确定的值,用来表示值为空。 (正如前面的Optional源代码所示,它是一个枚举值)

    Swift的Optional让我们能明确地标注一个值有没有可能为空。并且值的类型没有限制。

  • if let                 可选绑定(Optional binding)

    Swift的Optional Binding机制确保我们在使用Optional值时先判断值是否为空。

    在Objective-C中,判空操作不是强制和必须的。判空是一个良好的习惯,但是因为没有约束和规范。很多时候判空操作都被遗漏了,导致了许多潜在的问题。

    但在Swift中,我们在使用一个Optional值之前必须先unwarp它。

    语法:

     if let constant = someOptional {
       //use constant
     }

  • guard                   提前退出(Early Exit)

    在编程规范中有个名字好听的黄金大道(Golden Path)的准则。

    就是我们在编程的过程中使用条件语句时,代码的左边应该是一条Golden Path。也就是避免过多的if嵌套,可以合理地通过return提前退出。

    也就是:

     //推荐:
     func someMethod() {
      if someBoolValue == false {
      	return
      }
     // Do something important
     }
     //不推荐
     func someMethod() {
      if someBoolValue == true {
    	 	// Do something important
      }
     }

    而Swift中,我们可以用Guard来提前退出,且对Optional进行unwarp的操作。

     guard let constant = someOptional else {
     	return
     }
     //可以直接操作constant

    考虑我们用if来操作

     if someOptional == nil {
     	return
     }
     //仍然需要unwrap optional

    在这里用if,首先是语义上面不够清晰,通常我们使用if是想检查我们想要的情况,但在这里变成检查我们不想要的情况,然后提前退出。其次是检查过后,我们仍然需要去unwrap optional。

    而使用guard,语义更清晰,关键词gurad就是专门用于提前退出,并且能够在检查的过程中unwarp optional,并在后面的流程中使用。

  • as?as!       向下转型(Downcasting)

     if let subclassObject = superclassObject as? subclass {
     } 

  • ??                         空合运算符(Nil Coalescing Operator)

    空合运算符是对三目运算符其中一个用例的简化,相当于:

     a != nil ? a! : b

    空合运算法提供了一种更优雅的语法来表示判空,unwarp和提供新选项三者的结合。

     var optionalName: String?
     let defaultName = "Mango"
     var name = optionalName ?? defaultName
     //相当于
     var name = optionlName != nil ? optionlName! : defaultName


参考:
why use optional let

Optional源代码

Swift之 ? 和 !

Swift Guard Statement

编程的智慧

2.学习泛型。抽象的魅力。

泛型编程,简单地总结。就是让我们在保持type safety的同时写出不局限于单一类型的代码,也即灵活与安全。

###泛型函数(Generic Functions):

举个最简单的例子:交换。对比以下两种写法,一种是只针对Int类型的交换。而我们用泛型改写后,适用于其它所有类型。

func oldSwap(inout a:Int ,inout _ b:Int){
    let temp = a
    a = b
    b = temp
}

func genericSwap<T>(inout a:T,inout _ b:T){
    let temp = a
    a = b
    b = temp
}

泛型在提供灵活抽象的同时,也保持了类型安全,占位类型T代表了一种类型,使得交换限制在同种类型上,比如我们尝试交换数字和字符串swap(1,"2"),那么编译器就会报错。

###泛型类型(Generic Types):

Swift也允许我们自定义自己的泛型类型,像Swift标准库提供的Array,Dictionary都是泛型类型。

泛型类型一般是容器类型,利用好它,我们能创造出灵活通用的类型。

这里举一个简单但却强大的例子:

在MVVM模式里,有个很重要的核心内容就是如何将ViewModel里的变化传递给View进行更新,苹果官方在iOS上没有提供一个绑定机制,我们可以使用delegate,KVO,Notification等系统途径来通知View进行更新,但很多时候都显得非常繁琐。

在这里我们简单地造一个绑定工具。

class DynamicString {
  typealias Listener = String -> Void
  var listener: Listener?

  func bind(listener: Listener?) {
    self.listener = listener
  }

  var value: String {
    didSet {
      listener?(value)
    }
  }

  init(_ v: String) {
    value = v
  }
}

上面这段代码很简单,利用了Swift的property observer,在每次Model设置的时候,调用闭包,我们可以在闭包里面做视图的更新,用起来像是这样:

let nameModel = DynamicString("Mango")
let nameLabel = UILabel()

nameModel.bind{ nameLabel.text = $0 }
nameModel.value = "100Mango"  //修改model的值
print(nameLabel.text) // 输出 "100mango" , UI的值得到了更新

我们很快就能够发现问题所在,对于一个String类型,我们要创造一个DynamicString。那么Int,NSdate...等等各种类型呢?

这个时候我们便可以利用Swift的泛型类型进行改造:

class Dynamic<T> {
  typealias Listener = T -> Void
  var listener: Listener?

  func bind(listener: Listener?) {
    self.listener = listener
  }
  
  var value: T {
    didSet {
      listener?(value)
    }
  }

  init(_ v: T) {
    value = v
  }
}

通过上面我们的改造,我们的简单绑定机制就能对各种类型的数据都起作用。

let text = Dynamic("Steve")
let bool = Dynamic(false)
let Int =  Dynamic(1)

参考引用:bindings-generics-swift-and-mvvm

###类型约束(Type Constraints):

我们上面的泛型函数inout和泛型类型Dynamic能够应用到任何类型中。但是有时候,对泛型函数和泛型方法应用的类型进行约束,会非常的有用。

类型约束可以指定一个类型参数(Type Parameters)继承自指定类,或者遵循某个协议或一系列协议。

语法:

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
}
  • 泛型函数的类型约束:

比如我们要自己写一个方法找出数组中最大的元素

Swift2:已引入了maxElement方法 :)

func findLargestInArray<T : Comparable>(array: Array<T>) -> T? {
    
    guard array.count > 0 else{
        return nil
    }
    
    var largest : T = array[0]
    for i in 1..<array.count {
        largest = array[i] > largest ? array[i] : largest
    }
    return largest
}

注意到这里我们标注了T : Comparable,这是因为只有遵循Comparable协议的类型才能够进行比较,如果我们在这里没有进行类型约束,则直接会编译不通过,因为不是所有类型都能进行大小的比较。类型约束在这里让我们的泛型方法变得更安全和更有针对性。

  • 泛型类型的类型约束:

比如Swift的字典的定义:

struct Dictionary<Key : Hashable, Value>

我们看到Dictionary的Key被约束为遵循Hashable协议。学过数据结构的我们知道,字典实际上是一个哈希表hash table。字典的键需要遵循Hashable协议,否则我们不能得到哈希表相关的插入,查找等特性。因此Dictionary这个泛型类型,需要通过类型约束来限制它的类型参数Key遵循Hashable协议。

###关联类型(Associated Types):

我们在上面看到了在函数和类型中的泛型编程,而Protocol作为Swift中重要的组成部分,自然也是支持泛型这个编程概念的。

protocol Container {
    associatedtype ItemType
    mutating func append(item: ItemType)
}

假设我们定义了一个Container协议,我们希望遵循我们协议的类型能够实现一个添加新元素的功能。我们希望这个协议是广泛使用的,不限制元素的类型。这里我们通过associatedtype这个关键词来声明关联类型。等到实现协议的时候再去确定真正的类型。

class myContainer:Container{
    associatedtype ItemType = String
    func append(item: String) {
    }
}

我们简单地实现了一个遵循Container协议的类,我们确定了类型是String。在这里

associatedtype ItemType = String

这一句代码是可以不写的,Swift的类型推导系统能够在append方法的参数类型里获得ItemType的具体类型。

#####为Associated Types添加约束

  • 强大的where语句

    在上面我们可以通过类型约束(Type Constraints)来对泛型函数和泛型类型的类型参数进行约束。那么我们如何对关联类型进行约束呢?

    语法:

     func allItemsMatch<C1: Container, C2: Container
     where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
     (someContainer: C1, anotherContainer: C2) -> Bool {
     	// 检查两个容器含有相同数量的元素
         if someContainer.count != anotherContainer.count {
             return false
         }
         // 检查每一对元素是否相等
         for i in 0..<someContainer.count {
             if someContainer[i] != anotherContainer[i] {
                 return false
             }
         }
         // 所有元素都匹配,返回 true
         return true
     }

    我们通过where语句来声明限制关联类型的语句,我们在类型参数列表后面加where子句。

    在上面的例子中,我们实现一个函数来确保两个遵循Container协议的类型包含的元素顺序和内容全部相同。

     where C1.ItemType == C2.ItemType, C1.ItemType: Equatable

    我们通过where子句限制了比较的两者的关联类型ItemType必须是相同类型,且是能够比较的。

    • Constrained Extensions

      我们还能够使用where子句在编写扩展的时候对泛型进行限制:

      1. 扩展泛型类型时,对类型参数进行限制。
      2. 扩展协议时,对关联类型进行限制。

      灵活地运用这个特性能够编写出很多实用巧妙的扩展。

      比如我们扩展泛型类型:

       extension Array where Element == CGPoint {
       	    var path : CGPath {
               let bezier = UIBezierPath()
               
               if self.count > 0 {
                   bezier.moveToPoint(self[0])
               }
               
               for point in self{
                   bezier.addLineToPoint(point)
               }
               
               return bezier.cgPath
           }
       }

      能够直接将CGFloat的数组转换为CGPathRef

      又比如我们扩展协议:

       extension CollectionType where Self.Generator.Element : Comparable {
           func largestElement() -> Self.Generator.Element?{
               guard self.count > 0 else {
                   return nil
               }
               
               var largest = self.first
               for element in self{
                   if largest < element {
                       largest = element
                   }
               }
               return largest
           }
       }

      在这里我们扩展了CollectionType协议,实现了类似我们上面的泛型方法 findLargestInArray的类似功能,但不同的是我们的使用范围更广了,所有遵循 CollectionType的类型都能够获得这样的功能,而Array也是遵循CollectionType协议的。


参考引用:

swift-generics-pt-2

The Swift Programming Language

swift-type-constrained-extensions-express-yourself

Concrete same-type requirements

3.Protocol Oriented Programming

protocol-oriented programming 的核心在于用组合替代继承(也就是面向对象编程准则:prefer composition over inheritance)。

初次学习面向协议编程,不要感到面对得是一个新的庞大的编程概念和体系,我们直接先从语法入手。看看面向协议编程在代码里面究竟是个什么东西。

protocol hello {
 func hello() -> String
}

//protocol extension
extension hello {
    func hello() -> String {
     return "hello world!"
    }
}

//Make an existing type conform to a protoco
extension String: hello {
	func hello() -> String {
		return "hello world"
	}
}

其实面向协议编程的语法基础就是我们熟知的protocol语法, Swift2.0引进的protocol extension(为protocol添加默认实现), 还有extension语法,使一个已有类型符合某个协议。

protocol extension 能够解决一些继承带来的问题。

  1. 庞大的基类

    mixins-and-traits-in-swift-2

  2. 非常深的继承树

  3. 类只能继承一个父类,多重继承有弊病。

    Multiple Inheritance vs. Traits or Protocol Extensions

Protocol Extension 独特的优势:各种Value Type 和 Class 都能使用 Protocol Extension。并且能够对 protocol extension 进行Constraint。

Is there a difference between Swift 2.0 protocol extensions and Java/C# abstract classes?

Swift就使用了Protocol Extension来改进自己的标准库。

例如在Swift1.2, map这个高阶函数是通过Extension实现的。因此每个CollectionType都需要自己实现一遍这些方法。

// Swift 1.2
extension Array : _ArrayType {
  func map<U>(transform: (T) -> U) -> [U]
}

而通过在Swift2开始引入的 Protocol Extension, 直接扩展了CollectionTypeProtocol,提供了默认实现。这样子所有遵循CollectionType的类型都拥有了map,不需要一个个单独实现。

extension CollectionType {
public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]

参考资料:

introducing-protocol-oriented-programming-in-swift-2

Practical Protocol-Oriented-Programming

4.函数式编程(Functional Programming)

什么是函数式编程?

纵览网上给出的各种定义。函数式编程指的是具有以下特点的编程范式:

事实上,个人觉得很难给函数式编程下一个明确的定义。

但是我们可以从另外一个角度出发,就是函数式编程通过什么样的语法特性体现出来,怎么样的编程范式和代码是函数式的来体会和学习函数式编程。避免空谈概念, Let's get our hands dirty.

高阶函数

reduce,filter,map,flapmap,forEach,zip

Map:

//循环版本:
  var bubbleModels = [BubbleModel]()
   for bubble in bubbles {
   		bubbleModels.append(bubble.bubbleModel)
   }
   return bubbleModels
//高阶函数:        
bubbleModels = bubbles.map({ $0.bubbleModel })

FlatMap:

//循环版本:
  var bubbles = [BubbleView]()
   for view in self.subviews {
	   	if let bubbleView = view as? BubbleView {
	   		bubbles.append(bubbleView)
	   	}
   	}

//高阶函数:
bubbles = self.subviews.flatMap({ $0 as? BubbleView })

Reduce与链式用法:

let detector = CIDetector(ofType: CIDetectorTypeFace, context: context, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
let faces = detector.featuresInImage(image)

//循环版本
var maskImage: CIImage?
for face in faces {
    let parameters = //...获取脸部位置,构造滤镜参数
    let radialGradient = CIFilter(name: "CIRadialGradient", withInputParameters: parameters)
    if let circleImage = radialGradient?.outputImage {
        if let oldMaskImage = maskImage {
            maskImage = sourceOver(circleImage)(oldMaskImage)
        }else{
            maskImage = circleImage
        }
    }
}

//高阶函数:
let mask = faces.flatMap({ face in
    let parameters = //...
    let radialGradient = CIFilter(name: "CIRadialGradient", withInputParameters: parameters)
    return radialGradient?.outputImage
}).reduce(CIImage(), combine: { sourceOver($0)($1) })

zip: 同时对两个sequnece进行操作

  for (points, var photoModel) in zip(collageModel.areas, photoModels) {
            photoModel.points = points
            let collageContentView = CollageContentView(model: photoModel)
            self.addSubview(collageContentView)
            collageContentView.setup()
        }

参考:

Swift源代码: map reduce,flatmap