iOS 9 by Tutorials 笔记(一)

最近入了 Raywenderlich 家的 《iOS 9 by Tutorials》,来刷下书做个笔记吧,感兴趣请支持正版


Chapter 1: Swift 2.0

第一章主要介绍了 Swift 2.0

一、Control Flow

1、repeat

do/while 变成了 repeat/while,语义更加明确

var jamJarBeer = Beer()  
repeat {  
  jamJarBeer.sip()
} while (!jamJarBeer.isEmpty) // 啤酒不为空就喝到空为止

2、guard

这个是条件预判断,也没啥好说的,Swift 的创始人 Chris Lattner 在 WWDC 2015 上说推出新关键字 guard 的原因是: 在 early exit 时觉得用 if let 要缩进太丑了

struct Beer {  
  var percentRemaining = 100
  var isEmpty: Bool { return percentRemaining <= 0 }
  var owner: Patron?
  mutating func sip() {
    guard percentRemaining > 0 else {
      print("Your beer is empty, order another!")
      return
    }
    percentRemaining -= 10
    print("Mmmm \(percentRemaining)% left")
  }
}

二、Error handling

本质上一个纯粹 Swift 错误(A pure Swift error),可以看做是遵循 ErrorType 协议的 enum。了解到这一点,我们就可以定制自己的 error type。

一般 Error handling 也记住三步:

  1. 创建自己的 ErrorType

    enum ParseError: ErrorType {
        case MissingAttribute(message: String)
    }
    
  2. 声明一个方法会 throw 错误,然后在实现中 throw 第一步定义的 errorType 枚举对象具体的错误分支(case)

    struct Person: JSONParsable {
        let firstName: String
        let lastName: String
        static func parse(json: [String : AnyObject]) throws
          -> Person {
            guard let firstName = json["first_name"] as? String else {
                let message = "Expected first_name String"
                throw ParseError.MissingAttribute(message: message) 
          // 1
            }
            guard let lastName = json["last_name"] as? String else {
                let message = "Expected last_name String"
                throw ParseError.MissingAttribute(message: message) 
          // 2
            }
            return Person(firstName: firstName, lastName: lastName)
        }
    }
    
  3. 在 do/try/catch 中调用第二步创建的这个方法,并捕获错误

    do {
        let person = try Person.parse(["foo": "bar"])
    } catch ParseError.MissingAttribute(let message) {
        print(message)
    } catch {
        print("Unexpected ErrorType")
    }
    

如果你能保证不出错误,也可以用 try!

    let p1 = try! Person.parse(["foo": "bar"])

三、The Project

1、String Validation Error

①.字符串验证协议

// 字符串验证规则
protocol StringValidationRule {  
  func validate(string: String) throws -> Bool
  var errorType: StringValidationError { get }
}

为了使用多个规则一起用,再定义一个 StringValidator 协议

protocol StringValidator {  
  var validationRules: [StringValidationRule] { get }
  // 这种返回值是一个元组,带 errors 信息,就不用 throw 了
  func validate(string: String) -> (valid: Bool,
    errors: [StringValidationError])
}

现在 Protocol Extensions 里可以直接写协议的方法实现了,只要遵循了该协议将自动享受到这些实现,比如在下面的 Protocol Extensions 中实现 validate 方法

extension StringValidator {  
  func validate(string: String) -> (valid: Bool,
    errors: [StringValidationError]) {
    var errors = [StringValidationError]()
    for rule in validationRules {
      do {
        try rule.validate(string)
      } catch let error as StringValidationError {
        errors.append(error)
      } catch let error {
        fatalError("Unexpected error type: \(error)")
      }
    }
    return (valid: errors.isEmpty, errors: errors)
  } 
}

②.具体的验证实例 以 xx 类型字符开头的验证规则,遵循 StringValidationRule 协议

struct StartsWithCharacterStringValidationRule  
  : StringValidationRule {
  let characterSet: NSCharacterSet
  let description: String

  var errorType: StringValidationError {
    return .MustStartWith(set: characterSet,
      description: description)
  }

  func validate(string: String) throws -> Bool {
    if string.startsWithCharacterFromSet(characterSet) {
      return true
    } else {
      throw errorType
    } 
  }
}

调用看输出

