<iOS>Swift闭包

Swift闭包


闭包是功能性自包含模块,可以在代码中被传递和使用。

Swift中的闭包与C和Objective-C中的blocks以及其他一些编程语言中的lambdas比较相似。


闭包可以捕获和存储其所在上下文中任意常量和变量的引用。这就是所谓的闭合并包裹着这些常量和变量,俗称闭包。Swift会为您管理在捕获过程中涉及到的内存操作。


在Swift函数章节中介绍的全局和嵌套函数实际上也是特殊的闭包,闭包采取如下三种形式之一:

1、全局函数是一个有名字但不会捕获任何值的闭包

2、嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包

3、闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的没有名字的闭包


Swift的闭包表达式拥有简洁的风格,并鼓励在常见场景中以实现语法优化,主要优化如下:

1、利用上下文推断参数和返回值类型

2、单表达式(single-expression)闭包可以忽略return关键字

3、参数名简写

4、Trailing闭包语法


闭包表达式

嵌套函数是一种在比较复杂函数中方便进行命名和定义自包含代码模块的方式。当然,有时候撰写小巧的没有完整定义和命名的类函数结构也是很有用处的,尤其是在处理一些函数并需要将另外一些函数作为该函数的参数时。


闭包表达式是一种利用简洁语法构建内联闭包的方式。闭包表达式提供了一些语法优化,使得撰写闭包简单明了。

下面闭包表达式的例子通过使用几次迭代展示了sort函数定义和语法优化的方式。每一次迭代都用更简洁的方式描述了相同的功能。


sort函数

Swift标准库提供了sort函数,会根据您提供的排序闭包将已知类型数组中的值进行排序。一旦排序完成,函数会返回一个与原数组大小相同的新数组。该数组中包含已经正确排序的同类型元素。


下面的闭包表达式示例使用sort函数对一个String类型的数组进行字母逆序排序,以下是初始数组值:

let names = [“Chris”,“Alex”,“Ewa”,“Barry”,“Daniella”];


排序函数有两个参数:

1、已知类型值的数组

2、一个闭包,采用相同类型的数组的内容的两个参数,并返回一个布尔值来表示是否将第一个值在排序时放到第二个值的前面或是后面。如果第一个值应该出现在第二个值之前,闭包需要返回true,否则返回false。


该例子对一个String类型的数组进行排序,因此排序闭包需为(String,String) -> Bool类型的函数。


提供排序闭包的一种方式是撰写一个符合其类型要求的普通函数,并将其作为sort函数的第二个参数传入:

func backwards(s1:String,s2:String) -> Bool{

    return s1 > s2

}

var reversed = sort(names,backwards)


如果第一个字符串(s1)大于第二个字符串(s2),backwards函数则返回true,表示在新的数组中s1应该出现在s2前。字符中的“大于”表示“按照字母顺序后出现”。这就意味着字母“B”大于字母“A”,字符串“Tom”大于字符串“Tim”。其将进行字母逆序排序,“Barry”将会排在“Alex”之后,以此类推。


然而,这是一个相当冗长的方式,本质上只是写了一个单表达式函数(a>b)。在下面的例子中,利用闭合表达式语法可以更好的构造一个内联排序闭包


闭包表达式语法

闭包表达式语法有如下一般形式:

{(parameters)-> returnType in

   statements

}

闭包表达式语法可以使用常量、变量和inout类型作为参数,但不提供默认值。也可以在参数列表的最后使用可变参数。元组也可以作为参数和返回值。


下面的例子展示了之前backwards函数对应的闭包表达式版本的代码:

reversed = sort(names,{(s1:String,s2:String) -> Bool in 

    return s1 > s2

})

需要注意的是内联闭包参数和返回值类型声明与backwards函数类型声明相同。在这两种方式中,都写成了(s1:String,s2:String) - > Bool类型。然而在内联闭包表达式中,函数和返回值类型都写在了大括号内,而不是大括号外。


闭包的函数体部分由关键字in引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。


因为这个闭包的函数体部分如此短以至于可以将其改写成一行代码:

reversed = sort(names,{(s1:String,s2:String) -> Bool in return s1 > s2})

这说明sort函数的整体调用保持不变,一对圆括号仍然包裹住了函数中整个参数集合。而其中一个参数现在变成了内联闭包(相比于backwards版本的代码)。


根据上下文推断类型

因为排序闭包是作为函数的参数进行传入的,Swift可以推断其参数和返回值的类型。sort期望第二个参数是类型为(String,String) ->Bool的函数,因此实际上String,String和Bool类型并不需要作为闭包表达式定义中的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:

reversed = sort(names,{s1,s2 in return s1 > s2})

实际上任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数时,都可以推断出闭包的参数和返回值类型,这意味着几乎不需要利用完整格式构造任何内联闭包。


