最近在用Swift来播映体系轰动音和运用Aspects来hook办法时遇到了点Swift闭包与OC的block和C言语函数指针转化的问题,所以学习并记录下。

一、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函数或许直接写一个闭包在参数方位。

下面示例代码中供给了3种解决办法:

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的函数,将作为swiftFunc参数传入
    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或许局部变量),会编译报错,这种状况怎样处理呢?

如下代码,在给下面AudioServicesAddSystemSoundCompletion函数的第4个参数传一个对应类型的closure, 假如closure中运用了self或许局部变量会报错:

A C function pointer cannot be formed from a closure that captures context

即不能从捕获了变量的closure形成C函数指针,Swift中的closure跟OC中的block一样,会捕获局部变量(self也属于局部变量),不捕获大局变量和静态变量。

解决办法有3个:

  • 第一种: 依据这个体系函数的特色,最终一个参数是一个指针,且体系在调用闭包时会将这个指针作为第二个参数,所以咱们能够将要捕获的变量转为指针传入这个体系函数的最终一个参数。咱们界说大局变量times, 然后转为指针withUnsafeMutablePointer后传入最终一个参数方位。

  • 第二种:直接将self转为指针作为最终一个参数传入,需求用unsafeBitCast函数将self转为指针传入。

  • 办法三:在闭包中运用大局变量,不运用会被捕获的内容。这个不引荐,由于大局变量会一直存在内存里。

具体见代码:

class ABCClass {
var times = 0
func testSound() {
    // C函数指针的闭包中引证了局部变量或许self
    // 解决办法:
    // 依据这个体系函数的特色,最终一个参数是一个指针,且体系在调用闭包时会将这个指针作为第二个参数,所以咱们能够将要捕获的变量转为指针传入这个体系函数的最终一个参数
    // 办法一:
    // 留意:(假如在闭包中捕获的是一个局部变量,那么这个局部变量需求自己确保不会太早被开释。)
    // var times2 = 10 // 这里不能用times2这个办法:由于假如将这个局部变量转为指针传入,则在闭包调用前现已被销毁,这不是预期的成果。
    // 所以下面的times需求是成员变量
    let times2Pointer = withUnsafeMutablePointer(to: &times, { 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来完结。

Unmanaged运用的状况:假如外部没有持有self,则这里需求passRetained来引证,以确保在closure调用前没有被销毁。

比如如下:

    // 办法四:
    // 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中指针