7.闭包

  闭包是自包含的函数代码块,可以在代码中被传递和使用。Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的匿名函数比较相似。

  闭包采取如下三种形式之一:

  1. 全局函数是一个有名字但不会捕获任何值的闭包;
  2. 嵌套函数是一个有名字并可以捕获其封闭函数域内值的闭包;
  3. 闭包表达式是一个利用轻量级语法所写的可以捕获其上下文中变量或常量值的匿名闭包;

  闭包一般形式语法:

{ (parameters) -> returnType in
     statements
 }

  OC中的闭包语法:^ 返回值类型 参数列表 表达式。示例如下:

int (^myBlock)(int) = ^int (int num){
    return 3;
 };

1.闭包表达式

// TODO: 从排序说起
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"];  //定义一个数组
func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}  //定义排序规则函数
var reversed = names.sort(backwards)  //将排序规则函数作为参数传给sort

// TODO: 闭包语法实现
//【注意】:
//1:在内联闭包表达式中,函数和返回值类型都写在大括号内,而不是大括号外
//2:闭包的函数体部分由关键字【in】引入。该关键字表示闭包的参数和返回值类型定义已经完成,闭包函数体即将开始。
var reversed2 = names.sort({(s1: String, s2: String) -> Bool in
    return s1 > s2;
});

// TODO: 进化1(根据上下文推断类型):因为排序闭包函数是作为sort(_:)方法的参数传入的,Swift 可以推断其参数和返回值的类型必须是(String, String) -> Bool类型的函数。这意味着(String, String)和Bool类型并不需要作为闭包表达式定义的一部分。因为所有的类型都可以被正确推断,返回箭头(->)和围绕在参数周围的括号也可以被省略:
//【注意】:
//1:实际在任何情况下,通过内联闭包表达式构造的闭包作为参数传递给函数或方法时,都可以推断出闭包的参数和返回值类型。 这意味着闭包作为函数或者方法的参数时,几乎不需要利用完整格式构造内联闭包。
//2:但是,如果完整格式的闭包能够提高代码的可读性,则可以采用完整格式的闭包。
var reversed3 = names.sort({s1, s2 in return s1 > s2});

// TODO: 进化2(单行表达式闭包隐式返回):单行表达式闭包可以通过省略return关键字来隐式返回单行表达式的结果
var reversed4 = names.sort({s1, s2 in s1 > s2});

// TODO: 进化3(参数名称缩写):Swift 自动为内联闭包提供了参数名称缩写功能,可以直接通过$0,$1,$2来顺序调用闭包的参数,以此类推。如果在闭包表达式中使用参数名称缩写,那么也可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。【in关键字也同样可以被省略】,因为此时闭包表达式完全由闭包函数体构成:
var reversed5 = names.sort({$0 > $1});

// TODO: 进化4(运算符函数):Swift 的String类型定义了关于大于号(>)的字符串实现,其作为一个函数接受两个String类型的参数并返回Bool类型的值。而这正好与sort(_:)方法的参数需要的函数类型相符合。因此,可以简单地传递一个大于号,Swift 可以自动推断出想使用大于号的字符串函数实现:
var resersed6 = names.sort(>);

2.尾随闭包

  • 如果需要将一个很长的闭包表达式作为最后一个参数传递给函数,可以使用尾随闭包来增强函数的可读性。尾随闭包是一个书写在函数括号之后的闭包表达式,函数支持将其作为最后一个参数调用。
func someFunctionThatTakesAClosure(closure: () -> Void) {
    // 函数体部分
}

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

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

  类比OC中的Block:

- (void)blockTest:(void(^)(NSString *str))block
{
    block(@"Hello");
}

[self blockTest:^(NSString *str) {
    NSLog(@"%@", str);
}];

  学习尾随闭包的基本知识后,我们继续对上节的排序进行优化:

// TODO: 进化5(尾随闭包):
var reversed7 = names.sort(){$0 > $1};

// TODO: 进化6(尾随闭包):如果函数只需要闭包表达式一个参数,当使用尾随闭包时,甚至可以把()省略掉:
var reversed8 = names.sort{$0 > $1};

// TODO: 再看一个示例:将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];

