router工具,属于项目的基础工具类之一,也查阅了一些第三方库,不过都不是特别满意,我就尝试着自己简单封装了一个。
先说下思路,因与后台、安卓、前端人员沟通,依旧采用key, value的形式来处理跳转,比如,后台给个
let dic = ["target":"setting","para":["testKey":"123"]]
这种结果的json,这里的target,就是约定的key,用来指定某一页面,后面的para代表传递的参数,如果想OC那样,用plist表来记录与维护的话,虽然也能实现,但是在自己想用这种方式的跳转的时候,就会因为这里的target没提示,而会有写错的可能性,所以这里采用枚举的方式实现。如下:
enum RouterType: String {
typealias RawValue = String
case setting
case accountSafe
case webView
func getController() -> BaseViewController {
switch self {
case .setting:
return UserSettingViewController.init()
case .accountSafe:
return AccountSafeVC.init()
default:
return BaseViewController.init()
}
}
}
这样,在使用的时候,直接提示出来,而不会因为全是字符串而拼写错误。
let target = RouterType.setting.getController()
然后就是赋值,开始时,会很自然的想到kvc来赋值
let vcKeys = RouterUtil.getAllPropertys(vc)
para.allKeys().forEach { key in
if vcKeys.contains(key) {
// 有这个值
let value = para[key]
// MARK: 该方法需要vc实现 setValue:forUndefinedKey: 方法
vc.setValue(value, forKeyPath: key)
}
}
这里的allKeys与getAllPropertys是我封装的小工具,allkeys,就是字典的所有key,而getAllPropertys是利用反射Mirror,来获取对应vc的属性,在一一赋值。
可是这里需要注意:使用kvc 需要目标vc实现
setValue:forUndefinedKey:
方法,且被赋值的属性前,需要加上@objc的标识
@objc var testKey:String?
而且类型要一致,才能赋值成功,虽然可以使用基类来实现setValue:forUndefinedKey: 但是,每个属性前都需要加上@objc就会很麻烦,而且如果哪天后台传来的参数中,类型不对了,也会导致赋值失败,这是我们不想看到的。这时,我就想起来了HandyJSON框架,它就可以不用加@objc,然后正确的给属性赋值,虽然已经不维护了,但是我还是决定去看看它的实现原理,然后看看能不能借鉴。
在查阅了相关资料后,我们得知,HandyJSON其实是通过操作内存的方式来完成赋值的。
在HandyJSON中,不管是class,还是struct,在赋值时,都会进入下面这个方法
static func _transform(dict: [String: Any], to instance: inout Self)
在这个方法中会有两个结构体需要提前看一下:
struct Property {
let key: String
let value: Any
/// An instance property description
struct Description {
/// key名称
public let key: String
/// 类型
public let type: Any.Type
/// 属性所需内存大小
public let offset: Int
public func write(_ value: Any, to storage: UnsafeMutableRawPointer) {
return extensions(of: type).write(value, to: storage.advanced(by: offset))
}
}
}
详情
struct PropertyInfo {
let key: String
let type: Any.Type
// 内存地址
let address: UnsafeMutableRawPointer
let bridged: Bool
}
下面仔细看该方法:
static func _transform(dict: [String: Any], to instance: inout Self) {
/// 获取类型的属性列表 包含 每个属性的内存大小
guard let properties = getProperties(forType: Self.self) else {
InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))")
return
}
// do user-specified mapping first
let mapper = HelpingMapper()
instance.mapping(mapper: mapper)
// get head addr 对象的Head节点
let rawPointer = instance.headPointer()
InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer))
// process dictionary 对dict进行格式化
let _dict = convertKeyIfNeeded(dict: dict)
// 判断是否是OC对象
let instanceIsNsObject = instance.isNSObjectType()
// 桥接属性
let bridgedPropertyList = instance.getBridgedPropertyList()
// 循环properties数组
for property in properties {
let isBridgedProperty = instanceIsNsObject && bridgedPropertyList.contains(property.key)
// 属性的地址
let propAddr = rawPointer.advanced(by: property.offset)
InternalLogger.logVerbose(property.key, "address at: ", Int(bitPattern: propAddr))
if mapper.propertyExcluded(key: Int(bitPattern: propAddr)) {
InternalLogger.logDebug("Exclude property: \(property.key)")
continue
}
/// 属性信息结构体
let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty)
InternalLogger.logVerbose("field: ", property.key, " offset: ", property.offset, " isBridgeProperty: ", isBridgedProperty)
/// 获取对应属性值
if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) {
/// 转换成需要的类型
if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) {
/// 进行赋值
assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail)
continue
}
}
InternalLogger.logDebug("Property: \(property.key) hasn't been written in")
}
}
这里在HandyJSON中,引用了Reflection https://github.com/Zewo/Reflection 用来实现类属性解析的功能。
看到这里,基本上就找到了 我们处理后台路由传值的方案了,所以我们项目的RouterUtil基础版就诞生了。
使用枚举,通过string来得到对应的VC,然后在通过这种方式进行赋值。赋值过程如下:
static func transform(dict: [String: Any], to instance: UIViewController) {
let type = type(of: instance)
/// 获取类型所有的属性
guard let properties = getProperties(forType: type) else {
return
}
/// 获取对象内存中的head节点
let rawPointer = instance.headPointerOfClass()
properties.forEach { property in
/// 属性在内存中的地址
let propAddr = rawPointer.advanced(by: property.offset)
let detail = PropertyInfo(key: property.key, type: property.type, address: propAddr)
/// 从字典中获取对应值
if let rawValue = dict[property.key] {
/// 把值转成需要的类型,比如 需要string,后台给了Int 直接赋值会失败,需要转成string
if let convertedValue = convertValue(rawValue: rawValue, property: detail) {
/// 写入对应内存地址内
extensions(of: property.type).write(convertedValue, to: detail.address)
}
}
}
}
转属性的方法,我借鉴了HandyJSON库里面的方式,并进行了改造
fileprivate func convertValue(rawValue: Any, property: PropertyInfo) -> Any? {
/// 需要转化时
if let transformType = property.type as? BuiltInBasicTypeProtocol.Type {
/// 转换结果, 比如需要Int,但是后台给了string
return transformType.transform(from: rawValue)
} else {
return extensions(of: property.type).takeValue(from: rawValue)
}
}
因为HandyJSON需要类遵守HandyJSON协议,而,我们这里只需要被赋值的属性遵守BuiltInBasicTypeProtocol协议就可以了。在BuiltInBasicTypeProtocol协议中,对基础属性进行了扩展。这里就不详细列出了,感兴趣可以查看源码。
使用起来也比较简单
RouterUtil.pushVC(target: "setting", params: ["testKey":123123])
网友评论