前言
網(wǎng)上關(guān)于runtime的教程都有很多,但時(shí)很多大部分都是講解原理,并沒(méi)有實(shí)際運(yùn)用runtime寫(xiě)一些東西,所以很難讓人理解。我覺(jué)得要是有實(shí)際的運(yùn)用的話,應(yīng)該能夠更深刻的理解。
先看一個(gè)效果

圖-1 是我寫(xiě)的代碼,我們假裝json時(shí)從網(wǎng)絡(luò)上取來(lái)的數(shù)據(jù) , 然后把json的值賦給ViewController的model , model 是通過(guò)ModelCompatible這個(gè)協(xié)議得來(lái)的 ,如圖-2

ModelCompatible有一個(gè)屬性和一個(gè)方法,但凡有繼承來(lái)這個(gè)屬性的類(lèi)(ps : 只有類(lèi),雖然swift中的enum struct也能繼承協(xié)議。但是我們這個(gè)協(xié)議的后面是繼承了一個(gè)class的關(guān)鍵字,表示這個(gè)協(xié)議就只能被類(lèi)繼承了)
這個(gè)json里面的key值都和ViewController的屬性相同,當(dāng)把json賦值給model的時(shí)候,就會(huì)相應(yīng)地改變ViewController的里面的屬性的值,


這里打印了它的id,id也自動(dòng)也被自動(dòng)賦了值
這樣寫(xiě)的好處是,能大大減少我們的代碼,而且使得代碼的可讀性更高。
思路
先通過(guò)runtime遍歷一個(gè)的類(lèi)的所有屬性(不包括父類(lèi)),然后和model里面的key比較,如果這個(gè)屬性和key一樣,在比較這個(gè)屬性的類(lèi)型,如果是String , Int , float 之類(lèi)的,就把它們的json的key對(duì)應(yīng)的值直接賦值給ViewController的屬性的值。使用下面這個(gè)方法賦值。
setValue(value: Any?, forKey: String)
如果不是上面那個(gè)類(lèi)型,那么就賦值給它的text的路徑的屬性下(因?yàn)樗械膌abel , textField 都有text這個(gè)屬性),使用下面的代碼
setValue(value: Any? , forKeyPath: String)
如果這個(gè)屬性沒(méi)有text的屬性會(huì)怎么辦,會(huì)崩潰。所以,前面使用了協(xié)議的方法,讓擁有了這個(gè)協(xié)議的viewcontroller才會(huì)擁有這個(gè)runtime的小技巧。如果是擴(kuò)展到所有UIViewController及它的子類(lèi)下,就有可能出現(xiàn)一些不可控的崩潰。
最后擴(kuò)展下UImageView給它添加text的屬性。

所有json要映射到UIViewController里面的視圖屬性上來(lái)顯示,一般都是label, textfield , imageView (imageview是沒(méi)有text的,所以要擴(kuò)展一個(gè)) 。所以下面的代碼直接ctrl + c 就可以用了。但是,最好是懂runtime的前提下去使用。這些主要是分享給那些懂runtime,但是不知道怎么在項(xiàng)目里運(yùn)用的人。不懂runtime的要先看懂別人的runtime的文章在來(lái)看這個(gè)。
import UIKit
extension NSObject {
var allKeys : [String] {
var returnArr = [String]()
var outCount : UInt32 = 0
let members = class_copyPropertyList(self.classForCoder, &outCount)
for i in 0..<numericCast(outCount) {
autoreleasepool{
let member = members![i]
let property = property_getName(member)
let propertyName = String.init(cString: property!)
returnArr.append(propertyName)
}
}
return returnArr
}
}
public protocol ModelCompatible : class {
var model : Any? {set get}
func render()
}
fileprivate var modelkey : Void!
public extension ModelCompatible {
public var model : Any? {
get{
return objc_getAssociatedObject(self, &modelkey)
}
set{
objc_setAssociatedObject(self, &modelkey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//每次賦值model后就調(diào)用這個(gè)方法
render()
}
}
func render() {
//因?yàn)橐玫絩untime , 所以要轉(zhuǎn)成OC的NSObject
if let objself = self as? NSObject {
var outCount : UInt32 = 0
//我選擇用property而不是ivar,是因?yàn)閜roperty足夠用了
let members = class_copyPropertyList(objself.classForCoder, &outCount)
for i in 0..<numericCast(outCount) {
autoreleasepool{
let member = members![i]
let property = property_getName(member)
let attribute = property_getAttributes(member)
//通過(guò)runtime獲取到了屬性名
let propertyName = String.init(cString: property!)
//通過(guò)runtime獲取到了類(lèi)型名
let typeName = String(cString: attribute!).components(separatedBy: ",").first as NSString!
let typeStr = typeName!.substring(from: 1)
//將模型轉(zhuǎn)為NSObject類(lèi) ,Dictionary也可以轉(zhuǎn)成NSObject
if let dic = self.model as? NSObject {
if dic.allKeys.contains(propertyName){
if typeStr == "@\"NSString\"" || typeStr == "q" || typeStr == "d" || typeStr == "d"{
//kvc賦值
objself.setValue(dic.value(forKey: propertyName)!, forKey: propertyName)
}else{
//kvc 賦值在一些類(lèi)屬性下的text屬性下 ,
//如果這個(gè)類(lèi)沒(méi)有text,就會(huì)崩潰。
objself.setValue(String(describing: dic.value(forKey: propertyName)!), forKeyPath: propertyName + ".text")
}
}
}
}
}
free(members)
}
}
}
extension UIImageView {
var text: String {
set{
print("把獲取到的url:", newValue , "轉(zhuǎn)成圖片")
/**
比如: self.kf.setImage(with : url)
*/
}
get{
return "沒(méi)有g(shù)et方法"
}
}
}