let letterSet = NSCharacterSet.letterCharacterSet()  
let startsWithRule = StartsWithCharacterStringValidationRule(  
  characterSet: letterSet,
  description: "letter")
do {  
  try startsWithRule.validate("foo")
  try startsWithRule.validate("123")
} catch let error {
  print(error)
}

必须以某种字符结尾的验证规则

struct EndsWithCharacterStringValidationRule  
  : StringValidationRule {
  let characterSet: NSCharacterSet
  let description: String
  var errorType: StringValidationError {
    return .MustEndWith(set: characterSet,
      description: description)
}
  func validate(string: String) throws -> Bool {
    if string.endsWithCharacterFromSet(characterSet) {
      return true
    } else {
      throw errorType
    }
  } 
}

结合上面两条 rules(StartsWithCharacterStringValidationRule,EndsWithCharacterStringValidationRule)创建一个 StartsAndEndsWithStringValidator,验证开头和结尾的字符

struct StartsAndEndsWithStringValidator: StringValidator {  
  let startsWithSet: NSCharacterSet             
  let startsWithDescription: String
  let endsWithSet: NSCharacterSet               
  let endsWithDescription: String
  var validationRules: [StringValidationRule] { 
    return [
      StartsWithCharacterStringValidationRule(
        characterSet: startsWithSet,
        description: startsWithDescription),
      EndsWithCharacterStringValidationRule(
        characterSet: endsWithSet ,
        description: endsWithDescription)
    ] 
  }
}

其实 StringValidator 协议还有个 validate 方法,但是在之前的 extension 方法已经实现了

验证:以字母开头,数字结尾

let numberSet = NSCharacterSet.decimalDigitCharacterSet()

let startsAndEndsWithValidator =  
  StartsAndEndsWithStringValidator(
    startsWithSet: letterSet,
    startsWithDescription: "letter",
    endsWithSet: numberSet,
    endsWithDescription: "number")

startsAndEndsWithValidator.validate("1foo").errors.description  
startsAndEndsWithValidator.validate("foo").errors.description  
startsAndEndsWithValidator.validate("foo1").valid  

2、Password Requirement Validation

现在将 StringValidator 投入到实际的工作中,用来验证密码是否符合规范

  • 密码长度至少 8 位
  • 必须包含一个大写字母
  • 必须包含一个小写字母
  • 必须包含一个数字
  • 必须包含一个以下特殊字符:"!@#$%^&*()_-+<>?/[]{}"

首先来创建一条验证字符串长度的 LengthStringValidationRule

public struct LengthStringValidationRule  
  : StringValidationRule {
  public enum Type {
    case Min(length: Int)
    case Max(length: Int)
  }
  public let type: Type
  public var errorType: StringValidationError { get }
  public init(type: Type)
  public func validate(string: String) throws -> Bool
}

接着创建验证包含某些字符的 ContainsCharacterStringValidationRule

public struct ContainsCharacterStringValidationRule  
  : StringValidationRule {
  public enum Type {
    case MustContain
    case CannotContain
    case OnlyContain
    case ContainAtLeast(Int)
  }
  public let characterSet: NSCharacterSet
  public let description: String
  public let type: Type
  public var errorType: StringValidationError { get }
  public init(characterSet: NSCharacterSet,
    description: String,
    type: Type)
  public func validate(string: String) throws -> Bool
}

两条 rules 在手,下面我们来验证密码的有效性:

struct PasswordRequirementStringValidator: StringValidator {  
  var validationRules: [StringValidationRule] {
    let upper = NSCharacterSet.uppercaseLetterCharacterSet()
    let lower = NSCharacterSet.lowercaseLetterCharacterSet()
    let number = NSCharacterSet.decimalDigitCharacterSet()
    let special = NSCharacterSet(
      charactersInString: "!@#$%^&*()_-+<>?/\\[]}{")
    return [
      LengthStringValidationRule(type: .Min(length: 8)),
      ContainsCharacterStringValidationRule(
        characterSet:upper ,
        description: "upper case letter",
        type: .ContainAtLeast(1)),
      ContainsCharacterStringValidationRule(
        characterSet: lower,
        description: "lower case letter",
        type: .ContainAtLeast(1)),
      ContainsCharacterStringValidationRule(
        characterSet:number ,
        description: "number",
        type: .ContainAtLeast(1)),
      ContainsCharacterStringValidationRule(
        characterSet:special,
        description: "special character",
        type: .ContainAtLeast(1))
    ] 
  }
}

