前言
网上关于runtime的教程都有很多,但时很多大部分都是讲解原理,并没有实际运用runtime写一些东西,所以很难让人理解。我觉得要是有实际的运用的话,应该能够更深刻的理解。
先看一个效果
图-1 是我写的代码,我们假装json时从网络上取来的数据 , 然后把json的值赋给ViewController的model , model 是通过ModelCompatible这个协议得来的 ,如图-2
ModelCompatible有一个属性和一个方法,但凡有继承来这个属性的类(ps : 只有类,虽然swift中的enum struct也能继承协议。但是我们这个协议的后面是继承了一个class的关键字,表示这个协议就只能被类继承了)
这个json里面的key值都和ViewController的属性相同,当把json赋值给model的时候,就会相应地改变ViewController的里面的属性的值,
这里打印了它的id,id也自动也被自动赋了值
这样写的好处是,能大大减少我们的代码,而且使得代码的可读性更高。
思路
先通过runtime遍历一个的类的所有属性(不包括父类),然后和model里面的key比较,如果这个属性和key一样,在比较这个属性的类型,如果是String , Int , float 之类的,就把它们的json的key对应的值直接赋值给ViewController的属性的值。使用下面这个方法赋值。
setValue(value: Any?, forKey: String)
如果不是上面那个类型,那么就赋值给它的text的路径的属性下(因为所有的label , textField 都有text这个属性),使用下面的代码
setValue(value: Any? , forKeyPath: String)
如果这个属性没有text的属性会怎么办,会崩溃。所以,前面使用了协议的方法,让拥有了这个协议的viewcontroller才会拥有这个runtime的小技巧。如果是扩展到所有UIViewController及它的子类下,就有可能出现一些不可控的崩溃。
最后扩展下UImageView给它添加text的属性。
所有json要映射到UIViewController里面的视图属性上来显示,一般都是label, textfield , imageView (imageview是没有text的,所以要扩展一个) 。所以下面的代码直接ctrl + c 就可以用了。但是,最好是懂runtime的前提下去使用。这些主要是分享给那些懂runtime,但是不知道怎么在项目里运用的人。不懂runtime的要先看懂别人的runtime的文章在来看这个。
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后就调用这个方法
render()
}
}
func render() {
//因为要用到runtime , 所以要转成OC的NSObject
if let objself = self as? NSObject {
var outCount : UInt32 = 0
//我选择用property而不是ivar,是因为property足够用了
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)
//通过runtime获取到了属性名
let propertyName = String.init(cString: property!)
//通过runtime获取到了类型名
let typeName = String(cString: attribute!).components(separatedBy: ",").first as NSString!
let typeStr = typeName!.substring(from: 1)
//将模型转为NSObject类 ,Dictionary也可以转成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 赋值在一些类属性下的text属性下 ,
//如果这个类没有text,就会崩溃。
objself.setValue(String(describing: dic.value(forKey: propertyName)!), forKeyPath: propertyName + ".text")
}
}
}
}
}
free(members)
}
}
}
extension UIImageView {
var text: String {
set{
print("把获取到的url:", newValue , "转成图片")
/**
比如: self.kf.setImage(with : url)
*/
}
get{
return "没有get方法"
}
}
}