最近在用Swift来播放系统震动音和使用Aspects
来hook方法时遇到了点问题,所以学习并记录下。
一、Swift闭包作为block或者c函数指针参数时需使用@convention
@convention(swift) : 表明这个是一个swift的闭包。
@convention(block) :表明这个是一个兼容oc的block的闭包。
@convention(c) : 表明这个是兼容c的函数指针的闭包。
1.1 作为C语言函数指针
下面AudioServicesAddSystemSoundCompletion
是一个系统播放震动的函数,其中第四个参数是一个c函数指针,此时如果将一个提前声明的闭包变量传入,则闭包声明时需要使用@convention(c)
修饰,否则会报如下错误:
A C function pointer can only be formed from a reference to a 'func' or a literal closure.
翻译过来就是一个C函数指针只能传一个func或者闭包字面量,即只能传一个Swift函数或者直接写一个闭包在参数位置。
func testConvention2() {
// 如果把closure放在外面定义好再作为参数传进去,则需要使用'@convention(c)'修饰
// 不然会报错
// A C function pointer can only be formed from a reference to a 'func' or a literal closure
// 解决方法一:使用'@convention(c)'修饰闭包
let completion: @convention(c) (SystemSoundID, UnsafeMutableRawPointer?) -> Void = { soundId, pointer in
print("@convention的闭包 ----")
}
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, nil, nil, completion, nil)
// 解决方法二:定义一个swift的函数作为参数
func swiftFunc(id: SystemSoundID, pointer: UnsafeMutableRawPointer?) {
print("swiftFunc---")
}
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, nil, nil, swiftFunc, nil)
// 解决方法三:参数位置写闭包字面量
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, nil, nil, { soundID, pointer in
print("字面量闭包--")
}, nil)
}
1.2 作为OC的block
相对于上面C语言函数指针,对于OC中的block,大部分情况下Swift闭包可以直接传入使用。但在Aspects中hook方法需要使用@convention(block)
修饰闭包,因为里面涉及到了block的方法签名。下面是一个Swift中使用Aspects
来hook方法的例子:
/// 做交换: 用一个全局的常量来实现的dispatchOnce效果
let doSwizzles: () = {
let oriSel1 = #selector(UIViewController.viewWillAppear(_:))
// '@convention(block)'修饰闭包作为参数传入才不会触发Aspects的断言奔溃
// 因为closure没有block的方法签名
let wrappedBlock: @convention(block) (AspectInfo, Bool) -> Void = { aspectInfo, _ in
print("wrappedBlock---")
}
_ = try? UIViewController.aspect_hook(oriSel1, with: AspectOptions.positionBefore, usingBlock: wrappedBlock)
}
二、闭包作为C语言指针传入时捕获变量怎么处理?
在上面例子中,闭包作为block参数时,是一样可以捕获局部变量的;但是,当闭包作为C语言函数指针时,闭包中如果捕获局部变量(一般是使用了self或者局部变量),会编译报错,这种情况怎么处理呢?
class ABCClass {
var times = 0
func testSound() {
// C函数指针的闭包中引用了局部变量或者self
/* 1.如下代码,
* 在给下面函数的第4个参数之间传一个对应类型的closure, 如果closure中使用了self或者局部变量会报错:
* A C function pointer cannot be formed from a closure that captures context
* 即不能从捕获了变量的closure形成C函数指针
* Swift中的closure跟OC中的block一样,会捕获局部变量(self也属于局部变量),不捕获全局变量和静态变量。 */
// 解决方法:
// 根据这个系统函数的特点,最后一个参数是一个指针,且系统在调用闭包时会将这个指针作为第二个参数,
// 所以我们可以将要捕获的变量转为指针传入这个系统函数的最后一个参数。
// 方法一:
// 注意:(如果在闭包中捕获的是一个局部变量,那么这个局部变量需要自己保证不会太早被释放。)
// var times2 = 10 // 如果将这个局部变量转为指针传入,则在闭包调用前已经被销毁,这不是预期的结果
// 所以下面的times需要是成员变量
let times2Pointer = withUnsafeMutablePointer(to: ×, { UnsafeMutableRawPointer($0) })
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, nil, nil, { _, pointer in
// 这里是控制音效播放3次后停止
var times22 = pointer?.assumingMemoryBound(to: Int.self)
times22?.pointee += 1
if times22?.pointee == 3 {
AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate)
return
}
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
}, times2Pointer)
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
// 方法二:直接将self转为指针作为最后一个参数传入
// 2.1 需要外部强引用self,保证在闭包回调时不会被释放
let selfPointer2 = unsafeBitCast(self, to: UnsafeMutableRawPointer.self)
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, nil, nil, { _, pointer in
guard let pointer = pointer else { return }
// 下面是控制音效播放3次后停止
let self_p = unsafeBitCast(pointer, to: AbbVC.self)
self_p.times += 1
if self_p.times == 5 {
AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate)
return
}
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
}, selfPointer2)
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
// 方法三:在闭包中使用全局变量,不使用会被捕获的内容。这个不推荐,因为全局变量会一直存在内存里。
}
}
三、使用Unmanaged来管理self的生命和指针转换
在上面的例子中,播放震动完毕的C函数回调中,如果闭包作为参数要使用self,我们还可以通过Unmanaged
来完成,例子如下:
// 方法二:
// 2.1 如果外部没有持有self,则这里需要passRetained来引用,以保证在closure调用时没有被销毁
let selfPointer = Unmanaged<AbbVC>.passRetained(self).toOpaque()
/* 2.2 如果外部已经持有self,则这里可以使用passUnretained,
let selfPointer = Unmanaged<AbbVC>.passUnretained(self).toOpaque()
且在使用完后不需要调用Unmanaged<AbbVC>.fromOpaque(pointer).release()
*/
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, nil, nil, { _, pointer in
guard let pointer = pointer else { return }
// 下面是控制音效播放3次后停止
let self_p = Unmanaged<AbbVC>.fromOpaque(pointer).takeUnretainedValue()
print(self_p.times)
self_p.times += 1
print(self_p.times)
if self_p.times == 5 {
Unmanaged<AbbVC>.fromOpaque(pointer).release()
AudioServicesRemoveSystemSoundCompletion(kSystemSoundID_Vibrate)
return
}
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
}, selfPointer)
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
代码解释:
1.
Unmanaged<AbbVC>.passRetained(self)
是创建一个Unmanaged类型的retain引用,会强引用self。
2.Unmanaged<AbbVC>.passUnretained(self)
创建一个Unmanaged类型的引用,不会强引用self。
3.Unmanaged<AbbVC>.fromOpaque(pointer).release()
是对之前创建的Unmanaged类型的强引用做一次release,不这样做的话,self不会释放。
推荐阅读:
swift的@convention
Swift中指针
网友评论