router工具,屬于項(xiàng)目的基礎(chǔ)工具類之一煞茫,也查閱了一些第三方庫(kù)帕涌,不過(guò)都不是特別滿意,我就嘗試著自己簡(jiǎn)單封裝了一個(gè)续徽。
先說(shuō)下思路蚓曼,因與后臺(tái)、安卓钦扭、前端人員溝通纫版,依舊采用key, value的形式來(lái)處理跳轉(zhuǎn),比如客情,后臺(tái)給個(gè)
let dic = ["target":"setting","para":["testKey":"123"]]
這種結(jié)果的json其弊,這里的target,就是約定的key膀斋,用來(lái)指定某一頁(yè)面梭伐,后面的para代表傳遞的參數(shù),如果想OC那樣仰担,用plist表來(lái)記錄與維護(hù)的話糊识,雖然也能實(shí)現(xiàn),但是在自己想用這種方式的跳轉(zhuǎn)的時(shí)候惰匙,就會(huì)因?yàn)檫@里的target沒(méi)提示技掏,而會(huì)有寫錯(cuò)的可能性,所以這里采用枚舉的方式實(shí)現(xiàn)项鬼。如下:
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()
}
}
}
這樣哑梳,在使用的時(shí)候,直接提示出來(lái)绘盟,而不會(huì)因?yàn)槿亲址磳戝e(cuò)誤鸠真。
let target = RouterType.setting.getController()
然后就是賦值,開(kāi)始時(shí)龄毡,會(huì)很自然的想到kvc來(lái)賦值
let vcKeys = RouterUtil.getAllPropertys(vc)
para.allKeys().forEach { key in
if vcKeys.contains(key) {
// 有這個(gè)值
let value = para[key]
// MARK: 該方法需要vc實(shí)現(xiàn) setValue:forUndefinedKey: 方法
vc.setValue(value, forKeyPath: key)
}
}
這里的allKeys與getAllPropertys是我封裝的小工具吠卷,allkeys,就是字典的所有key沦零,而getAllPropertys是利用反射Mirror祭隔,來(lái)獲取對(duì)應(yīng)vc的屬性,在一一賦值路操。
可是這里需要注意:使用kvc 需要目標(biāo)vc實(shí)現(xiàn)
setValue:forUndefinedKey:
方法疾渴,且被賦值的屬性前千贯,需要加上@objc的標(biāo)識(shí)
@objc var testKey:String?
而且類型要一致,才能賦值成功搞坝,雖然可以使用基類來(lái)實(shí)現(xiàn)setValue:forUndefinedKey: 但是搔谴,每個(gè)屬性前都需要加上@objc就會(huì)很麻煩,而且如果哪天后臺(tái)傳來(lái)的參數(shù)中桩撮,類型不對(duì)了敦第,也會(huì)導(dǎo)致賦值失敗,這是我們不想看到的店量。這時(shí)芜果,我就想起來(lái)了HandyJSON框架,它就可以不用加@objc垫桂,然后正確的給屬性賦值师幕,雖然已經(jīng)不維護(hù)了,但是我還是決定去看看它的實(shí)現(xiàn)原理诬滩,然后看看能不能借鑒霹粥。
在查閱了相關(guān)資料后,我們得知疼鸟,HandyJSON其實(shí)是通過(guò)操作內(nèi)存的方式來(lái)完成賦值的后控。
在HandyJSON中,不管是class空镜,還是struct浩淘,在賦值時(shí),都會(huì)進(jìn)入下面這個(gè)方法
static func _transform(dict: [String: Any], to instance: inout Self)
在這個(gè)方法中會(huì)有兩個(gè)結(jié)構(gòu)體需要提前看一下:
struct Property {
let key: String
let value: Any
/// An instance property description
struct Description {
/// key名稱
public let key: String
/// 類型
public let type: Any.Type
/// 屬性所需內(nèi)存大小
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
// 內(nèi)存地址
let address: UnsafeMutableRawPointer
let bridged: Bool
}
下面仔細(xì)看該方法:
static func _transform(dict: [String: Any], to instance: inout Self) {
/// 獲取類型的屬性列表 包含 每個(gè)屬性的內(nèi)存大小
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 對(duì)象的Head節(jié)點(diǎn)
let rawPointer = instance.headPointer()
InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer))
// process dictionary 對(duì)dict進(jìn)行格式化
let _dict = convertKeyIfNeeded(dict: dict)
// 判斷是否是OC對(duì)象
let instanceIsNsObject = instance.isNSObjectType()
// 橋接屬性
let bridgedPropertyList = instance.getBridgedPropertyList()
// 循環(huán)properties數(shù)組
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
}
/// 屬性信息結(jié)構(gòu)體
let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty)
InternalLogger.logVerbose("field: ", property.key, " offset: ", property.offset, " isBridgeProperty: ", isBridgedProperty)
/// 獲取對(duì)應(yīng)屬性值
if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) {
/// 轉(zhuǎn)換成需要的類型
if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) {
/// 進(jìn)行賦值
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 用來(lái)實(shí)現(xiàn)類屬性解析的功能张抄。
看到這里,基本上就找到了 我們處理后臺(tái)路由傳值的方案了洼怔,所以我們項(xiàng)目的RouterUtil基礎(chǔ)版就誕生了署惯。
使用枚舉,通過(guò)string來(lái)得到對(duì)應(yīng)的VC镣隶,然后在通過(guò)這種方式進(jìn)行賦值极谊。賦值過(guò)程如下:
static func transform(dict: [String: Any], to instance: UIViewController) {
let type = type(of: instance)
/// 獲取類型所有的屬性
guard let properties = getProperties(forType: type) else {
return
}
/// 獲取對(duì)象內(nèi)存中的head節(jié)點(diǎn)
let rawPointer = instance.headPointerOfClass()
properties.forEach { property in
/// 屬性在內(nèi)存中的地址
let propAddr = rawPointer.advanced(by: property.offset)
let detail = PropertyInfo(key: property.key, type: property.type, address: propAddr)
/// 從字典中獲取對(duì)應(yīng)值
if let rawValue = dict[property.key] {
/// 把值轉(zhuǎn)成需要的類型,比如 需要string安岂,后臺(tái)給了Int 直接賦值會(huì)失敗轻猖,需要轉(zhuǎn)成string
if let convertedValue = convertValue(rawValue: rawValue, property: detail) {
/// 寫入對(duì)應(yīng)內(nèi)存地址內(nèi)
extensions(of: property.type).write(convertedValue, to: detail.address)
}
}
}
}
轉(zhuǎn)屬性的方法,我借鑒了HandyJSON庫(kù)里面的方式域那,并進(jìn)行了改造
fileprivate func convertValue(rawValue: Any, property: PropertyInfo) -> Any? {
/// 需要轉(zhuǎn)化時(shí)
if let transformType = property.type as? BuiltInBasicTypeProtocol.Type {
/// 轉(zhuǎn)換結(jié)果咙边, 比如需要Int,但是后臺(tái)給了string
return transformType.transform(from: rawValue)
} else {
return extensions(of: property.type).takeValue(from: rawValue)
}
}
因?yàn)镠andyJSON需要類遵守HandyJSON協(xié)議,而败许,我們這里只需要被賦值的屬性遵守BuiltInBasicTypeProtocol協(xié)議就可以了友瘤。在BuiltInBasicTypeProtocol協(xié)議中,對(duì)基礎(chǔ)屬性進(jìn)行了擴(kuò)展檐束。這里就不詳細(xì)列出了,感興趣可以查看源碼束倍。
使用起來(lái)也比較簡(jiǎn)單
RouterUtil.pushVC(target: "setting", params: ["testKey":123123])