iOS开发程序员iOS Developer

iOS Swift xib 动态桥接工具

2016-05-27  本文已影响470人  WWest

由于文章修改颇多,现已重新写了一篇文章:

http://www.jianshu.com/p/d39f19d60544

前言:开发一直用@sunnyxx 的 OC 版的动态桥接库XXNibBridge,然而最近在学习 Swift , 很多非常好用的开源库都没有 Swift 版,例如 XXNibBridge ,虽说可以用 OC 混编,但那不是我的风格。研究了一下 XXNibBridge 的代码,以及 @sunnyxx 提供的思路,自己尝试着给转成 Swift 版的。

遇到的几个问题

#### 1、替换系统的方法

1、重写系统的方法

xib桥接所用到的最重要的一个方法,

func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject?

这个方法在加载 xib 的时候调用,给了我们偷天换日的机会

 func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject?{
   // 1. 判断是否要进行替换
  // 2. 根据self.class从xib创建真正的view
  // 3. 将placeholder的属性、autolayout等替换到真正的view上
  // 4. return 真正的view
}

>######OC 由于 load 方法是在编译的时候就已经调用的,所以通过runtime 在load方法里执行替换方法,但是在Swift行不通,为此我在网上找了一个资料。发现用 extension 来处理比较好 。

对 UIView 进行拓展 ,重写 initialize() 方法进行系统方法替换。

extension UIView{
public override class func initialize(){
~~struct Static { ~~
static var token: dispatch_once_t = 0
}
dispatch_once(&Static.token) {
//做方法替换
//篇幅问题就不用在这里贴那么多代码了
//所有的问题在我给的库里都能找到
}
}

2、加载xib时的递归问题

从 storyboard 中创建 view 的时候会调用 (问题1)中所说道的方法,而且从 xib 创建真正的 view 时也会调用这个方法,所以会造成递归。

@sunnyxx 给出的解决问题的思路是采用设置标志位的方式来避免递归调用

- (id)awakeAfterUsingCoder:(NSCoder *)aDecoder {
    self = [super awakeAfterUsingCoder:aDecoder];
    if (这个类的Loading标志位 -> NO)
    {
          Loading标志位 -> YES
          从xib加载真实的View (这里会递归调用这个函数)
          return 真实View
    }
    Loading标志位 -> NO
    return self
}

考虑了一下我也以这种思路来做。

public override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
    
    if class_conformsToProtocol(self.classForCoder, LCNibBridge.self) {
        let version = class_getVersion(self.classForCoder)
        
        if version == 0 {
            //标记该类正在桥接
            
            print(self.classForCoder)
            class_setVersion(self.classForCoder, 1)
            return self.instantiateRealView(self)
        }
        //标记该类已经桥接完毕
        class_setVersion(self.classForCoder, 0)
        return self

    }
    return self
}

试验了很多种方法,最后确定以动态的向类中添加方法来作为类的标志位,来区分是从 storyboard 中加载还是从 xib 加载

1、首先从类中获取获取对应的方法

let isLoading = class_getInstanceMethod(self.classForCoder,NSSelectorFromString("LC_NibBridgeIsLoading"))

2、判断 如果为空 isLoading == nil 就说明说明从 storyboard 中来,这时切换到从 xib 加载 ,然后添加上该方法。

class_addMethod(self.classForCoder, NSSelectorFromString("LC_NibBridgeIsLoading"), nil, nil)

3、这样等从 xib 加载调用这个方法的时候, isLoading 不为 nil 那么就返回 self 加载自身。

天真如我以为这样就成功了,后来发现自己还是 too young.

这样做只能用一次,如果 storyboard 有两个地方都用到了同一个 xib 那么就只有第一次加载的时候有用,由于第一次加载后添加了 LC_NibBridgeIsLoading 方法,那后面再加载的话这份代码就不起作用了。

对于这个问题我采用了笨方法。

给类添加一对标志,一个标明正在加载,另一个标明已经加载,每次加载都添加一对标志,然后通过 for 循环来判断当前这次加载的状态,然后做相应的处理

开源库 LCNibBridge

demo以及源码已经放到 我的github上面的LCNibBridge项目

下面简单说一下 LCNibBridge 的用法

首先让大家看一下效果图

LCNibBridge

LCNibBridge 用法

1、创建View类以及同名的xib

LCMokeView
2、遵守 LCNibBridge 协议, 并实现协议方法。
class LCMokeView: UIView,LCNibBridge{
}

class LCMokeView: UIView,LCNibBridge{
func LC_NibBridgeUsefull() -> Bool {
return true
}
}

这里要说明一下注意事项,本来源码是想要检查该类是否遵从协议,但是用到 NSProtocolFromString() 这个方法老是出错,搞了好久,最后取了折中的办法,检测该类是否实现了协议中的方法。所以造成了一定缺陷,这个问题以后会再进行研究。
注意:如果该类不想用xib动态桥接了,请把遵守的协议以及已经实现的协议方法一并清除,施主切记!!!

3、 storyboard 中设置成同名的Class

storyboard

至此所有工作全部做完,是不是很简单,是不是想尽快感受一下

PS: LCNibBridge 现已加入 cocoa pods 豪华午餐~

pod 'LCNibBridge', '~> 1.2.3'

最后重述一下LCNibBridge github 地址 :

https://github.com/liutongchao/LCNibBridge

以及对这篇文章贡献最大的一篇博客 @sunnyxx

http://blog.sunnyxx.com/2014/07/01/ios_ib_bridge/

以我的水平 LCNibBridge 还有很多问题需要优化, 如果你有好的建议欢迎来与我互相探讨 -- LC.West

上一篇下一篇

猜你喜欢

热点阅读