1.我們知道Objective-C是一門動態(tài)性語言,能夠通過runtime API調(diào)用和替換任意方法局义,那Swift也具有這些動態(tài)性嗎欢际?
- 分析示例
PureSwiftClass是純Swift類,MuixSwiftClass是一個繼承自NSObject的類耍属。兩個類中都包含了屬性托嚣、方法。
//MARK: - 純SwiftClass
class PureSwiftClass {
@objc var bolValue: Bool = false
@objc var age: Int = 0
@objc var height: Float = 0
@objc var name: String?
@objc var exName: String?
@objc func testPureAction() {
print("PureSwiftClass.testPureAction")
}
}
class MuixSwiftClass: UIViewController {
@objc var bolValue: Bool = false
@objc var age: Int = 0
@objc var height: Float = 0
@objc var name: String?
@objc var exName: String?
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
@objc func createSubView(view : UIView) {
print("MuixSwiftClass.createSubView")
}
@objc func testVoidWithBool(boolValue : Bool , tempInt : Int , tempFloat : Float , str : String , obj : AnyObject) {
print("MuixSwiftClass.testVoidWithBool")
}
// @objc func testVoidWithTuple(boolValue : Bool , tempInt : Int , tempFloat : Float) -> (Bool , Int , Float){
// print("MuixSwiftClass.testVoidWithTuple")
// return (boolValue,tempInt,tempFloat)
// }
//
// @objc func testVoidWithCharacter(char : Character ) {
// print("MuixSwiftClass.testVoidWithCharacter")
// }
}
- 動態(tài)獲取方法厚骗、屬性的方法
func showClsRuntime (cls: AnyClass) {
print("----------方法列表開始-----------")
var methodNum: UInt32 = 0
let methodList: UnsafeMutablePointer<objc_property_t>! = class_copyMethodList(cls, &methodNum)
for index in 0..<numericCast(methodNum) {
let method: Method = methodList[index]
if let methodName: String = String(utf8String: property_getName(method)){
print(methodName)
}
}
print("----------方法列表結(jié)束-----------")
free(methodList)
print("----------屬性列表開始-----------")
var propertyNum: UInt32 = 0
let propertyList: UnsafeMutablePointer<objc_property_t>! = class_copyPropertyList(cls, &propertyNum)
for index in 0..<numericCast(propertyNum) {
let property: objc_property_t = propertyList[index]
if let proName: String = String(utf8String: property_getName(property)){
print(proName)
}
}
free(propertyList)
print("----------屬性列表開始-----------")
}
- 方法調(diào)用示例
showClsRuntime(cls: MuixSwiftClass.self)
showClsRuntime(cls: PureSwiftClass.self)
- 打印結(jié)果
MuixSwiftClass動態(tài)獲取方法示启、屬性
----------方法列表開始-----------
bolValue
setBolValue:
exName
setExName:
createSubViewWithView:
testVoidWithBoolWithBoolValue:tempInt:tempFloat:str:obj:
.cxx_destruct
name
setName:
initWithCoder:
initWithNibName:bundle:
viewDidLoad
viewDidAppear:
height
setHeight:
setAge:
age
----------方法列表結(jié)束-----------
----------屬性列表開始-----------
bolValue
age
height
name
exName
----------屬性列表結(jié)束-----------
PureSwiftClass動態(tài)獲取方法、屬性
----------方法列表開始-----------
bolValue
setBolValue:
exName
setExName:
testPureActionWithAId:
name
setName:
height
setHeight:
setAge:
age
----------方法列表結(jié)束-----------
----------屬性列表開始-----------
bolValue
age
height
name
exName
----------屬性列表結(jié)束-----------
純Swfit類PureSwiftClass都未獲取到方法领舰、屬性夫嗓,對于MuixSwiftClass獲取成功。
1.純Swift類的函數(shù)調(diào)用已經(jīng)不再是Objective-c的運行時發(fā)消息冲秽,而是類似C++的vtable舍咖,在編譯時就確定了調(diào)用哪個函數(shù),所以沒法通過runtime獲取方法锉桑、屬性排霉。
2.MuixSwiftClass繼承自UIViewController,基類NSObject民轴,而Swift為了兼容Objective-C攻柠,凡是繼承自NSObject的類都會保留其動態(tài)性球订,所以我們能通過runtime拿到他的方法。這里有一點說明:老版本的Swift(如2.2)是編譯期隱式的自動幫你加上了@objc辙诞,而4.0以后版本的Swift編譯期去掉了隱式特性辙售,必須使用顯式標(biāo)示!
3.可以看到MuixSwiftClass中有兩個注釋掉的方法飞涂。因為此兩個方法在加上@objc后旦部,Xcode直接報錯提示此方法參數(shù)類型不能映射到OC類型,runtime也就無法獲取较店。
4.因為方法參數(shù)里的Character和Tuple兩個類型是Swift獨有的士八,無法映射到OC的類型。
5.結(jié)合以上幾點梁呈,不管是純Swift類還是繼承自NSObject的類只要在屬性和方法前面添加@objc關(guān)鍵字就可以使用runtime婚度。
Method Swizzling
MuixSwiftClass方法替換代碼
extension MuixSwiftClass {
@objc func swizzle_viewWillAppear(_ animated: Bool) {
swizzle_viewWillAppear(animated)
print("swizzle_viewWillAppear")
}
}
extension MuixSwiftClass: SelfAware{
static func awake() {
takeOnceTime
}
private static let takeOnceTime: Void = {
let originalSelector = #selector(viewWillAppear(_:))
let swizzledSelector = #selector(swizzle_viewWillAppear(_:))
swizzlingForClass(MuixSwiftClass.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
}()
}
PureSwiftClass方法替換代碼
extension PureSwiftClass {
@objc func swizzle_testPureAction() {
swizzle_testPureAction()
print("swizzle_testPureAction")
}
}
extension PureSwiftClass: SelfAware {
static func awake() {
self.takeOnceTime
}
private static let takeOnceTime: Void = {
let originalSelector = #selector(testPureAction)
let swizzledSelector = #selector(swizzle_testPureAction)
swizzlingForClass(OtherPureSwiftClass.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
}()
}
方法調(diào)用代碼(具體調(diào)用原理可參考 資源鏈接)
protocol SelfAware: class {
static func awake()
static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector)
}
extension SelfAware {
static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
let originalMethod = class_getInstanceMethod(forClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
guard (originalMethod != nil && swizzledMethod != nil) else {
return
}
if class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!)) {
class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount {
(types[index] as? SelfAware.Type)?.awake()
}
types.deallocate()
}
}
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
UIApplication.runOnce
return super.next
}
}
打印如下
swizzle_viewWillAppear
PureSwiftClass.testPureAction