Swift三剑客:闭包、泛型和协议
一、闭包
如何看闭包,晕
参考文章:谈一谈闭包
1、闭包定义:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function
closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外
2、闭包的必要条件
闭包形成的必要条件
1、函数引用自由变量
2、函数的执行环境和自由变量的声明环境不同
两个重点是自由变量,引用环境
3、自由变量
值捕获一:
var a = 3
func add5(b:Int) -> Int {
a += b
return a
}
print(add5(b: 2))
输出5
值捕获二:
func makeIncrementor(amount: Int) -> () -> Int {
var runningTotal = 0
withUnsafePointer(to: &runningTotal) {print($0)}#输出变量地址
func incrementor() -> Int {
runningTotal += amount
withUnsafePointer(to: &runningTotal) {print($0)}
return runningTotal
}
return incrementor
}
let a = makeIncrementor(amount:10)
print(a())
print(a())
let b = makeIncrementor(amount:10)
print(b())
闭包外的runningTotal地址:0x000060400022fdf0
闭包内的runningTotal地址:0x000060400022fdf0
10
第二次执行:0x000060400022fdf0
20
--------------------------------------
闭包外的runningTotal地址:0x000060400022fe30
闭包内的runningTotal地址:0x000060400022fe30
10
题外话:
函数或者闭包格式:() → 类型
去哪里都是这个格式
先去找最里面一层,看哪一层符合这个格式,再扩大寻找外面一层
4、闭包作用
1、改变自由变量生命周期
通过概念和实例代码, 很明显闭包的存在改变了变量的生命周期, 大部分情况下它可以将自由变量的生命周期延迟到闭包函数的执行, 而函数式中最重要的一个思想是尽可能多使用纯函数(纯函数是指对于相同的输入必定有相同的输入的函数), 在纯函数中如果想要保持一个变量, 那闭包肯定是最佳选择
2、柯里化
什么是柯里化?
柯里化(英语:Currying),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
let add = { (x, y) -> Int in
return x + y
}
print(add(10,20))
print(add(10,30))
print(add(10,50))
上面对add函数的调用其实都是10+y的形式, 很多时候我们为了封装, 又对10+y这样的式子进行封装
var add = { (x) -> Int in
return x + 10
}
这里有一个不好的地方就是add10这个函数的封装只能适用于10+y, 虽然实现了柯里化, 但是对于使用者来说灵活性不够, 其实这里我们可以利用闭包对add函数稍加改造, 既方便使用又不失灵活性
let add = { (x) -> ((Int) -> Int) in
return {(y) -> Int in
return x+y
}
}
let add10 = add(10)
print(add10(30))
print(add10(30))
print(add10(50))
5、如何简略闭包?
详见文章:使用闭包简化语法
5、闭包种类
尾随闭包
如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:
func someFunctionThatTakesAClosure(closure: () -> Void) {
// 函数体部分
}
// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
// 闭包主体部分
})
// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
// 闭包主体部分
}
逃逸闭包
当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping
,用来指明这个闭包是允许“逃逸”出这个函数的。
一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
二、面向协议
1、总结:
用完全新的眼光去看协议,遵守协议取代子类继承
2、协议语法:
2、Swift Protocol 详解 - 协议&面向协议编程
3、协议
// 协议的定义
protocol Pet{
// 对于属性,不能有初始值
var name: String{ get set }// = "My Pet"
// 统一使用var关键字
var birthPlace: String{ get }
// 对于属性,get,set隐藏了实现细节,可以使用let实现只读,也可以使用只有get的计算型属性
// 对于方法,不能有实现
func playWith()
// 对于方法,不能有默认参数(默认参数就是一种实现)
//func fed(food: String = "leftover")
func fed()
func fed(food: String)
}
// 协议的继承
protocol PetBird: Pet{
var flySpeed: Double{ get }
var flyHeight: Double{ get }
}
// 协议的实现,实现协议规定的所有属性和方法即可
struct Dog: Pet{
var name: String
var birthPlace: String
func playWith() {
print("Wong!")
}
func fed(){
print("I want a bone.")
}
// 在具体实现上可以加默认参数
func fed(food: String = "Bone"){
if food == "Bone"{
print("Happy")
}else{
print("I want a bone")
}
}
}
3、协议的最大用处:
1、回顾面向对象
class Animal {
var leg: Int { return 2 }
func eat() {
print("eat food.")
}
func run() {
print("run with \(leg) legs")
}
}
class Tiger: Animal {
override var leg: Int { return 4 }
override func eat() {
print("eat meat.")
}
}
let tiger = Tiger()
tiger.eat() // "eat meat"
tiger.run() // "run with 4 legs"
我们看到 Tiger
和 Animal
共享了一部分代码,这部分代码被封装到了父类中,而除了 Tiger
的其他的子类也能够使用 Animal
的这些代码。这其实就是 OOP 的核心思想 - 使用封装和继承,将一系列相关的内容放到一起。
虽然我们努力用面向对象来抽象和继承的方法进行建模,但是实际的事物往往是一系列特质的组合,而不单单是以一脉相承并逐渐扩展的方式构建的。
面向对象的三个困境:
- 横切关注点
- 菱形缺陷
- 动态派发安全性
横切关注点:
面向对象
class ViewCotroller: UIViewController
{
// 继承
// 新加
func myMethod() {
}
}
class AnotherViewController: UITableViewController
{
// 继承
// 新加
func myMethod() {
}
}
由上看出,很难在不同继承关系的类里共用代码。这里的问题用“行话”来说叫做“横切关注点” (Cross-Cutting Concerns)。我们的关注点 myMethod
位于两条继承链 (UIViewController
-> ViewCotroller
和 UIViewController
-> UITableViewController
-> AnotherViewController
) 的横切面上。
那么POP如何来解决?协议扩展
protocol P {
func myMethod()
}
// class ViewController: UIViewController
extension ViewController: P {
func myMethod() {
doWork()
}
}
// class AnotherViewController: UITableViewController
extension AnotherViewController: P {
func myMethod() {
doWork()
}
}
所谓协议扩展,就是我们可以为一个协议提供默认的实现。对于 P
,可以在 extension P
中为 myMethod
添加一个实现:
protocol P {
func myMethod()
}
extension P {
func myMethod() {
doWork()
}
}
有了这个协议扩展后,我们只需要简单地声明 ViewController
和 AnotherViewController
遵守 P
,就可以直接使用 myMethod
的实现了:
extension ViewController: P { }
extension AnotherViewController: P { }
viewController.myMethod()
anotherViewController.myMethod()
动态派发安全性
ViewController *v1 = ...
[v1 myMethod];
AnotherViewController *v2 = ...
[v2 myMethod];
NSArray *array = @[v1, v2];
for (id obj in array) {
[obj myMethod];
}
我们如果在 ViewController 和 AnotherViewController 中都实现了 myMethod 的话,这段代码是没有问题的。myMethod 将会被动态发送给 array 中的 v1 和 v2。但是,要是我们有一个没有实现 myMethod 的类型,会如何呢?
```
NSObject *v3 = [NSObject new]
// v3 没有实现 `myMethod`
NSArray *array = @[v1, v2, v3];
for (id obj in array) {
[obj myMethod];
}
// Runtime error:
// unrecognized selector sent to instance blabla
// 编译依然可以通过,但是显然,程序将在运行时崩溃。Objective-C 是不安全的,编译器默认你知道某个方法确实有实现,这是消息发送的灵活性所必须付出的代价
struct Person: Greetable {
let name: String
func greet() {
print("你好 \(name)")
}
}
struct Cat: Greetable {
let name: String
func greet() {
print("meow~ \(name)")
}
}
let array: [Greetable] = [
Person(name: "Wei Wang"),
Cat(name: "onevcat")]
for obj in array {
obj.greet()
}
// 你好 Wei Wang
// meow~ onevcat
对于没有实现 Greetbale 的类型,编译器将返回错误,因此不存在消息误发送的情况:
struct Bug: Greetable {
let name: String
}
// Compiler Error:
// 'Bug' does not conform to protocol 'Greetable'
// protocol requires function 'greet()'
协议拓展:
1、Swift的协议默认实现: Swift 的 extension 也比OC的强大的多,protocol 和 extension配合起来, 做到了更大的灵活性,实现更加强大的功能, 比起继承和组合更加有效。
2、Swift開發指南:Protocols與Protocol Extensions的使用心法:这是Swift从面向对象到面向协议转变的关键,毕竟,我们在Objective C中也有protocols。那么为什么Objective C不曾考虑POP,Swift却开始使用呢?答案在于protocol extension。 就像我们在上一节看到的那样,我们可以在Swift中扩展classes和structs以向它们添加功能。然而,通过protocols让extensions更加强大,因为它们允许你为协议提供预设功能,
泛型
总结:
<Element> <T> 泛型,<>提前申明,接下来把Element,T当成一个类型使用就行
在Swift中,泛型可作用于几个地方:
- Function
- Classess/Structs/Enums
- Protocol
- Extension
重要重要
泛型加where 来代替子类。。。。。。。
参考文章:
2、泛型
四、闲话拓展Extension
拓展Extension
类比iOS:http://swift.gg/2016/05/16/using-swift-extensions/
作用域:https://www.cnblogs.com/tieria/p/4507261.html
五、可选值、可选链
参考文章:
3、从枚举类型看
5、可选映射
1、为什么使用可选值?
引入一个显式可选类型的意义是什么呢?对于习惯了 Objective-C 的程序员来说,最初使用可选类型也许会觉得奇怪。Swift 的类型系统相当严格:一旦我们有可选类型,就必须处理它可能是 nil 的问题。编程相对复杂。在 Objective-C 中,这一切更灵活。
- 场景1:字典取值 —— 可能你会想要区分失败 (键不存在于字典) 和成功返回 nil (键存在于字典,但关联值是 nil) 两种情况。若要在 Objective-C 中做到这一点,你只能使用 NSNull。
- 场景2:编译时安全,运行时崩溃。项目最常见的就是Array,Dictionary。项目中创建请求的参数
2、可选值是什么
可选类型其根源是一个枚举型,里面有None和Some两种类型。
可选值定义:
3、可选值的基本用法
1)、显式拆包(强制解析)
显式拆包返回两个值:
- 拆包的动作其实就是将Some里面的值取出来;
- 当Optional没有值时,编译不通过(保证运行安全)
let a: String? = "1"
print(a!)
// 输出 1
let a: String? = nil
print(a!)
// 编译不通过
2)、自动拆包
通过在声明时的数据类型后面加一个感叹号(!),编译器自动拆包
自动拆包返回两个值:
- 拆包的动作其实就是将Some里面的值取出来;
- 当Optional没有值时,运行时崩溃
坏处:运行时不安全,谨慎使用
3)、对比
声明定义 | 拆包 | 安全 | |
---|---|---|---|
显示拆包 | ? | ! | 编译不通过,运行时安全 |
自动拆包 | ! | 需要 | 运行时崩溃 |
4、玩转可选值(如何拆包)
1)、可选绑定(Optional Binding) ——— if let
可选绑定返回两个值:
- 如果目标有值,调用就会成功,返回该值
- 如果目标为nil,调用将返回nil
var name: String? = nil
//var name: String? = "jike"
if let name = name {
print("name is \(name)")
} else {
print("name is \(name)")
}
对比显式拆包的两个优点:
1、不必一直使用!来拆包
2、可以处理nil的情况
2)、可选链(Optional chaining)
一句话总结:可选链是一个可选值,一个为nil,一个为非nil
可选链返回两个值:
- 如果目标有值,调用就会成功,返回该值
- 如果目标为nil,调用将返回nil
用可选值变量的时候各种判断有时候还需要if嵌套真的很麻烦,所以出现了可选链
可选链与强制解析对比
可选链 '?' | 感叹号(!)强制展开方法,属性,下标脚本可选链 |
---|---|
? 放置于可选值后来调用方法,属性,下标脚本 | ! 放置于可选值后来调用方法,属性,下标脚本来强制展开值 |
当可选为 nil 输出比较友好的错误信息 | 当可选为 nil 时强制展开执行错误 |
3)、可选映射 map
public func map<U>(f: Wrapped -> U) -> U?
public func flatMap<U>(f: Wrapped -> U?) -> U?
/// If `self == nil`, returns `nil`.
/// Otherwise, returns `f(self!)`.
public func map<U>(@noescape f: (Wrapped) throws -> U)
rethrows -> U? {
switch self {
case .Some(let y):
return .Some(try f(y)) //区别点
case .None:
return .None
}
}
/// Returns `nil` if `self` is `nil`,
/// `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?)
rethrows -> U? {
switch self {
case .Some(let y):
return try f(y) //区别点
case .None:
return .None
}
}
-
f
函数一个返回U
,另一个返回U?
。 - 一个调用的结果直接返回,另一个会把结果放到 .Some 里面返回。
// flatMap降维和 map的对比示例
let a: String? = "1"
let am = a.map{ Int($0) } // 闭包结果类型 Int?
print( am )
// Optional(Optional(1)) map结果类型 Int?? .some( transform(y) )
let afm = a.flatMap{ Int($0) } // 闭包结果类型 Int?
print(afm)
// Optional(1) flatMap结果类型 Int?? transform(y)
// 原来类型:Int?, 返回值类型:String?
let b: Int? = 1
let bm = b.map { String("result = \($0)") } // 闭包结果类型 String
print(bm)
/// "Optional("result = 1")"
// 原optional没有值, map和flatMap函数都直接返回nil
let c:Int? = nil
let cm = c.map { String("result = \($0)") } // 闭包结果类型 String
print(cm)
/// "nil" map结果类型 String?
4)、空合运算符 ??
let newName = name == nil ? "no name" : name!
let newName2 = name ?? "no name"
项目方面: