美文网首页
Swift 4.0学习之旅—闭包

Swift 4.0学习之旅—闭包

作者: lixiangdev | 来源:发表于2018-05-25 18:16 被阅读0次
        /*
         闭包,闭包是自包含的函数代码块,可以在代码中被传递和使用,Swift中的闭包与c 和oc 中的代码块block以及其他一些编程语言中的匿名函数比较相似
         闭包可以捕获和存储其所在上下文中任意常量和变量的引用,被称为包裹常量和变量,Swift会为你管理在捕获过程中涉及到的所有内存操作
         
         在函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取下面三种形式
         1.全局函数是一个有名字但不会捕获任何值得闭包
         2.嵌套函数时一个有名字可以捕获其封闭函数域内值得闭包
         3.闭包表达式是一个利用轻量级语法所写的可以捕获其上下文变量或常量值得匿名闭包
         
         
         Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中进行语法优化,主要优化如下
         1.利用上下文推断参数和返回值类型
         2.隐式返回单表达式闭包,即单表达式闭包可以省略return关键字
         3.参数名称缩写
         4.尾随闭包语法
         
         */
        
        /*
         闭包表达式  嵌套函数是一个在较复杂函数中方便进行命名和定义自包代码模块的方式,当然有时候编写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在你处理一些函数并需要将另外一些函数作为该函数的参数时。
         闭包表达式是一种利用间接语法构建内联闭包的方式,闭包表达式提供了一些语法化,使得撰写闭包变得简单明了.
         */
        
        //下面闭包表达式的例子通过使用了几次迭代展示了  sorted(by:)方法定义和语法优化的方式,每一次迭代都用更简洁的方式描述了相同的功能。
        
        //sorted方法, swift标准库提供了名为 sorted(by:)的方法,它会根据你所提供的用于排序的闭包函数将已知类型数组中的值进行排序.一旦排序完成 sorted方法会返回一个与原数组大小相同,包含同类型元素已正确的排序的新数组。原数组不会被sorted方法修改
        
        //下面使用sorted方法对一个string 类型的数组进行字母逆序排序。
        let names = ["ali","bengbu","dashi","chengshi","efi","gangnan","haha"]
        
        //sorted方法接收一个闭包,该闭包函数需要传入与函数元素类型相同的两个值,并返回一个布尔类型值来表明当前排序结束后传入的第一个参数排在第二个参数前面还是后面,如果第一个参数值出现在第二个参数值前面,排序闭包函数需要返回true 反之返回false
        //该例子对一个 string 类型的数组进行排序,因此排序闭包函数类型为(string,string) -> bool
        
        //提供排序闭包函数的一种凡是是撰写一个函数其类型要求的普通函数,并将其作为sorted方法的参数传入
        
        func backword (_ s1 : String ,_ s2 : String) -> Bool{
            print("s1 = \(s1) > s2 = \(s2)   result = \(s1 > s2)")
            return s1 > s2
        }
    

    // var reversednames = names.sorted(by: <#T##(String, String) throws -> Bool#>)
    // var sortedname = names.sorted()
    var sortedname = names.sorted(by: backword)
    //编译器或提供出两个 一个是sorted() 默认是正序排列 ,而且方法跟过去会跳到官方方法说明而且还有举例
    //另一种是sorted(by: (string,string) throws -> bool) 方法,是要传入一个函数,函数表达为 传入两个字符串返回一个布尔值得函数,所以可以自定义函数也可以直接传入 true或false

        //如果按照backword来执行的  猜想岂不是 传入的数组顺序前两位来决定是 true 还是false
        print(sortedname)
        print(names.sorted())
        let ab = ["a","b","c"]
        let ba = ["b","a","c"]
        var absort = ab.sorted(by: backword)
        /*
         s1 = b > s2 = a   result = true
         s1 = c > s2 = a   result = true
         s1 = c > s2 = b   result = true
         
         这是backword方法执行的顺序, 很迷啊,两两比较,但总是后一个与前一个进行比较
         */
        
        var basort = ba.sorted(by: backword)  //b 大于a  返回false
        /*
         s1 = a > s2 = b   result = false
         s1 = c > s2 = a   result = true
         s1 = c > s2 = b   result = true
         第二个数组的比较法也是 两两比较 后一个与前一个进行比较  但是是有一个false true为两个
         */
        
        /*
         sorted(by: >) 大于时为倒序, 小于时正序
         举例说,b,a,c
         1.a与b比较 a不大于b 返回false 不执行 排列
         2.然后c 又与之前的a 比较 c 大于 a 返回true 为倒序 c 与 a 换个位置  此时为  b,c,a
         3.然后 c 需要与之前的b 进行比较   c 大于b 返回true 为倒序  c 与b 换个位置 此时为 c,b,a
         */
        
        print(absort)
        print(basort)
        
        //这么写闭包函数 实在太繁琐,可以利用闭包表达式语法可以更好的构造一个内联排序闭包
        
        
        //闭包表达式语法, 闭包表达式语法有如下的一般形式
        /*
        {(parameters) -> returnType in
            statements
        }
        */
        
        //闭包表达式参数可以是in-out参数,但不能设定默认值,也可以使用具名的可变参数。但是如果可变参数不放在参数列表的最后一位的话,调用闭包的时候编译器会报错。元组也可以作为参数和返回值。
        var sortedFunc = names.sorted(by: { (_ s1 : String ,_ s2 : String) -> Bool in
            return s1 > s2
        })
        
        let testClosure = { (scores: Int..., name: String) -> String in
            return "Happy"
        }
    

    // let k = testClosure(1, 2, 3, "John")//该行代码报错,因为 scores 是可变参数,要求是可变参数必须是参数列表的最后一位,编译器也不知道你传的是第二参数还是可变参数的在最后一个参数
    //应该写成
    let testClosure1 = { ( name: String ,scores: Int...) -> String in
    return "Happy"
    }
    let k = testClosure1("John",1, 2, 3)//这样就不会报错了
    //如果有可变参数必须放在函数的参数列表的最后一位

        //注意的是 内联闭包参数和返回值类型声明与 backward函数类型声明相同,在这两种方式中,都写成了(s1:string,s2:string)->bool。然而在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外。
        
        //闭包的函数体部分由关键字in引入,该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。in前面是参数类型与返回值类型定义 in后是返回体
        
        //由于闭包体较短 可以写成一行
        var reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )
    
        //方法sorted的整体调用保持不变,一对圆括号任然包裹住了方法的整个参数,然而参数现在变成了内联闭包
        //根据上下文推断类型
        //因为排序闭包函数时作为sorted方法的参数传入的,swift可以推断其参数和返回值的类型
        //sorted方法被一个字符串数组调用,因此参数必须是(string,string)->bool 类型的函数,这意味着(string,string)和bool 类型并不需要作为闭包表达式定义的一部分,因为所有的类型都可以被正确推断,返回箭头 -> 和围绕在周围的括号也可以被省略
        
        var funx = names.sorted(by: {s1,s2 in return s1 > s2})
        //实际上,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,总是能够推断出闭包的参数和返回值类型,这意味着闭包作为函数或者方法的参数时,你几乎不需要利用完整格式构造内联闭包。
        //尽管如此,你任然可以明确完整格式的闭包,如果完整格式的闭包能够提高代码的可读性,我么更鼓励采用完整格式的闭包,在特定的方法里,如sorted就是就是排序方法,就是处理字符串,所以无需写的很完整。
        
        
        //单表达式闭包隐式返回  单行表达式闭包可以通过省略return 关键字来隐式返回单行表达式的结果
        
        var funxx = names.sorted(by: {s1,s2 in s1 > s2}) //仅限单行
        //有点意思,妈的啥都可以隐藏掉不写,唯独 in 关键字不能省
        //这个例子sorted方法的参数类型明确了闭包必须返回一个bool 类型值,因为闭包函数体只包含了一个单一表达式(s1>s2),该表达式返回bool类型值,因此这里没有歧义,return关键字可以省略
        
        //总结 如果能推断出返回值类型,就不需要定义返回值类型 参数的括号也就不需要写,如果在单行表达式中,return后只有一个语句 可以不写return
        
        //参数名称缩写
        /*
         Swift自动为内联闭包提供了参数名称缩写功能,可以直接通过 $0,$1,$2来顺序调用闭包的参数,以此类推
         如果你在闭包表达式中使用参数名称缩写,可以在闭包定义中省略参数列表,并且对应参数名称缩写的类型会通过函数类型进行推断,in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成.   md 居然in 也可以被省略
         */
        
        var funcc = names.sorted(by: {$0 > $1})
        //卧槽被惊到了  这个例子中,$0 和 $1 表示闭包中第一个和第二个string 类型的参数
        
        //运算符方法,实际上还有一中更简短的方式来编写上面例子中的闭包表达式,swift的string类型定义了关于 大于号 > 的字符串实现,其作为一个函数接收两个string 类型的参数并返回bool 类型的值。而这正好与sorted方法的参数需要的函数类型相符合。因此你可以简单的传递一个大于号或等于号,Swift可以自动推断出你想使用大于号的字符串函数实现
        
        var funccx = names.sorted(by: >) //还有更多的 后面慢慢学
    

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

        func myfunc( closure:() ->Void){ //其中 ()->void 是个闭包  是需要往里传一个 无返回的闭包
            //函数体部分
        }
        
        //不使用尾随闭包
        myfunc(closure: { //closure 后是参数部分,也是最后一个参数部分,也是闭包的结构,前面说过,闭包由{(参数)->返回值 in 闭包体 }
            //闭包主体部分
        })
        
        //使用尾随闭包
        myfunc() {  //定义时,使用尾随闭包时,不用写出函数的参数标签, 用()代替
            //闭包主体
        }
        
        
        func method(named : String , closer : () -> String) -> String{
            return named + closer()//这是拼接字符串应该使用closer这个方法 虽然是参数,但是是闭包类型的,返回值时字符串,所以要调用参数是closer的这个闭包方法的返回值,调用方法就得到返回值。
        }
        
        method(named: "hello") { () -> String in
    

    // return named + "world" 这时候发现不能用named 因为named 不属于闭包内定义的,取不到值
    return "world"
    }

        // var funcc = names.sorted(by: {$0 > $1}) 这个函数 也可以写成
        var ffuncc = names.sorted(){$0 > $1}
        //另外  如果闭包表达式不仅是最后一个参数 而是唯一参数时,也可以把参数标签的替代的括号 () 省略;
        var ffff = names.sorted { $0 > $1}
        
        //当闭包非常长以至于不能在一行中书写时,尾随闭包变得非常有用。swift 的array 类型有一个map(_:)方法,这个方法获取一个闭包表达式作为其唯一参数,该闭包函数会为数组中的每一个元素调用一次,并返回该元素所映射的值,具体的映射方式和返回值类型由闭包来指定。
        //当提供给数组的闭包应用于每个数组元素后,map方法将返回一个新的数组,数组中包含了与原数组中的元素一一对应的映射后的值。
        
        let keyvalue = [0:"zero",1:"one",2:"two",3:"three",4:"four",5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"]
        
        let numbers = [1,10,11,33,99]
        
        //通过传递一个尾随闭包numbers 数组的map(_:)方法来创建对应的字符串版本数组
        let strings = numbers.map { (number) -> String in
            var num = number
            var output = ""
            
            repeat {
                output = keyvalue[num % 10]! + output
                num /= 10
            } while num > 0
            return output
        }
        
        
        
        
        /* map为数组中每一个元素调用了一次闭包表达式,你不需要指定闭包的输入参数 number 的类型,因为可以不通过要映射的数组类型进行推断。
         在该例中,局部变量 num 的值由闭包的number 参数获得,因此可以在闭包函数体内对其进行修改,闭包或者函数的参数总是常量,闭包表达式指定了返回类型为string,以表明存储映射新数组类型为string
         
         闭包表达式在每次被调用的时候创建了一个叫做output的字符串并返回,其使用求余运算符(number % 10)计算最后一个数字并利用digitnames字典获取所映射的字符串,这个闭包能够用于创建任意正整数的字符串表示。
         
         
         其中,digitnames 下标后跟着一个感叹号,因为字典下标返回一个可选值,表明该键不存在时,会查找失败如果已知所有的字典的有效下标,可以加感叹号强制解包,存储在下标的可选类型的返回值中的string 类型的值。
         
         从digitname字典中获取的字符串被添加到output的前部,逆序建立了一个字符串版本的数字,在表达式num %10中,如果num为16 返回6 ,num变量之后除以10,因为其是整数,在计算过程中未除尽部分被忽略,因此16变成了1,整个过程重复进行,直到num /10 为0,这时闭包会将字符串output返回,而map 方法则会将字符串添加到映射数组中。
         
         这个例子通过尾随闭包语法,在函数后封装了闭包的具体功能,不需要将整个闭包包裹在map方法括号内。
         */
        
        //值捕获  闭包可以在其被定义的上下文章捕获常量或变量,即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。swift 中,可以捕获值得闭包的最简单形式是嵌套函数,也就是在定义在其他函数的函数体内的函数,嵌套函数可以捕获其外阿布函数所有的参数以及定义的常量和变量。
        //起个叫testfffunc 函数,其包含了一个叫做incrementer 的嵌套函数,嵌套函数incrementer从上下文中捕获了两个值,reunningtotal 和amount。捕获这些值之后,testfffunc 将incrementer 作为闭包返回。每次调用incrementer时,其会以amount作为增量增加runningtotal的值
        
        
        func testfffunc(forincrement amount : Int) -> () -> Int{
            var runningtotal = 0
            func incrementer() -> Int{
                runningtotal += amount
                return runningtotal
            }
            return incrementer
        }
        
        //testfffunc 返回类型为 ()-> Int 意味着其返回的是一个函数,而非一个简单类型的值,该函数在每次调用时不接受参数,只返回一个Int类型的值。
        
        //testfffunc 中 incrementer函数没有任何参数,只是从外围捕获了runningttla和amount变量的引用。捕获引用保证了runningtotal和amount变量在调用完incrementer后不会消失,并且保证了在下一次执行incrementer函数时,running依旧存在。
        //为了优化,如果一个值不会被闭包改变,或者在闭包创建后不会改变,swift可能会改为捕获并保存一份对值得拷贝,swift也会负责被捕获变量的所有内存管理工作,包括释放不再需要的变量。
        
        
        let incrementbytem = testfffunc(forincrement: 10)
        //结果为10  该例子定义了一个常量,该常量指向一个每次调用会将其runningtotal变量增加10的 incrementer函数,调用这个函数多次可以多到多下面结果
        
        incrementbytem()//10
        incrementbytem()//20
        incrementbytem()//30
        print(incrementbytem()) //40
        //如果创建了一个新的incrementer 它会有属于自己的引用,指向一个全新,独立的runningtotal变量,
        //所以调用了3次 都是只在第一次创建的时候创建了一次runningtotal,不会再创建出新的值。所以会累加
        
        let incre111 = testfffunc(forincrement: 10)
        print(incre111())
        //该实例与上一个都用了一个函数赋,不会涉及函数内部的变量,函数内部变量只在的定义的时候创建,再怎么同样的调用,都是指向了最初定义的函数。
        //如果你将闭包赋值给一个实例的属性,并且该闭包通过访问该实例或其他成员而捕获了实例,你将在闭包和该实例间创建一个循环引用,swift使用捕获列表来打破这种循环强引用。
        
        //闭包是引用类型
        /*
         上例子中,incre111 和incrementbytem 都是常量,但是这些常量指向的闭包任然可以增加其捕获的变量的值,这是因为函数和闭包都是引用类型
         无论你将函数或者闭包赋值给一个常量还是变量,你实际上都是将常量或变量的值设置为对应函数或闭包的引用,上面的例子中,指向闭包的引用incrementbytem是一个常量,而非闭包内容本身
         
         这也意味着如果你将闭包赋值给了两个不同的常量或变量,两个值都会指向同一个闭包。
         */
        let inss = incre111
        inss()//20 用了同一个引用,都指向了同一个闭包
        
        //逃逸闭包
        /*
         当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才会执行,我们称该闭包从函数中逃逸,当你定义接收闭包作为参数的函数时,你可以在参数名之前标注 @escaping ,用来指明这个闭包是允许逃逸出这个函数的。
         一种能使闭包逃逸出函数的方法是,将这个闭包保存在一个函数外部定义的变量中,举个例子,很多启动异步操作的函数接收一个闭包参数作为 completion handler 。这类函数会在异步操作开始之后立即返回,但是闭包直到异步操作结束后才会被调用,
         */
        
        var completionHandlers :[()->Void] = []
        func funcwithEscapingClosure(comletionHandler : @escaping () ->Void) {
            //函数funcwithEscapingClosure 无返回值,参数无逃逸闭包 ()->void
            completionHandlers.append(comletionHandler)
            //该函数接收一个闭包作为参数,该闭包被添加到一个函数外定义的数组中,如果你不将这个参数标记为 @escaping ,就会得到一个编译错误。
        }
        
        func noescaping(completionhandler : ()->Void){
            completionhandler()
            //completionHandlers.append(completionhandler)//当在用编译器使用completionhandler会提示一条红线不让使用
            //上行代码报错,我说缺少一个@escaping  Passing non-escaping parameter 'completionhandler' to function expecting an @escaping closure
            //目前认为如果想要把闭包作为参数,还要在函数内使用该闭包,那么就必须在参数定义时加上 @escaping使闭包为逃逸闭包
            
        }
        
        //将一个闭包标记为@escaping意味着你必须在闭包中显式的引用self,比如说,在下面代码中,传递到funcwithEscapingClosure中的闭包是一个逃逸闭包,这意味着它需要显式的引用self,相对的,传递到 noescaping 中的闭包是一个非逃逸闭包,这意味着它可以隐式引用self
       
        func dosth() {
            funcwithEscapingClosure {
                self.x = 100  //x 不能写在func 里,要写在全局变量里,
            }
            
            noescaping {
                x = 200
            }
        }
        let instance = testclass()
        instance.dosth()
        print(instance.x)
        
        
        
        
        //自动闭包
        /*
         我们经常会调用采用自动闭包的函数,但是很少去实现这样的函数。
         assert(condition:message:file:line:) 函数接受自动闭包作为它的condition参数和message参数,它的condition参数仅会在debug模式下被求值,它的message参数仅当condition参数为false时被计算求值
         自动闭包让你能够延迟求值,因为知道你调用这个闭包,代码段才会被执行,延迟求值对于那些副作用(side effect)和高计算成本的代码来说是很有益处的,因为它使得你能控制代码的执行时机,下面的代码展示了闭包如何延迟求值。
         */
        
        
        
        
        var customNames = ["beijing","nanjin","shanghai","tianjian","anhui","haha"]
        print(customNames.count)
        
        let customperProvider = { customNames.remove(at: 0)} //remove(at:) 返回的是一个数组的一个元素 不是整个数组
        //此时customperProvider又是闭包 又是一个返回有值的常量  引导类型的常量
        //定义的是 自动闭包不包含任何参数,被调用的时候 会返回被包装在其中表达式的“值”。
        print(customNames.count)
        
        print("customerprovier = \(customperProvider())")
        print(customNames.count)
        
        let copyfunc = customperProvider
        
        print(copyfunc())
        
        
        /*
         尽管在闭包的代码中,customnames的第一个元素被移除了,不过在闭包被调用之前,这个元素是不会被移除的,如果这个闭包永远不被调用,那么在闭包里面的表达式将永远不会执行,那以为这列表中的元素永远不会被移除,请注意,customerProvider的类型不是string 而是 ()->string,一个没有参数且返回值为string 的函数。
         */
        
        //将闭包作为参数传递给函数时,你能获得同样的延时求值行为
        func serve(costomer customerProvider : ()->String){
            print("now serving \(customperProvider())")
        }
        
        serve(costomer: {customNames.remove(at: 0)})
    
        //上面的server(customer:)函数接收一个返回顾客名字的显式的闭包,下面的完成了相同的操作,不过它并没有接收一个显式的闭包,而是通过将参数标记为@autoclosure来接收一个自动闭包,现在你可以将该函数当做接受 string 类型参数
        
        func server(costomer customerprovide : @autoclosure() -> String){
            print("now serving \(customperProvider())")
        }
        
        server(costomer: customNames.remove(at: 0))
        
        //如果参数需要传闭包进去 并且加了@autoclosure 标记 ,就不需要在调用时,把闭包样式全写进去,直接把需要返回的值,且是闭包的返回值得函数体放进去
        
        //过度的使用 自动闭包 @autoclosure 会让你的代码变得难以理解,上下文和函数名应该能够清晰的表明求职是被延迟执行。
        
        //如果想让一个自动闭包可以 逃逸,则应该同时使用 @autoclosure 和 @escaping 属性。
        var closersarray : [()->String] = []
        
        func autoandescaping(_ customerprovider : @autoclosure @escaping() ->String){
            closersarray.append(customperProvider)
        }
        print(customNames)
        autoandescaping(customNames.remove(at: 0))
        autoandescaping(customNames.remove(at: 0))
        
        for closer in closersarray{
            print("value = \(closer())")
        }
        
        //定义一个数组,数组的元素类型是闭包, 然后定义一个函数 需要传入一个闭包,闭包的返回值时string类型,然后在函数内,将传进去的闭包添加到闭包类型的数组里。然后调用函数,传入一个闭包返回值是对应的string类型的闭包。此时数组里已经存入一个闭包。但闭包还未执行。之后遍历数组,遍历的同时,调用临时变量后面加() 表示执行数组里的闭包元素。
        
        
        
    }
    
    
    
    var closers : [()->Void] = []
    func closerMeth(closer : @escaping ()->Void){
        closers.append(closer)
    }
    
    func noESCCloser(closer : ()->Void){
        
    }
    
    class testclass {
        var x = 10
        func dosth(){
            let classs = ViewController() //因为是在viewcontroller 类里定义的,用self 是取不到的,跨类操作了
            classs.closerMeth {
                self.x = 100 //本以为会取到父类viewcontroller的全局x,跟过去显示的是testclass类里的x 非父类的
            }
            classs.noESCCloser {
                x = 200 //跟过去显示的也是当前类的x
            }
        }
    }

    相关文章

      网友评论

          本文标题:Swift 4.0学习之旅—闭包

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