一.@convention
之前在执行vtable
取出的函数地址时,使用到了@convention
用于修饰函数类型
- 修饰Swift中的函数类型(调用C函数的时候)
- 调用OC方法时,修饰Swift函数类型
例如在C文件中添加一个函数,函数的参数是一个回调函数
int testConvention(int sum(int, int)) {
return sum(10, 20);
}
Swift中使用
//直接使用testConvention,编译器已经进行了优化,已经进行了转换
let result = testConvention( {
return $0 + $1
})
print(result) // 30
//testConvention传入一个closure
let closure = { (a: Int32, b: Int32) -> Int32 in
return a + b
}
//报错
let result1 = testConvention(closure)
此时将closure传入testConvention会报错A C function pointer can only be formed from a reference to a 'func' or a literal closure
大致的意思是,一个C函数指针只能由方法或字面闭包构成。简单来说就是类型不匹配
此时,就可以使用到@convention
来进行类型转换
//@convention(c)中的c指的是后面的(Int32, Int32) -> Int32是一个c函数
let closure: @convention(c) (Int32, Int32) -> Int32 = { (a: Int32, b: Int32) -> Int32 in
return a + b
}
let result1 = testConvention(closure)
二.defer
defer {}
里的代码会在当前代码块返回的时候执行,无论当前代码块是从哪个分支 return 的,即使程序抛出错误,也会执行。
如果多个 defer 语句出现在同一作用域中,则它们出现的顺序与它们执行的顺序相反,也就是 先出现的后执行。
func f() {
defer { print("First defer") }
defer { print("Second defer") }
print("End of function")
}
f()
//End of function
//Second defer
//First defer
使用场景1:FileHandle
使用完成后需要执行closeFile()
使用场景2:类型指针,管理析构函数
let count = 2
let pointer = UnsafeMutablePointer.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)
defer {
pointer.deinitialize(count: count)
pointer.deallocate()
}
使用场景3:我们在进行网络请求的时候,可能有不同的分支进行回调函数的执行
func netRquest(completion: () -> Void) {
defer {
self.isLoading = false
completion()
}
guard error == nil else { return }
}
三.全局变量和局部变量捕获的区别
1.全局变量
Swift代码
var i = 10
let closure = {
i += 10
}
closure()
IR代码
define i32 @main(i32 %0, i8** %1) #0 {
entry:
%2 = bitcast i8** %1 to i8*
// 将10赋值到TSI,Int64上。
store i64 10, i64* getelementptr inbounds (%TSi, %TSi* @"$s4main1iSivp", i32 0, i32 0), align 8
// s4mainyycfU_ 闭包的函数地址
// s4main7closureyycvp 就是closure变量。也就是let closure = {xxx}中的closure
// 将i8*赋值到closure(%swift.function)的第一个元素i8*上。%swift_function ---> {i8*, %swift_refcounted*}
store i8* bitcast (void ()* @"$s4mainyycfU_" to i8*), i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7closureyycvp", i32 0, i32 0), align 8
// closure的%swift_refcounted*赋值给null
store %swift.refcounted* null, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7closureyycvp", i32 0, i32 1), align 8
// %3为closure的i8*
%3 = load i8*, i8** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7closureyycvp", i32 0, i32 0), align 8
// %4为closure的%swift_refcounted*
%4 = load %swift.refcounted*, %swift.refcounted** getelementptr inbounds (%swift.function, %swift.function* @"$s4main7closureyycvp", i32 0, i32 1), align 8
// 引用计数+1
%5 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %4) #1
// i8*强转为void (%swift.refcounted*)*
%6 = bitcast i8* %3 to void (%swift.refcounted*)*
// 执行i8*的函数
call swiftcc void %6(%swift.refcounted* swiftself %4)
// 引用计数-1
call void @swift_release(%swift.refcounted* %4) #1
ret i32 0
}
// 闭包的调用
define internal swiftcc void @"$s4mainyycfU_"() #0 {
entry:
%access-scratch = alloca [24 x i8], align 8
%0 = bitcast [24 x i8]* %access-scratch to i8*
call void @llvm.lifetime.start.p0i8(i64 -1, i8* %0)
call void @swift_beginAccess(i8* bitcast (%TSi* @"$s4main1iSivp" to i8*), [24 x i8]* %access-scratch, i64 33, i8* null) #1
// 从TSI(全局变量)中取值之前存放的值,这里也就是10
%1 = load i64, i64* getelementptr inbounds (%TSi, %TSi* @"$s4main1iSivp", i32 0, i32 0), align 8
// 这里执行10与10做了一个加法
%2 = call { i64, i1 } @llvm.sadd.with.overflow.i64(i64 %1, i64 10)
%3 = extractvalue { i64, i1 } %2, 0
%4 = extractvalue { i64, i1 } %2, 1
%5 = call i1 @llvm.expect.i1(i1 %4, i1 false)
br i1 %5, label %8, label %6
6: ; preds = %entry
store i64 %3, i64* getelementptr inbounds (%TSi, %TSi* @"$s4main1iSivp", i32 0, i32 0), align 8
call void @swift_endAccess([24 x i8]* %access-scratch) #1
%7 = bitcast [24 x i8]* %access-scratch to i8*
call void @llvm.lifetime.end.p0i8(i64 -1, i8* %7)
ret void
8: ; preds = %entry
call void @llvm.trap()
unreachable
}
结论:对于全局变量,闭包是不捕获的
2.局部变量
当然我们在闭包(上)也分析过局部变量的情况,这里也再一次做简单的回顾
Swift代码
func test() {
var i = 10
let closure = {
i += 10
}
closure()
}
test()
IR代码
define hidden swiftcc void @"$s4main4testyyF"() #0 {
entry:
%0 = alloca %TSi, align 8
%1 = bitcast %TSi* %0 to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
%closure.debug = alloca %swift.function, align 8
%2 = bitcast %swift.function* %closure.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 16, i1 false)
%3 = bitcast %TSi* %0 to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %3)
//将10存入TSI
%._value = getelementptr inbounds %TSi, %TSi* %0, i32 0, i32 0
store i64 10, i64* %._value, align 8
//把10存进堆区空间
%4 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #3
%5 = bitcast %swift.refcounted* %4 to <{ %swift.refcounted, %TSi* }>*
%6 = getelementptr inbounds <{ %swift.refcounted, %TSi* }>, <{ %swift.refcounted, %TSi* }>* %5, i32 0, i32 1
store %TSi* %0, %TSi** %6, align 8
%7 = bitcast %swift.function* %closure.debug to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* %7)
%closure.debug.fn = getelementptr inbounds %swift.function, %swift.function* %closure.debug, i32 0, i32 0
store i8* bitcast (void (%swift.refcounted*)* @"$s4main4testyyFyycfU_Tf0s_nTA" to i8*), i8** %closure.debug.fn, align 8
%closure.debug.data = getelementptr inbounds %swift.function, %swift.function* %closure.debug, i32 0, i32 1
store %swift.refcounted* %4, %swift.refcounted** %closure.debug.data, align 8
%8 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %4) #3
//把堆区实例对象传入到了闭包
call swiftcc void @"$s4main4testyyFyycfU_Tf0s_nTA"(%swift.refcounted* swiftself %4)
call void @swift_release(%swift.refcounted* %4) #3
call void @swift_release(%swift.refcounted* %4) #3
%9 = bitcast %TSi* %0 to i8*
call void @llvm.lifetime.end.p0i8(i64 8, i8* %9)
ret void
}
总结:对于局部变量,开辟堆区内存空间,存入局部变量,执行闭包时把堆区实例对象传入进去
四.捕获引用类型
Swift代码
class LGTeacher {
var age = 10
}
func test() {
var t = LGTeacher()
let closure = {
t.age += 10
}
closure()
}
test()
IR代码
// test函数
define hidden swiftcc void @"$s4main4testyyF"() #0 {
entry:
%0 = alloca %T4main9LGTeacherC*, align 8
%1 = bitcast %T4main9LGTeacherC** %0 to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
// %closure.debug其实就是一个%swift.function
%closure.debug = alloca %swift.function, align 8
%2 = bitcast %swift.function* %closure.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 16, i1 false)
%3 = bitcast %T4main9LGTeacherC** %0 to i8*
call void @llvm.lifetime.start.p0i8(i64 8, i8* %3)
%4 = call swiftcc %swift.metadata_response @"$s4main9LGTeacherCMa"(i64 0) #6
%5 = extractvalue %swift.metadata_response %4, 0
// $s4main9LGTeacherCACycfC ---> main.LGTeacher.__allocating_init() -> main.LGTeacher
// 创建LGTeacher实例变量
%6 = call swiftcc %T4main9LGTeacherC* @"$s4main9LGTeacherCACycfC"(%swift.type* swiftself %5)
%7 = bitcast %T4main9LGTeacherC* %6 to %swift.refcounted*
%8 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %7) #3
store %T4main9LGTeacherC* %6, %T4main9LGTeacherC** %0, align 8
// 将创建的LGTeacher实例转化为%swift.refcounted*
%9 = bitcast %T4main9LGTeacherC* %6 to %swift.refcounted*
%10 = bitcast %swift.function* %closure.debug to i8*
call void @llvm.lifetime.start.p0i8(i64 16, i8* %10)
%closure.debug.fn = getelementptr inbounds %swift.function, %swift.function* %closure.debug, i32 0, i32 0
// 将闭包表达式的函数地址存入closure.debug.fn
store i8* bitcast (void (%swift.refcounted*)* @"$s4main4testyyFyycfU_Tf2i_nTA" to i8*), i8** %closure.debug.fn, align 8
%closure.debug.data = getelementptr inbounds %swift.function, %swift.function* %closure.debug, i32 0, i32 1
// 把LGTeacher存入closure.debug.data
store %swift.refcounted* %9, %swift.refcounted** %closure.debug.data, align 8
// LGTeacher引用计数+1
%11 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %9) #3
// 执行闭包的时候传入LGTeacher的实例变量
// 至于之前声明的%closure.debug,存了函数地址及LGTeacher实例数据后,后续也再也没有使用了。具体还不清楚这里的用意
call swiftcc void @"$s4main4testyyFyycfU_Tf2i_nTA"(%swift.refcounted* swiftself %9)
call void @swift_release(%swift.refcounted* %9) #3
call void @swift_release(%swift.refcounted* %9) #3
%toDestroy = load %T4main9LGTeacherC*, %T4main9LGTeacherC** %0, align 8
call void bitcast (void (%swift.refcounted*)* @swift_release to void (%T4main9LGTeacherC*)*)(%T4main9LGTeacherC* %toDestroy) #3
%12 = bitcast %T4main9LGTeacherC** %0 to i8*
call void @llvm.lifetime.end.p0i8(i64 8, i8* %12)
ret void
}
总结:对于引用类型,已经在堆区上了,因此不需要在堆区开辟内存空间。此时的闭包只需要在传值的过程中,把这个引用类型的地址传递进去就可行了。
五.捕获多个局部变量值
Swift代码
func makeIncreasemer(_ amount: Int) -> () -> Int {
var runningTotal = 10
func increasmer() -> Int {
runningTotal += amount
return runningTotal
}
return increasmer
}
IR代码
define hidden swiftcc { i8*, %swift.refcounted* } @"$s4main15makeIncreasemerySiycSiF"(i64 %0) #0 {
entry:
%amount.debug = alloca i64, align 8
%1 = bitcast i64* %amount.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %1, i8 0, i64 8, i1 false)
%runningTotal.debug = alloca %TSi*, align 8
%2 = bitcast %TSi** %runningTotal.debug to i8*
call void @llvm.memset.p0i8.i64(i8* align 8 %2, i8 0, i64 8, i1 false)
store i64 %0, i64* %amount.debug, align 8
// 把10存入到了开辟的堆区内存空间中
%3 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata, i32 0, i32 2), i64 24, i64 7) #2
%4 = bitcast %swift.refcounted* %3 to <{ %swift.refcounted, [8 x i8] }>*
%5 = getelementptr inbounds <{ %swift.refcounted, [8 x i8] }>, <{ %swift.refcounted, [8 x i8] }>* %4, i32 0, i32 1
%6 = bitcast [8 x i8]* %5 to %TSi*
store %TSi* %6, %TSi** %runningTotal.debug, align 8
%._value = getelementptr inbounds %TSi, %TSi* %6, i32 0, i32 0
store i64 10, i64* %._value, align 8
%7 = call %swift.refcounted* @swift_retain(%swift.refcounted* returned %3) #2
// 分配堆区内存空间
%8 = call noalias %swift.refcounted* @swift_allocObject(%swift.type* getelementptr inbounds (%swift.full_boxmetadata, %swift.full_boxmetadata* @metadata.3, i32 0, i32 2), i64 32, i64 7) #2
// 强转至<{ %swift.refcounted, %swift.refcounted*, %TSi }>*结构体
%9 = bitcast %swift.refcounted* %8 to <{ %swift.refcounted, %swift.refcounted*, %TSi }>*
// 向这个结构体的第2个元素存入%3(存有10的堆区内存空间)
%10 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 1
store %swift.refcounted* %3, %swift.refcounted** %10, align 8
// 向这个结构体的第3个元素存入%0,也就是函数的参数
%11 = getelementptr inbounds <{ %swift.refcounted, %swift.refcounted*, %TSi }>, <{ %swift.refcounted, %swift.refcounted*, %TSi }>* %9, i32 0, i32 2
%._value1 = getelementptr inbounds %TSi, %TSi* %11, i32 0, i32 0
store i64 %0, i64* %._value1, align 8
call void @swift_release(%swift.refcounted* %3) #2
//第一个元素闭包的执行地址
//第二个元素%8
%12 = insertvalue { i8*, %swift.refcounted* } { i8* bitcast (i64 (%swift.refcounted*)* @"$s4main15makeIncreasemerySiycSiF10increasmerL_SiyFTA" to i8*), %swift.refcounted* undef }, %swift.refcounted* %8, 1
ret { i8*, %swift.refcounted* } %12
}
还原此时闭包的数据结构
func makeIncreasemer(_ amount: Int) -> () -> Int {
var runningTotal = 10
func increasmer() -> Int {
runningTotal += amount
return runningTotal
}
return increasmer
}
struct ClosureData {
//闭包的地址
var ptr: UnsafeRawPointer
var object: UnsafePointer
}
struct MultBox {
var object: HeapObject
var box: UnsafePointer>
var value: T2
}
struct Box {
var object: HeapObject
var value: T
}
struct HeapObject {
var metadata: UnsafeRawPointer
var refCount: Int
}
struct NoMeanStruct {
let f: () -> Int
}
let f = NoMeanStruct(f: makeIncreasemer(20))
let ptr = UnsafeMutablePointer.allocate(capacity: 1)
ptr.initialize(to: f)
defer {
ptr.deinitialize(count: 1)
ptr.deallocate()
}
let p = ptr.withMemoryRebound(to: ClosureData>.self, capacity: 1) {
$0.pointee
}
print(p.object.pointee.box.pointee.value) // 捕获的局部变量10
print(p.object.pointee.value) // 捕获的参数20
/*
闭包已经把所有执行过程中所有的信息放入结构体里面,执行的地址,捕获的值
调用的过程中其实就是从结构体拿到值,从而完成调用
*/
六.逃逸闭包
当闭包作为一个实际参数传递给一个函数的时候,并且是在函数返回之后调用,我们就说这个闭包逃逸了。当我们声明一个接受闭包作为形式参数时,你可以在形式参数前写@escaping
来明确闭包是允许逃逸的。
- 作为函数的参数传递
- 当前闭包在函数内部异步执行或者被存储
- 函数结束,闭包被调用,声明周期结束
class TestEscapingClosure {
var complete:((Int) -> Void)?
//赋值存储
func test(handle: @escaping ((Int) -> Void)) {
complete = handle
}
//异步执行
func testAsyn(handle: @escaping ((Int) -> Void)) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
handle(10)
}
}
}
1.非逃逸闭包
/*
非逃逸闭包:闭包的生命周期是确定的
注意:可选闭包表达式是逃逸闭包!!!
func testNoEscaping(_ f: (() -> Void)?) {
f?()
}
如果f是可选值就会变为逃逸闭包,虽然闭包生命周期与函数生命周期一致,
但是编译器默认可选闭包表达式是逃逸闭包
Swift库 pr:4905,就是解释的可选闭包表达式
https://github.com/apple/swift/pull/4905
*/
func testNoEscaping(_ f: () -> Void) {
f()
}
func test() {
var age = 10
//闭包的生命周期与函数生命周期一致
//此时的age就不会被捕获到堆区了
testNoEscaping {
age += 20
}
}
test()
- 不会产生循环引用,函数作用域内释放
- 编译器更多性能优化(没有retain和release)
- 上下文的内容保存在栈上,不是堆上
2.逃逸闭包
之前的案例
func test() {
var age = 10
//逃逸闭包,个人理解为赋值导致编译器认为它是一个逃逸闭包,因此会把age捕获到堆区
let closure = {
age += 20
}
closure()//;
// {age + 10}()
}
test()
七.自动闭包
是一种用来把实际参数传递给函数表达式打包的闭包,不接受任何实际参数,当其调用的是,返回内部表达式的值
好处:用普通表达式代替闭包的写法,语法糖的一种
func debugOutPrint(_ condition: Bool , _ message: @autoclosure () -> String){
if condition {
print("lg_debug:\(message())")
}
}
//只能传入String,编译器会将字符串变为闭包形式{return String}
debugOutPrint(true, "testString")
//v2必须传入的是函数/闭包表达式
func xxx(_ v1: Int, _ v2: () -> Int) -> Int {
return v1 > 0 ? v1 + 1 : v2()
}
//v2必须传入的是值,然后编译器再生成闭包表达式{return String}
func xxx(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
return v1 > 0 ? v1 : v2()
}
/*
Swift中函数名相同,参数列表不同或者返回值类型不同的函数都可以构成重载
因此这2个xxx函数就构成了函数的重载
*/
八.简单了解Swift多线程
Swift多线程默认还是调用的GCD(libdispatch)
1.Operation与DispatchWorkItem
执行一个任务
let operation = BlockOperation{
print(#function)
}
operation.start()
let workItem = DispatchWorkItem{
print(#function)
}
workItem.perform()
2.DispatchWorkItem可以cancel
class ViewController: UIViewController {
lazy var workItem: DispatchWorkItem = {
return DispatchWorkItem{
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
guard self.workItem.isCancelled == false else {
return
}
print(#function)
}
}
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
DispatchQueue.global().async(execute: workItem)
}
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
workItem.cancel()
}
}
3.DispatchWorkItem实现Operation依赖的效果
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let workItem1 = DispatchWorkItem{
print("1")
}
let workItem2 = DispatchWorkItem{
print("2")
}
let workItem3 = DispatchWorkItem{
print("3")
}
//类似于Operation中的addDependency
workItem3.notify(queue: .global(), execute: workItem1)
workItem1.notify(queue: .global(), execute: workItem2)
DispatchQueue.global().async(execute: workItem3)
//3,1,2
}
4.Operation依赖是怎么形成的
/*
依赖形成的原因:Operation里有一个数组__dependencies来保存依赖的任务,
当所有的依赖任务全部执行完成后(isReady)才能执行自己
当前任务执行的先决条件:是否处于isReady的状态或者依赖数组执行完毕
*/
internal func _addDependency(_ op: Operation) {
withExtendedLifetime(self) {
withExtendedLifetime(op) {
var up: Operation?
_lock()
if __dependencies.first(where: { $0 === op }) == nil {
__dependencies.append(op)
up = op
}
_unlock()
if let upwards = up {
upwards._lock()
_lock()
let upIsFinished = upwards._state == __NSOperationState.finished
if !upIsFinished && !_isCancelled {
assert(_unfinishedDependencyCount >= 0)
//增加未完成的依赖个数, += 1
_incrementUnfinishedDependencyCount()
upwards._addParent(self)
}
_unlock()
upwards._unlock()
}
//当前未完成的依赖数组个数为0时(isReady),执行当前任务
Operation.observeValue(forKeyPath: _NSOperationIsReady, ofObject: self)
}
}
}
5.Operation任务放入队列中是怎么调度的
internal func _addOperations(_ ops: [Operation], barrier: Bool = false) {
//前面代码依据任务生命周期的状态形成链表,会依次调度链表的任务
...
if !barrier {
_schedule()
}
}
internal func _schedule() {
...
/*
本质上还是通过GCD来进行任务的调度
*/
let queue: DispatchQueue
if __mainQ {
queue = DispatchQueue.main
} else {
queue = __dispatch_queue ?? _synthesizeBackingQueue()
}
if let schedule = operation.__schedule {
if operation is _BarrierOperation {
queue.async(flags: .barrier, execute: {
schedule.perform()
})
} else {
//有且只有一条线程~
queue.async(execute: schedule)
}
}
...
}
6.了解Operation
func aboutOperation(){
/*
1.认识Operation
Operation是一个抽象类,对GCD进行封装,并不具备封装操作的能力。
必须使用它的子类:BlockOperation、自定义子类继承Operation、NSInvocationOperation(Swift中不能使用,OC中使用)
*/
let opBlock = BlockOperation{
print("BlockOperation")
}
//注意这里任务执行方式为串行
opBlock.start()
/*
2.使用OperationQueue
*/
//默认是并发的,如果设置maxConcurrentOperationCount为1,则为串行队列
let queue = OperationQueue()
//最大并发数
queue.maxConcurrentOperationCount = 2
/*
3.设置任务队列优先级
*/
let op1 = BlockOperation{
print("1")
}
let op2 = BlockOperation{
print("2")
}
let op3 = BlockOperation{
print("3")
}
//当队列并发数<任务个数,先执行队列优先级高的任务
//当队列并发数>任务个数,所有任务一起执行
op1.queuePriority = .low
op2.queuePriority = .high
op3.queuePriority = .normal
//waitUntilFinished如果为true会阻塞线程,直到ops中所有任务执行完成后,后面的任务才能执行
queue.addOperations([op1, op2, op3], waitUntilFinished: false)
/*
4.Operation依赖关系
*/
let opA = BlockOperation{
print("A")
}
let opB = BlockOperation{
print("B")
}
let opC = BlockOperation{
print("C")
}
let opD = BlockOperation{
print("D")
}
let opE = BlockOperation{
print("E")
}
/*
C依赖于A、B
E依赖于C、D
A和B执行完才能执行C
C和D执行完才能执行E
E永远最后执行
*/
opC.addDependency(opA)
opC.addDependency(opB)
opE.addDependency(opC)
opE.addDependency(opD)
let queue1 = OperationQueue()
queue1.maxConcurrentOperationCount = 5
queue1.addOperations([opA,opB,opC,opD,opE], waitUntilFinished: false)
}
7.Swift中使用dispatch_once
在Swift3后就废弃了dispatch_once函数,因为Swfit创建单例本来就是线程安全的
extension DispatchQueue {
private static var _tokens = [String]()
static func once(token: String, block: () -> Void) {
//确保线程安全
objc_sync_enter(self)
defer{
objc_sync_exit(self)
}
if _tokens.contains(token) {
return
}
_tokens.append(token)
block()
}
}
let uuid = UUID().uuidString
DispatchQueue.once(token: uuid) {
//doSomething
}