iOS Swift xib 动态桥接工具
由于文章修改颇多,现已重新写了一篇文章:
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 的用法
首先让大家看一下效果图
LCNibBridgeLCNibBridge 用法
1、创建View类以及同名的xib
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/