美文网首页
1.2、OC->Swift:函数与闭包

1.2、OC->Swift:函数与闭包

作者: 双鱼子曰1987 | 来源:发表于2022-11-06 18:04 被阅读0次

    零、概述

    • 函数的一等公民,可以当做类型、函数参数、返回值等,支持面向函数编程。
    • 每个函数 都是 一个由 函数的 参数值类型 和 返回值类型 组成的类型。

    一、函数基础

    1、声明与定义

    • 关键字:func,参数列表:括号()包裹,返回值:->,函数体:大括号{}包裹。
    func sayHelloWorld() -> String {
        print("hello, world")
    }
    
    sayHelloWorld()
    // 打印“hello, world”
    

    2、函数参数

    2.1、参数列表:(参数名1:类型, 参数名2:类型, 参数名3:类型)

    • 注意:调用函数时候,入参实参 必须与 声明函数时参数列表里的参数顺序一致。

    2.2、参数标签(即参数别名,和OC类似)

    a、默认支持参数同名标签
    func testFunc(name: String) {
    }
    testFunc(name: "haha")
    
    b、支持自定义函数的参数标签
    • 如果自定义了参数的标签,调用的时候必须写具体的标签名:
    func testFunc_0(name: String) {
    }
    testFunc_0(name: "haha")
    
    func testFunc_1(nickname name: String) {
    }
    testFunc_1(nickname: "123")
    
    c、忽略参数标签:使用一个下划线_来代替一个参数标签;调用的时候忽略标签名。
    func someFunction(_ firstParameterName: Int, secondParameterName: Int) { 
        ... 
    }
    someFunction(10)
    

    2.3、支持函数默认值:(参数名1:类型, 参数名2:类型 = 默认值)

    • 当默认值被定义后,调用这个函数时可以忽略这个参数,不用入参。
    • 需要将不带有默认值的参数放在函数参数列表的最前,默认值参数位于最后面。
    func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
        ...
    }
    
    someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) 
    // 调用函数时,可以不传默认参数
    someFunction(parameterWithoutDefault: 4)
    

    2.4、可变参数:(参数名:类型 ...)

    • 一个函数最多只能拥有一个可变参数,只能位于参数列表最后面。
    • 可变参数的传入值:本质上,在函数体中变为此类型的一个数组。
    func arithmeticMean(_ numbers: Double...) -> Double {
        var total: Double = 0
        // numbers为double的数组
        for number in numbers {
            total += number
        }
        return total / Double(numbers.count)
    }
    arithmeticMean(1, 2, 3, 4, 5)
    // 返回 3.0, 是这 5 个数的平均数。
    arithmeticMean(3, 8.25, 18.75)
    // 返回 10.0, 是这 3 个数的平均数。
    

    复合参数:默认值 + 可变参数

    func testFunc(_ name:String, age:Int=0, list:[String]) {
        print("name:\(name), age:\(age), list:\(list)")
    }
    
    testFunc("hui", list: ["a", "b", "c"])
    testFunc("hui", age: 18, list: ["a", "b", "c"])
    

    2.5、输入输出参数(类似于,C语言的指针语法)

    • 注意:函数参数默认是常量,因此试图在函数体中修改参数值将会导致编译错误。
      如果希望一个函数内可以修改参数的值,并且这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数

    • 语法:参数名: inout Type,关键字inout;调用函数时,在参数名前加 & 符,表示这个值可以被函数修改。

    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
        let temporaryA = a
        a = b
        b = temporaryA
    }
    
    var someInt = 3
    var anotherInt = 107
    // 3,107
    print("\(someInt),\(anotherInt)")
    
    swapTwoInts(&someInt, &anotherInt)
    // 107,3
    print("\(someInt),\(anotherInt)")
    

    3、返回值

    • 无返回值(本质是返回Void)
    func sayHello() -> Void {
        print("Hello World!");
    }
    
    // 等价于
    func sayHello() {
        print("Hello World!");
    }
    
    • 多重返回值:即返回 元组可选元组
    // 返回数组中的最大值和最小值
    func minMax(array: [Int]) -> (min: Int, max: Int) {
        var currentMin = array[0]
        var currentMax = array[0]
        for value in array[1..<array.count] {
            if value < currentMin {
                currentMin = value
            } else if value > currentMax {
                currentMax = value
            }
        }
        return (currentMin, currentMax)
    }
    
    let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
    print("min is \(bounds.min) and max is \(bounds.max)")
    // 打印“min is -6 and max is 109”
    

    返回可选元组:如果函数返回的元组类型有可能整个元组都“没有值”。

    func minMax(array: [Int]) -> (min: Int, max: Int)? {
        ...
        return (currentMin, currentMax)
    }
    
    • 隐式返回(即省略return关键字)
      如果一个函数的整个函数体是一个单行表达式,这个函数可以隐式地返回这个表达式。
    func greeting(for person: String) -> String {
        // 没有return,默认返回字符串
        "Hello, " + person + "!"
    }
    print(greeting(for: "Dave"))
    // 打印 "Hello, Dave!"
    

    二、函数类型

    在Swift中,函数的一等公民,可以当做类型、函数参数、返回值等,支持面向函数编程。

    1、定义与语法

    • 定义:由 函数参数类型函数返回类型 共同组成 函数类型。
    • 语法格式:(参数列表) -> 返回类型
      例如:() -> Void ,表示的是 没有参数无返回值 类型的函数
    //  函数类型:(Int, Int) -> Int
    func addTwoInts(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
    // 1、函数类型,做为变量
    var mathFunction: (Int, Int) -> Int = addTwoInts
    print("Result: \(mathFunction(2, 3))") // Prints "Result: 5"
    
    
    // 2、函数类型,做为函数参数
    func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
        print("Result: \(mathFunction(a, b))")
    }
    printMathResult(addTwoInts, 3, 5) // 打印“Result: 8”
    
    // 3、函数类型,作为返回类型
    func chooseStepFunction(backward: Bool) -> (Int) -> Int {
        return backward ? stepBackward : stepForward
    }
    var currentValue = 3
    let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
    // moveNearerToZero 现在指向 stepBackward() 函数。
    

    2、函数嵌套

    • 把函数定义在别的函数体中,称作 嵌套函数(nested functions)。
    func chooseStepFunction(backward: Bool) -> (Int) -> Int {
        func stepForward(input: Int) -> Int { return input + 1 }
        func stepBackward(input: Int) -> Int { return input - 1 }
        return backward ? stepBackward : stepForward
    }
    
    var currentValue = -4
    let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
    while currentValue != 0 {
        print("\(currentValue)... ")
        currentValue = moveNearerToZero(currentValue)
    }
    print("zero!")
    // -4...
    // -3...
    // -2...
    // -1...
    // zero!
    

    三、闭包

    Swift的闭包类似于OC的(block) 或者 其他语言的匿名函数(lambdas)语法,其目的都是提供简易便捷的代码块封装方式。

    • 闭包的三种表现形式
      全局函数是一个有名字但不会捕获任何值得闭包。
      嵌套函数是一个有名字并且可以捕获其封闭函数域内值得闭包。
      闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值得没有名字的闭包。

    • Swift闭包看起来内容很多,其实很多是提供语法糖功能,学习的时候注意区分和思考。

    • 闭包的学习重点在于 值捕获 和 循环引用。
      闭包核心:闭包可以捕获和存储其所在上下文中任意常量和变量的引用。被称为包裹常量和变量。 这其中涉及到的内存管理。

    1、闭包的语法格式

    下面将以Swift 标准库提供了名为 sorted(by:) 的方法做为闭包的讲解。

    a、标准语法:

    大括号{}包裹:标识整个闭包代码块;
    函数类型声明:括号()内定义参数列表;箭头符号-> 闭包返回值类型;
    关键字in:标识 闭包 代码语句的开始

    { (parametersList) -> returnType in
        // statements
    }
    

    例子:以集合类型的系统排序函数sorted为例

    names.sorted(by: { (s1: String, s2: String) -> Bool in
        return s1 > s2
    })
    
    // 等价于
    names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
    

    b、闭包 支持的 参数和返回值

    • 支持inout参数,支持可变参数,不支持默认值参数
    • 支持多参数返回值(即元组和可选元组)。

    c、闭包语法糖:闭包的各种省略模式

    • 省略参数列表 和 返回值:names.sorted(by: { s1, s2 in return s1 > s2 } )
      如果上下文可以推导出 参数和返回值的类型,那么参数和返回值类型都可以省略;并且箭头-> 和 围绕在参数周围的括号()也可以被省略。(依赖于上下文类型推断原理)
    • 单表达式省略return语句: names.sorted(by: { s1, s2 in s1 > s2 } )
      这是依赖于单表达式的隐式返回原理

    • 省略参数名称:names.sorted(by: { $0 > $1 } )
      Swift 自动为内联闭包提供了参数名称缩写功能,你可以直接通过 $0,$1,$2 ... 来顺序调用闭包的参数,以此类推。

    • 运算符方法:names.sorted(by: >)
      Swift 的 String 类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个 String 类型的参数并返回 Bool 类型的值。而这正好与 sorted(by:) 方法的参数需要的函数类型相符合。
      因此,你可以简单地传递一个大于号,Swift 可以自动推断找到系统自带的那个字符串函数的实现。


    2、闭包的值捕获 和 循环引用

    2.1、捕获

    • 闭包 可以 在其被定义的上下文中 捕获 常量或变量。
      即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
    func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
    }
    
    // 由于 闭包或者嵌套函数 addByTen 和 addBySix 各自分别捕获一份变量 runningTotal,
    // 因此,addByTen 和 addBySix 调用不会相互干扰
    let addByTen = makeIncrementer(forIncrement: 10)
    let addBySix = makeIncrementer(forIncrement: 6)
    
    print(addByTen()) // 10
    print(addByTen()) // 20
    
    print(addBySix()) // 6
    print(addBySix()) // 12
    
    print(addByTen()) // 30
    
    • 注意:为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,Swift 可能会改为捕获并保存一份对值的拷贝。Swift 也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。

    • 注意:如果你将闭包赋值给一个类实例的属性,并且该闭包通过访问该实例或其成员而捕获了该实例,你将在闭包和该实例间创建一个循环强引用。

    2.2、闭包的循环强引用

    • 循环应用的产生(同OC循环引用一样)
      将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了这个类实例时,实例属性访问或实例方法的调用,这两种情况都导致了闭包“捕获”self(闭包的捕获特性),从而产生了循环强引用。

    • 循环强引用的产生,是因为闭包和类相似,都是引用类型。

    2.3、循环强引用的解决:捕获列表

    • 语法:[unowned/weak 变量/常量名称, unowned/weak 变量/常量名称, ...]
      捕获列表 中的 每一项 都是由 一对元素组成;元素是由 weakunowned 关键字,紧跟着 类实例的引用(例如 self)或初始化过的变量(如 delegate = self.delegate!);这些项在方括号中用逗号分开。

    • 捕获列表 放在 闭包的函数类型 之前

    // 1、如果闭包有参数列表和返回类型,把捕获列表放在它们前面:
    lazy var someClosure: (Int, String) -> String = {
        [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
        // 这里是闭包的函数体
    }
    
    • 省略 闭包的函数类型
    // 2、如果闭包没有指明参数列表或者返回类型,它们会通过上下文推断,那么可以把捕获列表和关键字 in 放在闭包最开始的地方:
    lazy var someClosure: () -> String = {
        [unowned self, weak delegate = self.delegate!] in
        // 这里是闭包的函数体
    }
    

    2.4、弱引用weak 和 无主引用unowned的区别

    • 弱引用(和OC中的weak类似)
      在被捕获的引用可能会变为 nil 时,将闭包内的捕获定义为 弱引用。
      弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为 nil。

      注意:如果被捕获的引用绝对不会变为 nil,应该用无主引用,而不是弱引用。

    • 无主引用
      在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为 无主引用。


    3、逃逸闭包,关键字@escaping

    • 注意:swift默认的闭包都是非逃逸闭包。

    3.1、逃逸闭包 的 定义:

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

    一种能使闭包“逃逸”出函数的方法是,将这个闭包保存在一个函数外部定义的变量中。

    /* 
    * 函数接受一个 闭包completionHandler 作为参数,该闭包被添加到一个函数外定义的数组中。
    * 如果你不将这个参数标记为 @escaping,就会得到一个编译错误。
    */
    var completionHandlersList: [() -> Void] = []
    func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
        completionHandlersList.append(completionHandler)
    }
    

    3.2、类方法 与 逃逸闭包

    如果将一个闭包标记为 @escaping 意味着,必须在闭包中显式地引用 self

    func someFunctionWithNonescapingClosure(closure: () -> Void) {
        closure()
    }
    
    class SomeClass {
        var x = 10
        func doSomething() {
            /* 1、逃逸闭包 */
            someFunctionWithEscapingClosure { 
                self.x = 100  // 逃逸闭包,必须显示的引用 self
            }
          
            /* 2、非逃逸闭包 */
            someFunctionWithNonescapingClosure { 
                x = 200 // 非逃逸闭包,可以隐式的引用 self
            } 
        }
    }
    

    3.3、为什么要分逃逸闭包和非逃逸闭包?

    • 非逃逸闭包不会产生循环引用,它会在函数作用域内释放,编译器可以保证在函数结束时闭包会释放它捕获的所有对象,因此可以在非可逃逸闭包里放心的使用self关键字;
    • 非逃逸闭包的另一个好处是编译器可以应用更多强有力的性能优化,就可以省去一些保留(retain)和释放(release)的调用;
    • 非逃逸闭包它的上下文的内存可以保存在栈上而不是堆上。

    4、语法糖:尾随闭包

    函数调用时,针对最后一个函数参数是比闭包情况 的省略模式。

    • 适用场景:函数的最后一个参数是闭包的情况下。
      如果你需要将一个很长的 闭包表达式作为最后一个参数 传递给函数,尾随闭包 可以简化这个过程。
    func someFunctionThatTakesAClosure(closure: () -> Void) {
        ...
    }
    
    // 以下是不使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure(closure: {
        ...
    })
    
    // 以下是使用尾随闭包进行函数调用
    someFunctionThatTakesAClosure() {
        ...
    }
    
    • 省略括号
      如果闭包表达式是函数或方法的唯一参数,则当你使用尾随闭包时,你甚至可以把 () 省略掉。
    names.sorted() { $0 > $1 }
    // 等价于
    names.sorted { $0 > $1 }
    

    5、语法糖:自动闭包,关键字@autoclosure

    • 定义:
      自动闭包 是一种自动创建的闭包,用于将 参数类型是函数类型的参数 自动包装为一个闭包,只要在参数名之前标识@autoclosure即可。这种闭包是不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。

      这种便利语法让你能够省略闭包的花括号{},用一个普通的表达式来代替显式的闭包。

    // customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]
    // 参数 customerProvider 的类型:函数类型() -> String,
    func serve(customer customerProvider: () -> String) {
        print("Now serving \(customerProvider())!")
    }
    serve(customer: { customersInLine.remove(at: 0) } )
    // 打印出“Now serving Alex!”
    
    
    
    // customersInLine is ["Ewa", "Barry", "Daniella"]
    func serve(customer customerProvider: @autoclosure () -> String) {
        print("Now serving \(customerProvider())!")
    }
    
    // 自动闭包:省略闭包的花括号`{}`,用一个普通的表达式来代替显式的闭包。
    serve(customer: customersInLine.remove(at: 0))
    // 打印“Now serving Ewa!”
    
    • 自动闭包 是可以“逃逸”:支持同时使用 @autoclosure 和 @escaping 属性。
    • 注意:过度使用 autoclosures 会让你的代码变得难以理解。

    四、函数和闭包都是引用类型

    无论你将函数或闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用。

    相关文章

      网友评论

          本文标题:1.2、OC->Swift:函数与闭包

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