然而,你也可以使用明确的类型,如果你想它避免读者阅读可能存在的歧义,这样还是值得鼓励的。这个排序函数例子,闭包的目的是恨明确的,即排序替换,而且对读者来说可以完全的假设闭包可能会使用字符串值,因为它正协助一个字符串进行排序。


单行表达式闭包可以省略return

单行表达式闭包可以通过隐藏return关键字来隐式返回单行表达式的结果,如上版本的例子可以改写为:

reversed = sort(names,{s1,s2 in s1 > s2})

在这个例子中,sort函数的第二个参数函数类型明确了闭包必须返回一个Bool值。因为闭包函数体只包含了一个单一表达式(s1<s2),该表达式返回Bool类型值,因此这里没有歧义,return关键字可以省略。


参数名简写

Swift自动为内联函数提供了参数名称简写功能,可以直接通过$0,$1,$2等名字来引用闭包的参数的值。


如果在闭包列表中使用参数名称简写,可以在闭包参数列表中省略对其的定义,并且对应参数名称简写的类型会通过函数类型进行推断。in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成

reversed = sort(names,{$0 > $1})

在这个例子中,$0和$1表示闭包中的第一个和第二个String类型的参数。


运算符函数

实际上还有一种更简短的方式来撰写上面例子中的闭包表达式。Swift的String类型定义了关于大于号(>)的字符串实现,让其作为一个函数接受两个String类型的参数并返回Bool类型的值。而这正好与sort函数的第二个参数需要的函数类型相符合。因此,可以简单地传递一个大于号,Swift可以自动推动出想使用大于号的字符串函数实现:

reversed = sort(names,>)


Trailing闭包

如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用trailing闭包来增强函数的可读性。


Trailing闭包是一个书写在函数括号之外(之后)的闭包表达式,函数支持将其作为最后一个参数调用。

func someFunctionThatTasksAClosure(closure:() -> ()){

    //函数体部分

}


//以下是不使用trailing闭包进行函数调用

someFunctionThatTasksAClosure({

    //闭包主题部分

})


//以下是使用trailing闭包进行函数调用

someFunctionThatTasksAClosure(){

    //闭包主体部分

}

注意:如果函数只需要闭包表达式一个参数,当使用trailing闭包时,甚至可以把()省略掉。


在上例中作为sort函数参数的字符串排序闭包可以改写为:

reversed = sort(names){$0 > $1}


当闭包非常长以至于不能在一行中进行书写时,Trailing闭包就变得非常有用。


举例来说,Swift的Array数组有一个map方法,其获取一个闭包表达式作为唯一参数。数组中的每一个元素调用一次该闭包函数,并返回该元素所映射的值(也可以是不同类型的值)。具体的映射方式和返回值类型由闭包来指定。


当提供给数组改闭包函数后,map方法将返回一个新的数组,数组中包含了与原数组一一对应的映射后的值。


下列介绍了如何在map方法中使用trailing闭包将Int类型数组[16,58,510]转换为包含对应String类型的数组[“OneSix”,“FiveEight”,“FiveOneZero”];


let digitNames = [0:“Zero”,1:“One”,2:“Two”,3:“Three”,4:“Four”,5:“Five”,6:“Six”,7:“Seven”,8:“Eight”,9:“Nine”,];

let numbers = [16,58,510]

上面的代码创建了整数数字到它们的英文名字之间映射字典。同时定义了一个准备转换为字符串的整型数组。

现在可以通过传递一个trailing闭包numbers的map来创建对应的字符串版本数组。需要注意的是调用numbers.map不需要在map后面包含任何括号,因为只需要传递闭包表达式这一个参数,并且该闭包表达式参数通过trailing方式撰写

let strings = numbers.map{

    (var number) -> String in

    var output = “”

    while number > 0{

          output = digitNames[number % 10]! + output

           number /= 10

    }

    return output

}

//strings常量被推断为字符串类型数组,即String[]

map在数组中为每一个元素调用了闭包表达式。不需要指定闭包的输入参数number的类型,因为可以通过要映射的数组类型进行推断。


闭包number参数被声明为一个变量参数,因此可以在闭包函数体内对其进行修改。闭包表达式制定了返回值类型为String,以表明存储映射值的新类型为String。

闭包表达式在每次被调用的时候创建了一个字符串并返回。其使用求余运算符(number%10)计算最后一位数字并利用digitNames字典获取所映射的字符串。

注意:字典digitNames下标后跟着一个叹号(!),因为字典下标返回一个可选值,表明即使该key不存在也不会查找失败。在上例中,它保证了number % 10可以总是作为一个digitNames字典的有效下标key。因此叹号可以用于强展开存储在可选下标选项中的String类型值。

从digitNames字典中获取的字符串被添加到输出的前部,逆序建立了一个字符串版本的数字。