验证:

let passwordValidator = PasswordRequirementStringValidator()  
passwordValidator.validate("abc1").errors.description  
passwordValidator.validate("abc1!Fjk").errors.description  

四、Additional Things

1、Going further with Extensions

我们可以在某些类型上通过 Extensions 添加一些方法实现,比如在数组类型上添加一个 shuffles 方法,对数组内的元素进行随机排序

extension MutableCollectionType where Index == Int {  
  mutating func shuffleInPlace() {
    let c = self.count
    for i in 0..<(c-1) {
      let j = Int(arc4random_uniform(UInt32(c - i))) + i
      guard i != j else { continue }
      swap(&self[i], &self[j])
     } 
  }
}

验证:

// 因为数组遵循 MutableCollectionType 协议
var people = ["Chris", "Ray", "Sam", "Jake", "Charlie"]
people.shuffleInPlace()  

Extending functionality to generic type parameters is only available to classes and protocols.

2、Using defer

使用 defer { ... } 来确保 defer 紧跟的代码块在离开当前范围之前总是被执行,这里书上举了个 ATM 机的例子:

struct ATM {  
  var log = ""

  mutating func dispenseFunds(amount: Float,
    inout account: Account) throws {

    defer {
      log += "Card for \(account.name) has been returned " +
        "to customer.\n"
      ejectCard()
    }

    log += "====================\n"
    log += "Attempted to dispense \(amount) from " +
      "\(account.name)\n"

    guard account.locked == false else {
      log += "Account Locked\n"
      throw ATMError.AccountLocked
    }

    guard account.balance >= amount else {
      log += "Insufficient Funds\n"
      throw ATMError.InsufficientFunds
    }

    account.balance -= amount
    log += "Dispensed \(amount) from \(account.name)."
    log += " Remaining balance: \(account.balance)\n"
  }

  func ejectCard() {
    // physically eject card
  } 
}

在 ATM 机这个例子中,多个地方会对账户和取钱金额进行检查,不合适就会出错退出,我们这里用 defer 来保证用户的卡一定会被退回,即 ejectCard() 方法一定会被执行

验证一个被锁定的帐号:

do {  
  try atm.dispenseFunds(200.00, account: &billsAccount)
} catch let error {
  print(error)
}

虽然帐号被锁了,但卡还是退回来了

3、Pattern Matching

  • 在 swift 2.0 中 for...in 循环和 where 可以一起用

    var namesThatStartWithC = [String]()
    
    
    for cName in names where cName.hasPrefix("C") {
        namesThatStartWithC.append(cName)
    }
    
  • for...in 和 case 结合同样可以用在枚举集合里了

    var totalDaysLate = 0
    // 遍历枚举集合,针对特定 case 过滤出
    for case let .Late(daysLate) in authorStatuses {
        totalDaysLate += daysLate
    }
    
  • if case 可以直接用来判断某个枚举对象属于哪一分支,不用写switch,更不用 default

    var slapLog = ""
    for author in authors {
        if case .Late(let daysLate) = author.status where daysLate > 2 {
            slapLog += "Ray slaps \(author.name) around a bit " +
            "with a large trout.\n"
        } 
    }
    

4、Option Sets

你现在可以创建自己的 option set,其实就是创建一个遵循 OptionSetType 协议的结构体

struct RectangleBorderOptions: OptionSetType {  
  let rawValue: Int
  init(rawValue: Int) { self.rawValue = rawValue }
  static let Top = RectangleBorderOptions(rawValue: 0)
  static let Right = RectangleBorderOptions(rawValue: 1)
  static let Bottom = RectangleBorderOptions(rawValue: 2)
  static let Left = RectangleBorderOptions(rawValue: 3)
  static let All: RectangleBorderOptions =
    [Top, Right, Bottom, Left]
}

5、OS Availability

swift 2.0 可以让编译器帮你检查系统版本了

guard #available(iOS 9.0, *) else { return }
// do some iOS 9 or higher only thing

-EOF-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!