最近入了 Raywenderlich 家的 《iOS 9 by Tutorials》 ,来刷下书做个笔记吧,感兴趣请支持正版
第一章主要介绍了 Swift 2.0
do/while
变成了 repeat/while
,语义更加明确
var jamJarBeer = Beer() repeat { jamJarBeer.sip() } while (!jamJarBeer.isEmpty) // 啤酒不为空就喝到空为止
这个是条件预判断,也没啥好说的,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") } }
本质上一个纯粹 Swift 错误(A pure Swift error),可以看做是遵循 ErrorType 协议的 enum 。了解到这一点,我们就可以定制自己的 error type。
一般 Error handling 也记住三步:
创建自己的 ErrorType
enum ParseError: ErrorType { case MissingAttribute(message: String) }
声明一个方法会 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) } }
在 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"])
// 字符串验证规则 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
现在将 StringValidator 投入到实际的工作中,用来验证密码是否符合规范
首先来创建一条验证字符串长度的 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
我们可以在某些类型上通过 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.
使用 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) }
虽然帐号被锁了,但卡还是退回来了
在 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" } }
你现在可以创建自己的 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] }
swift 2.0 可以让编译器帮你检查系统版本了
guard #available(iOS 9.0, *) else { return } // do some iOS 9 or higher only thing