上例中trailing闭包语法在函数后整洁封装了具体的闭包功能,而不再需要将整个闭包包裹在map函数的括号内。


捕获(Caputure)

闭包可以在其定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。


Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数体内的函数。嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。


下例为一个叫做makeIncrementor的函数,其包含了一个叫做incrementor嵌套函数。嵌套函数incrementor从上下文捕获了两个值,runningTotal和amount。之后makeIncrement将incrementor作为闭包返回。每次调用incrementor时,其会以amount作为增量增加runningTotal的值。

func makeIncrementor(forIncrement amount:Int) ->() -> Int{

    

    var runningTotal = 0;

    func incrementor() ->Int{

        runningTotal += amount

        return runningTotal

    }

     return incrementor

}

makeIncrementor返回类型为()-> Int。这意味着其返回的是一个函数,而不是一个简单类型的值。该函数在每次调用时不接受参数只返回一个Int类型的值。


makeIncrementor函数定义了一个整型变量runningTotal(初始为0),用来存储当前增加总数。该值通过incrementor返回。


makeIncrementor有一个Int类型的参数,其外部命令为forIncrement,内部命令为amount,表示每次increment被调用时runningTotal将要增加的量。


incrementor函数用来执行实际的增加操作。该函数简单地使用runningTotal增加amount,并将其返回。

如果单独看这个函数,会发现看上去不同寻常:

   func incrementor() ->Int{

        runningTotal += amount

        return runningTotal

    }

incrementor函数并没有获取任何参数,但是在函数体内访问了runningTotal和amount变量。这是因为其通过捕获在包含它的函数体内已经存在的runningTotal和amount变量而实现。


由于没有修改过amount变量,incrementor实际上捕获并存储了该变量的一个副本,而该副本随着incrementor一同被存储。

然而,因为每次调用该函数的时候都会修改runningTotal的值,incrementor捕获了当前runningTotal变量的引用,而不仅仅复制该变量的初始值。捕获一个引用保证了当makeIncrementor结束的时候并不会消失,也保证了当下一次执行incrementor函数时,runningTotal可以继续增加。


注意:Swift会决定捕获是引用还是拷贝值。不用标注amount或者runningTotal来声明在嵌入的incrementor函数中的使用方式。Swift同时也处理runingTotal变量的内存管理操作,如果不再被incrementor函数使用,则会被清除。


下面为一个使用makeIncrementor的例子:

let incrementByTen = makeIncrementor(forIncrement:10)

该例子定义了一个叫做incrementByTen的常量,该常量指向一个每次调用会增加10的incrementor函数。调用这个函数多次可以得到以下结果:

incrementByTen() //返回值为10

incrementByTen() //返回值为20

incrementByTen() //返回值为30


如果创建了另一个incrementor,其会有一个属于自己的独立的runningTotal变量的引用。下面的例子中,incrementBySeven捕获了一个新的runningTotal变量,该变量和incrementByTen中捕获的变量没有任何联系:

let incrementorBySeven = makeIncrementor(forIncrement:7)

incrementBySeven()//返回值为7

incrementByTen()//返回值为40

注意:如果闭包分配给一个类实例的属性,并且该闭包通过指向该实例或其成员来捕获了该实例,将创建一个在闭包和实例间的强引用环。Swift使用捕获列表打破这种强引用环。


闭包是引用类型

上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量值。这是因为函数和闭包都是引用类型。


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

上面的例子中,incrementByTen指向闭包的引用是一个常量,而并非闭包内容本身。


这也意味着将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen

alsoIncrementByTen()

//返回值为50


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
struct VideoPicker: UIViewControllerRepresentable { @Environment(.presentationMode) private var presentationMode let sourceType: UIImagePickerController.SourceType // let onImagePicked: (UIImage) -> Void let onURLPicked: (URL) -> Void final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { @Binding private var presentationMode: PresentationMode private let sourceType: UIImagePickerController.SourceType private let onURLPicked: (URL) -> Void init(presentationMode: Binding<PresentationMode>, sourceType: UIImagePickerController.SourceType, onURLPicked: @escaping (URL) -> Void) { presentationMode = presentationMode self.sourceType = sourceType self.onURLPicked = onURLPicked } func imagePickerController( picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage // onImagePicked(uiImage) if let url = info[.mediaURL] as? URL{ onURLPicked(url) } presentationMode.dismiss() } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { presentationMode.dismiss() } } func makeCoordinator() -> Coordinator { return Coordinator(presentationMode: presentationMode, sourceType: sourceType, onURLPicked: onURLPicked) } func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIImagePickerController { let picker = UIImagePickerController() picker.sourceType = sourceType picker.delegate = context.coordinator picker.mediaTypes = ["public.movie"] return picker } func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<VideoPicker>) { } }这段代码获取的url中绝对路径不准确
05-24

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值