• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Swift ——闭包

武飞扬头像
LinShunIos
帮助2

1. 函数类型

之前在代码的书写过程中,我们已经或多或少的接触过函数,函数本身也有自己的类型,它由形式参数类型,返回类型组成。如果将函数传递给a变量,那么就可以借助a来调用方法,这里addTwoInts代表函数类型。如果出现了同名函数,但是返回值或者参数不一样,那么就需要指明函数类型。否则就会报 ambiguous user of *** 错误,因为这个时候编译器不知道要给a赋值哪一个参数。把函数赋值给a,那么a里面储存的是什么呢?

func addTwoInts(_ a: Int, _ b: Int) -> Int { return a   b
}
var a = addTwoInts
a(10,20)
func addTwoInts(_ a: Double, _ b: Double) -> Double { return a   b
}
func addTwoInts(_ a: Int, _ b: Int) -> Int { return a   b
}
var a: (Double, Double) -> Double = addTwoInts
a(10, 20)
var b = a
b(20 ,30)

运行后打印a,这里看懂存储的是函数类型.这里函数类型也是引用类型,当把函数赋值给a的时候,就把函数的metadata赋值给了a.
学新通
这里打印b,看到了存储的信息是一样的,证明了函数类型是引用类型。
学新通
到源码中查看函数的metadata,之前的学习中可以知道,直接搜索名字functiontype,就找到了函数在源码中的结构体。看到其继承自TargetMetadata,那么就会有kind,也就是oc中的isa。其次还有flags。
学新通
flags的类型点进去可以看到看到这里有enum,这些关键字标注了函数的类型。
学新通
函数类型里面还有一个ResultType,也就是返回值类型。最后就是参数了,这里看到是用连续的内存空间存放参数,读取参数是用getParameters方法读取。
学新通
模仿出function的结构体,这里flags与上掩码可以得到有多少个参数。
学新通
那么这里就可以使用unsafeBitCast将函数转换为TargetFunctionTypeMetadata结构体,然后调用numberArguments方法看到有几个参数了。
学新通

2. 闭包

闭包是一个捕获了上下文的常量或者是变量的函数,即使常量或者变量的作用域已经不在,闭包仍然可以修改他们。当每次修改捕获值的时候,其实是修改堆区的值。闭包中没有block哪有全局,堆,栈的概念了。

在函数makeIncrementer里面定一个函数incrementer,incrementer里面使用了外部函数的变量runningTotal。当调用makeIncrementer() 复制给a的时候
,明显的可以知道incrementer的生命周期比makeIncrementer要长,而runningTotal是属于外部函数的变量,外部函数返回之后,外部函数就消失了,而内部函数要想使用runningTotal,那么就必须把runningTotal捕获到incrementer的内部,这样才能在使用incrementer的使用,正确使用runningTotal的值,incrementer和匹配的runningTotal的变量,构成了闭包。

func makeIncrementer() -> () -> Int {
  var runningTotal = 10
  func incrementer() -> Int {
      runningTotal  = 1
      return runningTotal
      
  }
  return incrementer
}
var a = makeIncrementer()

闭包表达式

{ (param) -> (returnType) in //do something
}

OC 中的 Block 其实是一个匿名函数,闭包也可以当成是匿名函数,而这个表达式要具备

  • 作用域(也就是大括号)
  • 参数和返回值
  • 函数体(in)之后的代码
    Swift 中的闭包即可以当做变量,也可以当做参数传递,这里我们来看一下下面的例子熟悉一 下:
var closure : (Int) -> Int = { (age: Int) in return age}

同样的我们也可以把我们的闭包声明一个可选类型:

//错误的写法
var closure : (Int) -> Int? closure = nil
//正确的写法
var closure : ((Int) -> Int)? closure = nil

还可以通过 let 关键字将闭包声明位一个常量(也就意味着一旦赋值之后就不能改变了)

let closure: (Int) -> Int
closure = {(age: Int) in return age
}
closure = {(age: Int) in return age
}

同时也可以作为函数的参数

func test(param : () -> Int){
    print(param())
}
var age = 10
test { () -> Int in
    age  = 1
return age

}

3. 尾随闭包

当我们把闭包表达式作为函数的最后一个参数,如果当前的闭包表达式很⻓,我们可以通过尾随闭包的书写方式来提高代码的可读性。

func test(_ a: Int, _ b: Int, _ c: Int, by: (_ item1: Int, _ item2: Int, _ item3: Int) -> Bool) -> Bool {
   return  by(a, b, c)
}
///原结构
test(10, 20, 30, by: {(_ item1: Int, _ item2: Int, _ item3: Int) -> Bool in
    return (item1   item2 < item3)
})
///后置闭包结构
test(10, 20, 30) {
}

其中闭包表达式是 Swift 语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处 有很多:

  • 利用上下文推断参数和返回值类型
  • 单表达式可以隐士返回,既省略 return 关键字
  • 参数名称的简写(比如我们的 $0)
  • 尾随闭包表达式
