Swift三剑客

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、如何简略闭包?

img

详见文章:使用闭包简化语法

5、闭包种类

尾随闭包

如果你需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。在使用尾随闭包时,你不用写出它的参数标签:

func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

// 以下是不使用尾随闭包进行函数调用
someFunctionThatTakesAClosure(closure: {
    // 闭包主体部分
})

// 以下是使用尾随闭包进行函数调用
someFunctionThatTakesAClosure() {
    // 闭包主体部分
}
逃逸闭包

当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当你定义接受闭包作为参数的函数时,你可以在参数名之前标注 @escaping,用来指明这个闭包是允许“逃逸”出这个函数的。

一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。举个例子,很多启动异步操作的函数接受一个闭包参数作为 completion handler。这类函数会在异步操作开始之后立刻返回,但是闭包直到异步操作结束后才会被调用。在这种情况下,闭包需要“逃逸”出函数,因为闭包需要在函数返回之后被调用。例如:

var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

二、面向协议

1、总结:

用完全新的眼光去看协议,遵守协议取代子类继承

2、协议语法:

1、面向协议编程与 Cocoa 的邂逅

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"

我们看到 TigerAnimal 共享了一部分代码,这部分代码被封装到了父类中,而除了 Tiger 的其他的子类也能够使用 Animal 的这些代码。这其实就是 OOP 的核心思想 - 使用封装和继承,将一系列相关的内容放到一起。

虽然我们努力用面向对象来抽象和继承的方法进行建模,但是实际的事物往往是一系列特质的组合,而不单单是以一脉相承并逐渐扩展的方式构建的。

面向对象的三个困境:

  • 横切关注点
  • 菱形缺陷
  • 动态派发安全性

横切关注点:

面向对象

class ViewCotroller: UIViewController
{
    // 继承
    
    // 新加
    func myMethod() {        
    }
}
class AnotherViewController: UITableViewController
{
    // 继承
    // 新加
    func myMethod() {
        
    }
}

由上看出,很难在不同继承关系的类里共用代码。这里的问题用“行话”来说叫做“横切关注点” (Cross-Cutting Concerns)。我们的关注点 myMethod 位于两条继承链 (UIViewController -> ViewCotrollerUIViewController -> 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()
    }
}

有了这个协议扩展后,我们只需要简单地声明 ViewControllerAnotherViewController 遵守 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中,泛型可作用于几个地方:

  1. Function
  2. Classess/Structs/Enums
  3. Protocol
  4. Extension

重要重要

泛型加where 来代替子类。。。。。。。

参考文章:

1、邂逅Swift你需要知道的 n 件事

2、泛型

3、Swift 4 中的泛型

4、Swift学习之泛型

四、闲话拓展Extension

拓展Extension

类比iOS:http://swift.gg/2016/05/16/using-swift-extensions/

作用域:https://www.cnblogs.com/tieria/p/4507261.html

五、可选值、可选链

参考文章:

1、Swift3之细致理解Optional(可选类型)

2、为什么非可选任何可以容纳零

3、从枚举类型看

4、Swift中的可选类型?和隐式解析可选类型!

5、可选映射

1、为什么使用可选值?

引入一个显式可选类型的意义是什么呢?对于习惯了 Objective-C 的程序员来说,最初使用可选类型也许会觉得奇怪。Swift 的类型系统相当严格:一旦我们有可选类型,就必须处理它可能是 nil 的问题。编程相对复杂。在 Objective-C 中,这一切更灵活。

  • 场景1:字典取值 —— 可能你会想要区分失败 (键不存在于字典) 和成功返回 nil (键存在于字典,但关联值是 nil) 两种情况。若要在 Objective-C 中做到这一点,你只能使用 NSNull。
  • 场景2:编译时安全,运行时崩溃。项目最常见的就是Array,Dictionary。项目中创建请求的参数

2、可选值是什么

可选类型其根源是一个枚举型,里面有None和Some两种类型。

img

可选值定义:

img

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
img

用可选值变量的时候各种判断有时候还需要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
    }
}
  1. f 函数一个返回 U,另一个返回 U?
  2. 一个调用的结果直接返回,另一个会把结果放到 .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"

项目方面:

自学 iOS - 三十天三十个 Swift 项目

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容