Swift-你可能会遇到的与OC混编问题

本篇文章总结一下自己Swift项目中遇到与OC混编的问题及解决办法,文章尽量全面实用。

一、Swift属性关联

Swift中属性关联的写法跟OC是比较类似的,看一个例子你应该就能懂。有兴趣深入了解iOS-底层原理 18:关联对象底层原理探索

    // 关联的key使用 Void?类型时因为它只占1个字节
    private var BtnTitleKey: Void?
    /// 这里使用属性关联来保存和访问btnTitle
    var btnTitle: String? {
        get { objc_getAssociatedObject(self, &BtnTitleKey) as? String }
        set {  objc_setAssociatedObject(self, &BtnTitleKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }

二、Swift中实现方法交换

在Swift中方法交换的一个例子.

// let变量在Swift中只会初始化一遍,若是函数则可以反复调用
static let swizzleMethods() -> Bool = {
    let originalSelector = #selector(UIApplication.sendEvent(_:))
    let swizzledSelector = #selector(UIApplication.my_sendEvent(_:))
    
    let originalMethod = class_getInstanceMethod(UIApplication.self, originalSelector)
    let swizzledMethod = class_getInstanceMethod(UIApplication.self, swizzledSelector)
    
    let didAddMethod = class_addMethod(UIApplication.self, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
    
    if didAddMethod {
        class_replaceMethod(UIApplication.self, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
    } else {
        method_exchangeImplementations(originalMethod!, swizzledMethod!)
    }

    return true
}()

三、Swift协议与OC

  1. Swift协议如果想在OC中使用,需要加@objc修饰,。
  2. 但修饰后,这个协议在Swift中只能被类遵守。
  3. @objc修饰之后可以定义可选实现的方法。不要为了实现协议可选方法而将协议改为@objc,Swift实现可选的方式是给协议写扩展,扩展中提供默认实现。
@objc
protocol Runable {
    @objc optional fun run()
}

四、Swift中KVO、KVC的写法

Swift中使用KVO、KVC的条件:

  1. 属性所在类必须是遵守NSObject的类。
  2. 属性需要使用@objc dynamic修饰,这样才会走OC的一套
func testKVO() {
    let aa = ZLKVO()
    print(aa)
    aa.age = 20
    aa.observation1?.invalidate() // 使对应的这个KVO监听失效
    aa.age = 30
}

class ZLKVO: NSObject {
    @objc dynamic var age = 10
    // 如果age属性没有使用@objc dynamic,属性变化不会走OC的方法调用流程,使用KVO\KVC都会奔溃
//    var age = 10
    
    var observation1: NSKeyValueObservation?
    
    init(age: Int = 10) {
        self.age = age
        super.init()
        // 方法1.OC样式的KVO
        self.addObserver(self, forKeyPath: "age", options: .new, context: nil)
        
        // 方法2.闭包样式的KVO
        self.observation1 = observe(\ZLKVO.age, options: .new) { kvo, change in
            print("kvo= ", kvo, "kvo.age= ", kvo.age) // age已经是新值了
            print("change = ", change, "change.newValue = ", change.newValue)
        }
    }

    // 方法一的监听结果在这里调用
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        print(change?[.newKey])
    }
    
    deinit {
        self.removeObserver(self, forKeyPath: "age")
    }
}

五、Swift继承OC类

如果逼不得已要继承OC里面的类,这里说一个注意点:
重写方法时,要加@objc,不加的话你会发现,如果该方法是在OC中调用,会执行OC父类的那个方法,自己重写的方法并没有执行。

六、Swift扩展OC类

这里也只说明一个注意点:
场景:Swift给OC类写扩展时,OC中对一个属性重写了setter,我们不希望执行这个setter,所以需要覆盖这个setter。
1.如果使用Swift扩展,我们不能重写存储属性,所以做不到。
2.如果采用重写setter方法,那么我们会发现在setter中给该属性赋值会导致循环调用,所以也行不通。
3.所以能采取的方式:①属性关联。②写子类继承,然后重写属性为计算属性,重写set方法,在set中将值设置给子类的新存储变量。

七、Swift调用C语言函数

C语言函数定义在OC文件中,Swift中要实现调用分为以下情况:

  1. Swift项目调用Pod库中的C语言函数,可以直接调用,也可以起一个别名后调用。
  2. Swift调用的地方跟C语言函数属于同一个Target下(即在相同工程里或相同Pod库里),那么需要@_silgen_name起别名后调用
/// 1.ZLTest_sum是同在工程里定义的C语言函数,起别名swift_sum
@_silgen_name("ZLTest_sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
/// 2.kStatusBarHeight是Pod库中定义的C语言函数,起别名swift_statusBarHeight
@_silgen_name("kStatusBarHeight") func swift_statusBarHeight() -> CGFloat
func testOC() {
    // 1.调用时只能使用swift_sum
    print(swift_sum(10, 20))
    
    // 2.swift中调用OC库的C语言函数是可以直接调用的,也可以起别名后调用
    print(kStatusBarHeight())
    print(swift_statusBarHeight())
}

八、Swift调用方法走OC的runtime调用流程

Swift类中的方法使用dynamic修饰之后,方法的调用会走OC的runtime调用流程。

class Dog: NSObject {
    @objc dynamic fun run() {}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章