var array = [1, 2, 3]
array.sort(by: {(item1 : Int, item2: Int) -> Bool in return item1 < item2 } array.sort(by: {(item1, item2) -> Bool in return item1 < item2 }) array.sort(by: {(item1, item2) in return item1 < item2 }) array.sort{(item1, item2) in item1 < item2 }
array.sort{ return $0 < $1 }
array.sort{ $0 < $1 }
array.sort(by: <)

4. 捕获值

回顾一下oc中的捕获值,这里会打印2,1,2,对于block来说,这里会当作自己的变量,捕获i瞬时的值,这个时候外面对i进行改变,不会改变block中的值。如果用 __block修饰,那么就会改变block中变量的值了,这个时候就会打印2,2,2.

- (void)testBlock{
 NSInteger i = 1;
void(^block)(void) = ^{ 
NSLog(@"block %ld:", i);
};
i  = 1;
NSLog(@"before block %ld:", i);
 block();
NSLog(@"after block %ld:", i);
}

而对于闭包来说,这里就会打印2,2,2了。这里闭包和block不一样,在闭包中遇到i变量,这里是全局变量,那么闭包就会直接去取i的地址,拿到i的值。如果是在函数中,也就是局部变量,那么就会把变量捕获到堆区中,并且变量会成为closure的一部分。而对于引用类型来说,就直接把其在堆区的地址存放到闭包的数据结构里面。

学新通
下面会打印11,12,13,这里相当于操作同一个对象。

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal  = 1
        return runningTotal
    }
    return incrementer
}

let makeInc = makeIncrementer()

print(makeInc())

print(makeInc())

print(makeInc())

学新通

而这里打印的则都是11了,因为这里相当于每次创建一个新的实例对象。

func makeIncrementer() -> () -> Int {
    var runningTotal = 10
    func incrementer() -> Int {
        runningTotal  = 1
        return runningTotal
    }
    return incrementer
}

let makeInc = makeIncrementer()

print(makeIncrementer()())

print(makeIncrementer()())

print(makeIncrementer()())
学新通

5. 闭包本质

  • 1、捕获值原理:在堆上开辟内存空间,并将捕获的值放到这个内存空间里
  • 2、修改捕获值时:实质是修改堆空间的值
  • 3、闭包是一个引用类型(引用类型是地址传递),闭包的底层结构(是结构体:闭包执行地址 捕获变量堆空间的地址 == 闭包)
  • 4、函数也是一个引用类型(本质是一个结构体,其中只保存了函数的地址)。

6. OC Block 和 Swift闭包相互调用

我们在OC中定义的Block,在Swift中是如何调用的呢?

typedef void(^ResultBlock)(NSError *error);

@interface LSTest : NSObject

  (void)testBlockCall:(ResultBlock)block;

@end

@implementation LSTest

  (void)testBlockCall:(ResultBlock)block {
    NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:400 userInfo:nil];
    block(error);
}

@end
学新通

在 Swift 中我们可以这么使用

LSTest.testBlockCall { error in
    let errorcast = error as NSError
    print(errorcast)
}

func test(_ block: ResultBlock) {
    let error = NSError.init(domain: NSURLErrorDomain, code: 400, userInfo: nil)
    block(error)
}

比如我们在 Swift里这么定义,在OC中也是可以使用的

class LSTeacher: NSObject {
    @objc static var closure: (() -> ())?
}
  (void)test {
    LSTeacher.closure = ^{
        NSLog(@"end");
    };
}

7. defer

defer {} 里的代码会在当前代码块返回之后执行,无论当前代码块是从哪个分支 return 的,即使程序抛出错误,也会执行。如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是 先出现的后执行。那么defer有什么作用呢?defer一般用来关闭数据库/文件来使用,当打开了数据库或者文件,我们需要及时的关掉它,而等一个方法里面的逻辑代码太多,就需要用到defer来关闭数据库/文件。
学新通
有一点需要注意的是,如果有guard的话,应该将defer写在guard前面,否则defer不会执行。
学新通

8.逃逸闭包

逃逸闭包的定义:当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调 用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数的函数时,你可以在形式 参数前写 @escaping 来明确闭包是允许逃逸的。逃逸闭包的三个条件

  • 作为函数的参数传递
  • 当前闭包在函数内部异步执行或者被存储
  • 函数结束,闭包被调用,生命周期结束

一般逃逸闭包有2种情况

  • 当闭包被当作属性存储,导致函数完成时闭包生命周期被延长
    学新通
  • 异步执行网络请求
    学新通
    非逃逸闭包优点
  • 不会产生循环引用,函数作用域内释放
  • 编译器更多性能优化 (retain, relsase)
  • 上下文的内存保存再栈上,不是堆上

9. 自动闭包

自动闭包是一种用来把实际参数传递给函数表达式打包的闭包,不接受任何实际参数,当其调用时,返回内部表达式的值。用普通表达式代替闭包的写法,语法糖的一种。使用了自动闭包之后,那么就可以传入一个string或者是一个闭包。这里如果传入string就相当于把string用一个闭包包裹起来。

func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
    if condition {
      print("debug:(message())")
    }
}
debugOutPrint(true,getResult() )
debugOutPrint(true, getResult )

func getResult()->String{
    return "Application Error Occured"
}

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfkfabf
系列文章
更多 icon
同类精品
更多 icon
继续加载