美文网首页iOS
Swift三剑客

Swift三剑客

作者: 只为此心无垠 | 来源:发表于2018-03-13 17:31 被阅读0次

    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没有值时,运行时崩溃
    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 项目

    相关文章

      网友评论

        本文标题:Swift三剑客

        本文链接:https://www.haomeiwen.com/subject/nmuzfftx.html