Runtime簡(jiǎn)稱運(yùn)行時(shí)衩茸。OC就是運(yùn)行時(shí)機(jī)制,也就是在程序運(yùn)行時(shí)候的一些機(jī)制筏勒,其中最主要的是消息機(jī)制。對(duì)于我們熟悉的C語(yǔ)言旺嬉,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)管行。但對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過(guò)程鹰服,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)病瞳,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
也就有了下面這兩點(diǎn)結(jié)論:
1悲酷、在編譯階段套菜,OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn)设易,只要聲明過(guò)就不會(huì)報(bào)錯(cuò)逗柴。
2、在編譯階段顿肺,C語(yǔ)言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)戏溺。
我們定義一個(gè)純Swift的類 TestASwiftClass ,代碼如下:
class TestASwiftClass{
var aBoll :Bool = true
var aInt : Int = 0
func testReturnVoidWithaId(aId : UIView) {
print("TestASwiftClass.testReturnVoidWithaId")
}
再寫一個(gè)繼承自 UIViewController 的 ViewController
class ViewController: UIViewController{
let testStringOne = "testStringOne"
let testStringTwo = "testStringTwo"
let testStringThr = "testStringThr"
var count:UInt32 = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let SwiftClass = TestASwiftClass()
let proList = class_copyPropertyList(object_getClass(SwiftClass),&count)
for i in 0..<numericCast(count) {
let property = property_getName(proList?[i]);
print("屬性成員屬性:%@",String.init(utf8String: property!) ?? "沒(méi)有找到你要的屬性");
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
上面的代碼也很簡(jiǎn)單屠尊,我們?cè)赩iewController中添加了一些變量旷祸,然后通過(guò)Runtime的方法嘗試著先來(lái)獲取一下我們最上面定義的純Swift類TestASwiftClass的屬性,你運(yùn)行上面代碼你就會(huì)發(fā)現(xiàn):
什么都沒(méi)有K侠ァM邢怼!為什么浸赫?闰围?
下面我們先給出答案,用它來(lái)解釋一下為什么我們通過(guò)上面Runtime的API沒(méi)有獲取到任何東西既峡,然后再接著用OC來(lái)證明一下我們說(shuō)的結(jié)論:
C 語(yǔ)言是在函數(shù)編譯的時(shí)候決定調(diào)用那個(gè)函數(shù)羡榴,在編譯階段,C要是調(diào)用了沒(méi)有實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)运敢。
OC 的函數(shù)是屬于動(dòng)態(tài)調(diào)用校仑,在編譯的時(shí)候是不能決定真正去調(diào)用那個(gè)函數(shù)的,只有在運(yùn)行的時(shí)候才能決定去調(diào)用哪一個(gè)函數(shù) 传惠,在編譯階段迄沫,OC可以調(diào)用任何的函數(shù),即使這個(gè)函數(shù)沒(méi)有實(shí)現(xiàn)涉枫,只要聲明過(guò)也就不會(huì)報(bào)錯(cuò)邢滑。
純Swift類的函數(shù)的調(diào)用已經(jīng)不是OC的運(yùn)行時(shí)發(fā)送消息,和C類似,在編譯階段就確定了調(diào)用哪一個(gè)函數(shù)困后,所以純Swift的類我們是沒(méi)辦法通過(guò)運(yùn)行時(shí)去獲取到它的屬性和方法的乐纸。
Swift 對(duì)于繼承自O(shè)C的類,為了兼容OC摇予,凡是繼承與OC的都是保留了它的特性的汽绢,所以可以使用Runtime獲取到它的屬性和方法等等其他我們?cè)贠C中獲得的東西。
針對(duì)上面給出的結(jié)論侧戴,我們看看Swift對(duì)于繼承自O(shè)C的類是不是保留了OC所有的特性呢宁昭?再看下面代碼,只是做一個(gè)簡(jiǎn)單的修改,把通過(guò)object_getClass方法獲取的對(duì)象寫成self:
let proList = class_copyPropertyList(object_getClass(self),&count)
for i in 0..<numericCast(count) {
let property = property_getName(proList?[i]);
print("屬性成員屬性:%@",String.init(utf8String: property!) ?? "沒(méi)有找到你要的屬性");
}
可以看到我們獲取到了我們?cè)赩iewController中定義的變量酗宋。這樣也就證明了的確是上面答案說(shuō)的那樣积仗。
那這樣就又衍生出一個(gè)問(wèn)題
那Swiftw就沒(méi)辦法利用Runtime了嗎?
想一想蜕猫,要是真的Swift沒(méi)辦法利用Runtime寂曹,那是一件得多讓人失望的事!答案也肯定是否定的回右,我們還是能讓Swift用Runtime的隆圆。看下面的代碼:
class TestASwiftClass{
dynamic var aBoll :Bool = true
var aInt : Int = 0
dynamic func testReturnVoidWithaId(aId : UIView) {
print("TestASwiftClass.testReturnVoidWithaId")
}
}
嗯翔烁,我們利用了dynamic(英文單詞動(dòng)態(tài)的意思)關(guān)鍵字渺氧,在第一個(gè)變量和方法的定義前面我們添加了這個(gè)關(guān)鍵字,那添加了這個(gè)關(guān)鍵字之后又什么變化呢蹬屹?我們?cè)偻ㄟ^(guò)最開(kāi)始我們獲取純Swift類的代碼獲取一下試試侣背,看結(jié)果!
結(jié)果:
可以看到這里是獲取到了變量了的哩治。(這里是獲取屬性沒(méi)有寫獲取方法代碼所以是值拿到變量沒(méi)有拿到方法)
aBoll 這個(gè)變量前面是添加了dynamic關(guān)鍵字的秃踩,我們獲取到了衬鱼。在aInt這個(gè)變量前面我們是沒(méi)有添加的业筏,所以可以看到我們是沒(méi)有獲取到這個(gè)變量的,那關(guān)鍵的就是我們要理解:dynamic 關(guān)鍵字的含義:
首先有 @objc 這個(gè)關(guān)鍵字鸟赫,它是用來(lái)將Swift的API導(dǎo)出來(lái)給 Object-C 和 Runtime 使用的蒜胖,如果你類繼承自O(shè)C的類,這個(gè)標(biāo)識(shí)符就會(huì)被自動(dòng)加進(jìn)去抛蚤,加了這標(biāo)識(shí)符的屬性台谢、方法無(wú)法保證都會(huì)被運(yùn)行時(shí)調(diào)用,因?yàn)镾wift會(huì)做靜態(tài)優(yōu)化岁经,想要完全被聲明成動(dòng)態(tài)調(diào)用朋沮,必須使用 dynamic 標(biāo)識(shí)符修飾,當(dāng)然添加了 dynamic 的時(shí)候缀壤,它會(huì)自己在加上@objc這個(gè)標(biāo)識(shí)符樊拓。
這樣我們就理解了dynamic這個(gè)關(guān)鍵字纠亚,知道了它的作用,那我們接下來(lái)就是嘗試著多使用一下 Swift Runtime筋夏。
Swift Runtime
1蒂胞、獲取方法:
let methodList = class_copyMethodList(object_getClass(SwiftClass), &count)
for ind in 0..<numericCast(count) {
let method = method_getName(methodList![ind])
print("屬性成員方法:",String.init(NSStringFromSelector(method)))
}
2、屬性成員變量:
let IvarList = class_copyIvarList(object_getClass(SwiftClass),&count)
for index in 0..<numericCast(count) {
let Ivar = ivar_getName((IvarList?[index])!)
print("屬性成員變量:",String.init(utf8String: Ivar!) ?? "沒(méi)有找到你想要的成員變量")
}
3条篷、協(xié)議列表:
let protocalList = class_copyProtocolList(object_getClass(self),&count)
for index in 0..<numericCast(count) {
let protocal = protocol_getName((protocalList?[index])!)
print("協(xié)議:",String.init(utf8String: protocal) ?? "沒(méi)有找到你想要的協(xié)議")
}
4骗随、方法交換
這個(gè)就是Runtime的一個(gè)重點(diǎn)了,仔細(xì)說(shuō)一說(shuō)赴叹。
OC的動(dòng)態(tài)性最常用的其實(shí)就是方法的替換鸿染,將某個(gè)類的方法替換成自己定義的類,從而達(dá)到Hook的作用乞巧。(以前面試有人問(wèn)過(guò)OC怎樣Hook一個(gè)消息牡昆,那時(shí)候太懵懂,不知道怎么說(shuō)摊欠!不知道大家有沒(méi)有遇到過(guò)丢烘?)
對(duì)于純粹的Swift類,由于前面的測(cè)試你知道無(wú)法拿到類的屬性方法等些椒,也就沒(méi)辦法進(jìn)行方法的替換播瞳,但是對(duì)于繼承自NSObject的類,由于集成了OC的所有特性免糕,所以是可以利用Runtime的屬性來(lái)進(jìn)行方法替換赢乓,記得我們前面說(shuō)的dynamic關(guān)鍵字。
func ChangeMethod() -> Void {
// 獲取交換之前的方法
let originaMethodC = class_getInstanceMethod(object_getClass(self), #selector(self.originaMethod))
// 獲取交換之后的方法
let swizzeMethodC = class_getInstanceMethod(object_getClass(self), #selector(self.swizzeMethod))
//替換類中已有方法的實(shí)現(xiàn),如果該方法不存在添加該方法
//獲取方法的Type字符串(包含參數(shù)類型和返回值類型)
//class_replaceMethod(object_getClass(self), #selector(self.swizzeMethod), method_getImplementation(originaMethodC), method_getTypeEncoding(originaMethodC))
print("你交換兩個(gè)方法的實(shí)現(xiàn)")
method_exchangeImplementations(originaMethodC, swizzeMethodC)
}
dynamic func originaMethod() -> Void {
print("我是交換之前的方法")
}
dynamic func swizzeMethod() -> Void {
print("我是交換之后的方法")
}
5石窑、關(guān)聯(lián)屬性
說(shuō)上面的方法Hook比較重要的話牌芋,這個(gè)關(guān)聯(lián)屬性也是比較重要的,在前面我總結(jié)OC的Runtime的時(shí)候在方法的添加這里專門有提過(guò)一個(gè)Demo松逊,我們把這個(gè)Demo重新整理一下躺屁,導(dǎo)航的漸變就是利用Runtime給導(dǎo)航添加屬性來(lái)實(shí)現(xiàn)的。
extension UINavigationBar {
var navigationGradualChangeBackgroundView:UIView?{
get{
return objc_getAssociatedObject(self, &self.navigationGradualChangeBackgroundView) as? UIView;
}
set{
objc_setAssociatedObject(self, &self.navigationGradualChangeBackgroundView, navigationGradualChangeBackgroundView, objc_AssociationPolicy.OBJC_ASSOCIATION_COPY_NONATOMIC)
}
}
func setNavigationBackgroundColor (backgroundColor: UIColor) -> Void {
if (self.navigationGradualChangeBackgroundView == nil) {
self.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
self.navigationGradualChangeBackgroundView = UIView.init(frame: CGRect.init(x: 0, y: -20, width: SCREENWIDTH, height: self.bounds.size.height + 20))
self.navigationGradualChangeBackgroundView!.isUserInteractionEnabled = false
self.insertSubview(self.navigationGradualChangeBackgroundView!, at: 0)
}
self.navigationGradualChangeBackgroundView!.backgroundColor = backgroundColor
}
func removeNavigationBackgroundColor() -> Void {
self.setBackgroundImage(nil, for: UIBarMetrics.default)
self.navigationGradualChangeBackgroundView!.removeFromSuperview()
self.navigationGradualChangeBackgroundView = nil
}
}