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 其实是一个匿名函数,闭包也可以当成是匿名函数,而这个表达式要具备
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 语法。使用闭包表达式能更简洁的传达信息。当然闭包表达式的好处 有很多:
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的一部分。而对于引用类型来说,就直接把其在堆区的地址存放到闭包的数据结构里面。
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
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)
}
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"
}
原文地址:https://blog.csdn.net/LinShunIos/article/details/125563929
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_26284.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!