let string = numbers.map(){(number : Int) -> String in
    var output = "";
    var temp = number;
    while temp > 0
    {
        output = digitNames[temp % 10]! + output;
        temp /= 10;
    }
    return output;
}
//【说明】
//1.map(_:)为数组中每一个元素调用了闭包表达式;
//2.闭包表达式在每次被调用的时候创建了一个叫做output的字符串并返回。
//3.通过尾随闭包语法,优雅地在函数后封装了闭包的具体功能,而不再需要将整个闭包包裹在map(_:)方法的括号内。

3.捕获值

  • 闭包可以在其被定义的上下文中捕获常量或变量。即使定义这些常量和变量的原作用域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。
// TODO: 先看一个示例:incrementer()函数并没有任何参数,但是在函数体内访问了runningTotal和amount变量。这是因为它从外围函数捕获了runningTotal和amount变量的引用。捕获引用保证了runningTotal和amount变量在调用完makeIncrementer后不会消失,并且保证了在下一次执行incrementer函数时,runningTotal依旧存在。
func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0;
    func incrementor() -> Int {
        runningTotal += amount;
        return runningTotal;
    }
    return incrementor;
}

let incrementByTen = makeIncrementor(forIncrement: 10);
incrementByTen();  //10
incrementByTen();  //20
incrementByTen();  //30
let incrementBySeven = makeIncrementor(forIncrement: 7);
incrementBySeven();  //7
incrementByTen();  //40
incrementBySeven();  //14

4.闭包是引用类型

  • 在上面的例子中,incrementBySeven和incrementByTen是常量,但是这些常量指向的闭包仍然可以增加其捕获的变量的值。这是因为函数和闭包都是引用类型。
  • 无论将函数或闭包赋值给一个常量还是变量,实际上都是将常量或变量的值设置为对应函数或闭包的引用。上面的例子中,指向闭包的引用incrementByTen是一个常量,而并非闭包内容本身。

5.非逃逸闭包

  • 当一个闭包作为参数传到一个函数中,但是这个闭包在函数返回之后才被执行,我们称该闭包从函数中逃逸。当定义接受闭包作为参数的函数时,你可以在参数名之前标注@noescape,用来指明这个闭包是不允许“逃逸”出这个函数的。将闭包标注@noescape能使编译器知道这个闭包的生命周期,从而进行编译优化。
  • 标记了 @noescape的闭包可以免去写引用self,普通的闭包使用self时都需要。因为它相当于一个同步的调用,不会产生循环引用。例如SnapKit的函数定义:

// TODO: 示例1:
class ClosureA
{
    var iTemp = 0;

    func methodA(@noescape closureTemp: (Void->Void))
    {
        closureTemp();
    }

    func methodB()
    {
        methodA {() -> Void in
            iTemp = 1;  //一般的closure都是要self.iTemp = 1,@noescape则不需要
        };
    }
}

//【说明】:引用到外部变量的闭包是不能加@noescape标记的。
class ClosureB
{
    var varibleA: (Void -> Void)!;

    func methodA(closureTemp: (Void->Void))
    {
        self.varibleA = closureTemp;  //引用到属性varibleA
    }
}
//上面的闭包closureTemp是不能加@noescape标记的。

6.自动闭包

  • 自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法让你能够用一个普通的表达式来代替显式的闭包,从而省略闭包的花括号。
// TODO: 代码1:
var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"];

func serveCustomer(customerProvider: () -> String)
{
    print("Now serving \(customerProvider())!");
}
serveCustomer({customersInLine.removeAtIndex(0)});

//serveCustomer({(Void) -> String in
// return customersInLine.removeAtIndex(0);
//});

// TODO: 代码2
var customersInLine2 = ["Chris", "Alex", "Ewa", "Barry", "Daniella"];

func serveCustomer2(@autoclosure customerProvider: () -> String)
{
    print("Now serving \(customerProvider())!");
}
serveCustomer2(customersInLine2.removeAtIndex(0));

//【说明】:
//1.上面两段代码实现了同一个功能;
//2.customerProvider参数自动转化为一个闭包,因为该参数被标记了@autoclosure特性。
//3.过度使用autoclosure会让代码变得难以理解,因此不太推荐应用。
时间: 2024-10-17 16:33:08

7.闭包的相关文章

Javascript学习日志(三):闭包

