美文网首页
Swift - 简单Router

Swift - 简单Router

作者: hukun | 来源:发表于2022-12-02 10:33 被阅读0次

    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])
    

    github地址
    https://github.com/michael0217/SwiftRouterDemo

    相关文章

      网友评论

          本文标题:Swift - 简单Router

          本文链接:https://www.haomeiwen.com/subject/ofzyfdtx.html