说实话,前面一节的原型和原型链在当初学的时候并没有很头疼,对着高级编程第三版撸了几遍就理解透了,闭包这一节真的挺头疼的,很惭愧,看了差不多十来遍吧,还翻看了网上的其他博客和解释文档,五花八门的表达方式,虽然核心思想都一致,但是实在是不能做到自己的理解,后来结合函数作用域链,好不容易有点开窍,趁着热乎劲儿,赶紧写下来,感兴趣的可以参考一下. 闭包:高级编程上面的解释是指有权访问另一个函数作用域中的变量的函数,(是一个函数): 创建闭包的常见方式,就是在一个函数内部创建另一个函数. 在理解闭包之前,

Python 闭包函数

一.定义: 1. 定义在函数内部的函数 2. 包含对外部作用域名字的引用,而不是对全局作用域名字的引用那么该内部函数就称为闭包函数 x=1 def f1(): x=11111111111 def f2(): print(x) return f2 func=f1() 二.闭包函数的应用:惰性计算 def index(url): # url='https://www.python.org' def get(): # return requests.get(url).text print(reques

学习Javascript闭包(Closure)

闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 下面就是我的学习笔记,对于Javascript初学者应该是很有用的. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外

Python 闭包

闭包 1.注意:返回的函数内部不要使用后续会发生变化的变量. def f(): gs = [] for k in range(1, 4): def g(i): return i + k gs.append(g) return gs 例如这段代码感觉上应该返回三个函数分别return i+1.return i+2.return i+3(i为新函数参数),但事实却是得到了三个return i+3. >>> from test import f >>> g1, g2, g3

swift 深入理解Swift的闭包

我们可用swift的闭包来定义变量的值. 先来一个简单的例子大家先感受感受. 定义一个字符串的变量的方法: 直接赋值 var str="JobDeer" 还可以用闭包的方式定义: var str:String={ return "JobDeer" }() 闭包还可以这么定义,省略了等号和括号: var str:String{ return "JobDeer" } 闭包中可以定义get方法. var str:String{ get{ return

浅谈Js闭包现象

一.1.我们探究这个问题的时候如果按照正常的思维顺序,需要知道闭包是什么它是什么意思,但是这样做会让我们很困惑,了解这个问题我们需要知道它的来源,就是我们为什么要使用闭包,先不管它是什么意思!      2.我们使用闭包是因为js的作用域问题,前面我们已经对作用域了解了一些,在函数中,外部不能读取到内部的变量,而内部可以读取到外部的变量,这其实也是js特殊的一个地方!(这个特殊其实是因为函数的作用域是一条作用域链,而且作用域链是有顺序的,我们称之为链式作用域结构!)那么问题来了,如果我们想要从函

js的闭包概念

一.变量的作用域要懂得闭包,起首必须懂得Javascript特别的变量作用域.变量的作用域无非就是两种:全局变量和局部变量.Javascript说话的特别之处,就在于函数内部可以直接读取全局变量. Js代码 var n=999; function f1(){ alert(n); } f1(); // 999另一方面,在函数外部天然无法读取函数内的局部变量.Js代码 function f1(){ var n=999; } alert(n); // error这里有一个处所须要重视,函数内部声明变量

对js中闭包,作用域,原型的理解

前几天,和朋友聊天,聊到一些js的基础的时候,有一种‘好像知道,好像又不不知道怎么讲的感觉’...于是捡起书,自己理一理,欢迎拍砖. 闭包 理解闭包首先要理解,js垃圾回收机制,也就是当一个函数被执行完后,其作用域会被收回,如果形成了闭包,执行完后其作用域就不会被收回. 如果某个函数被他的父函数之外的一个变量引用,就会形成闭包 闭包的作用,就是保存自己私有的变量,通过提供的接口(方法)给外部使用,但外部不能直接访问该变量. 例子(使用闭包): var test=(function(){ var

一个循环和闭包的例子

1 for (var i=1; i<=5; i++) { 2 setTimeout(function timer() { 3 console.log(i); 4 }, i*1000); 5 } 预期:分别输出数字 1-5,每秒一次,每次一个. 实际上,会每秒一次输出 5次6,. 知识点: JS引擎是单线程的,定时器的工作方式:按指定时间间隔,将定时器的代码添加到JS引擎的消息队列:而非到了指定的时间立即执行回调函数. 在上例中, 作用域 尽管循环中的五个函数在各个迭代中分别定义,但都被封闭在一个

(转)js闭包初入门

先看一段JS代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 function a(){             var num = 0;             function b(){                 num++;                 console.log(num);             }             return b;         }         var add = a();         add();