JSPatch在Swift中的应用(一)

前言

上一篇文章中我们给出Swift中使用Method Swizzling有几个原则:

  1. 继承自NSObject的Swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性。
  2. 若方法的参数、属性类型为Swift特有、无法映射到Objective-C的类型(如Character、Tuple),则此方法、属性无法添加dynamic修饰(会编译错误)。
  3. 纯Swift类没有动态性,但在方法、属性前添加dynamic修饰可以获得动态性。

现在我们来做一些测试。

一、分析继承于NSObject的类

1.1 属性分析

我们先实现一个测试类,里面包含public和private变量。

class DemoViewController : UIViewController {
    
    var testVariable = "testVariable"
    private var privateTestVariable = "privateTestVariable"
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        
        let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC)))
        dispatch_after(delay, dispatch_get_main_queue()) {
            self.view .addSubview(self.testView)
            UIView.animateWithDuration(4, animations: {
                self.testView.transform = CGAffineTransformMakeTranslation(100, 100);
                }, completion: { (finished:Bool) in
                    print("Origin Function: \(#function) Line: \(#line)");
                    print("Test data output: \(self.testVariable)")
                    print("Test data output: \(self.privateTestVariable)")
            })
        }
    }
}

我们再实现一个Demo的JS脚本。它所实现的功能是运行时重新赋值我们上面定义的2个变量,testVariable和privateTestVariable。

defineClass('DemoProject.DemoViewController', {
    viewDidAppear: function(animated) {
        self.super().viewDidAppear(animated);
        var data = self.testVariable()     
        console.log('testVariable output: ' + data.toJS())

        var privateData = self.privateTestVariable()     
        console.log('privateTestVariable output: ' + privateData.toJS())

        self.setTestVariable('JSPatch')
        self.setPrivateTestVariable('Private JSPatch')

        var data = self.testVariable()     
        console.log('testVariable output: ' + data.toJS())

        var privateData = self.privateTestVariable()     
        console.log('privateTestVariable output: ' + privateData.toJS())

        self.ORIGviewDidAppear(animated);
    }
});

2016-04-14 20:33:58.907 DemoProject[25486:2704401] testVariable output: testVariable

2016-04-14 20:33:58.908 DemoProject[25486:2704401] exception=unrecognized selector privateTestVariable for instance <DemoProject.DemoViewController: 0x7fefb34a1970>

2016-04-14 20:33:58.908 DemoProject[25486:2704401] privateTestVariable output: false

2016-04-14 20:33:58.908 DemoProject[25486:2704401] exception=unrecognized selector setPrivateTestVariable: for instance <DemoProject.DemoViewController: 0x7fefb34a1970>

2016-04-14 20:33:58.908 DemoProject[25486:2704401] testVariable output: JSPatch

2016-04-14 20:33:58.909 DemoProject[25486:2704401] exception=unrecognized selector privateTestVariable for instance 

Origin Function: viewDidAppear Line: 170
Test data output: JSPatch
Test data output: privateTestVariable

从运行结果可以看到,DemoViewController的public变量testVariable成功的替换成了JSPatch,而访问private变量privateTestVariable则抛出了exception,并且其输出值为false,说明没有取到任何值,原因如下:

在JS里面判断是否为空要判断false:

var url = "";
var rawData = NSData.dataWithContentsOfURL(NSURL.URLWithString(url));
if (rawData == null) {} 
//这样判断是错误的应该如下判断:if (!rawData){}在JSPatch.js源码里_formatOCToJS方法对undefined,null,isNil转换成了false。

现在,我们在private变量前面加上dynamic。看看会发生什么?

//private var privateTestVariable = "privateTestVariable"前面加上dynamic
dynamic private var privateTestVariable = "privateTestVariable"
2016-04-14 20:57:49.882 DemoProject[26287:2716081] testVariable output: testVariable
2016-04-14 20:57:49.883 DemoProject[26287:2716081] privateTestVariable output: privateTestVariable
2016-04-14 20:57:49.883 DemoProject[26287:2716081] testVariable output: JSPatch
2016-04-14 20:57:49.883 DemoProject[26287:2716081] privateTestVariable output: Private JSPatch

Origin Function: viewDidAppear Line: 170
Test data output: JSPatch
Test data output: Private JSPatch

我们发现变量内容都实现了替换,所以在继承于NSObject的类中,public变量可以直接修改,而private变量需要加上dynamic。

1.2 自定义函数分析

我们在DemoViewController中加入2个测试函数。

class DemoViewController : UIViewController {
    
    var testVariable = "testVariable"
    dynamic private var privateTestVariable = "privateTestVariable"
    
    override func viewDidAppear(animated: Bool) {
        super.viewDidAppear(animated)
        
        self.testFunction("")
        self.privateTestFunction("")

        print("Origin Function: \(#function) Line: \(#line)");
        print("Test data output: \(self.testVariable)")
        print("Test data output: \(self.privateTestVariable)")
    }
    
    func testFunction(string:String) -> Void {
        print("Origin Function: \(#function) Line: \(#line)");
        print("Test data output: \(self.testVariable)")
    }
    
    private func privateTestFunction(string:String) -> Void {
        print("Origin Function: \(#function) Line: \(#line)");
        print("Test data output: \(self.privateTestVariable)")
    }
}

我们再实现一个替换上面2个函数的JS脚本,在testFunction函数中实现public变量的替换,在privateTestFunction函数中实现private变量的替换

defineClass('DemoProject.DemoViewController', {

    testFunction: function(string) {

        var data = self.testVariable()     
        console.log('testVariable output: ' + data.toJS())

        self.setTestVariable('JSPatch')

        var data = self.testVariable()     
        console.log('testVariable output: ' + data.toJS())

        self.ORIGtestFunction(string);

    },

    privateTestFunction: function(string) {

        var privateData = self.privateTestVariable()     
        console.log('privateTestVariable output: ' + privateData.toJS())

        self.setPrivateTestVariable('Private JSPatch')

        var privateData = self.privateTestVariable()     
        console.log('privateTestVariable output: ' + privateData.toJS())

        self.ORIGprivateTestFunction(string);
    }
});

运行结果如下:

Origin Function: testFunction Line: 183
Test data output: testVariable

Origin Function: privateTestFunction Line: 189
Test data output: privateTestVariable

Origin Function: viewDidAppear Line: 175
Test data output: testVariable
Test data output: privateTestVariable

从运行结果看,什么也没发生,脚本并没有执行,这也符合我们的预期。我们在2个函数前面加上dynamic再运行一次。

dynamic func testFunction(string:String) -> Void {
    print("Origin Function: \(#function) Line: \(#line)");
    print("Test data output: \(self.testVariable)")
}
    
dynamic private func privateTestFunction(string:String) -> Void {
    print("Origin Function: \(#function) Line: \(#line)");
    print("Test data output: \(self.privateTestVariable)")
}

运行结果如下:

2016-04-14 23:20:43.575 DemoProject[29236:2805444] testVariable output: testVariable

2016-04-14 23:20:43.576 DemoProject[29236:2805444] testVariable output: JSPatch

Origin Function: testFunction Line: 183
Test data output: JSPatch

2016-04-14 23:20:43.576 DemoProject[29236:2805444] privateTestVariable output: privateTestVariable

2016-04-14 23:20:43.577 DemoProject[29236:2805444] privateTestVariable output: Private JSPatch

Origin Function: privateTestFunction Line: 189
Test data output: Private JSPatch

Origin Function: viewDidAppear Line: 175
Test data output: JSPatch
Test data output: Private JSPatch

可以看到,所有的函数都实现了运行时替换,这也和我们的预期一致。

1.3 静态函数分析

我们测试一下静态函数的情况。在Swift中,静态函数有2种写法,一种是static func,一种是class func。先看测试代码:

class DemoTest : NSObject{
    
    dynamic static func staticTestFunction(string:String) -> Void {
        print("Origin Function: \(#function) Line: \(#line)");
        print("Static Fucntion output: !!!!!!!!!!")
    }
    
    dynamic class func classTestFunction(string:String) -> Void {
        print("Origin Function: \(#function) Line: \(#line)");
        print("Class Fucntion output: !!!!!!!!!!")
    }
}
defineClass('DemoProject.DemoTest', {


}, {
    staticTestFunction: function(string) {
        console.log('JSPatch staticTestFunction output:!!!!!!!!!!!')
    },
    classTestFunction: function(string) {
        console.log('JSPatch classTestFunction output:!!!!!!!!!!!')
    }
})

运行结果如下:

Origin Function: staticTestFunction Line: 172
Static Fucntion output: !!!!!!!!!!
2016-04-15 17:45:24.786 DemoProject[62603:3255589] JSPatch classTestFunction output:!!!!!!!!!!!

从测试结果可以看出,class写法的静态函数得到了替换。但是static写法的静态函数并没有得到替换。因此,如果想让Swift APP获得动态性,多用class的写法去描述静态函数。

二、分析纯Swift类

由于自定义变量上一章已经分析过了,我们就不再测试变量的动态性,直接测试函数的动态性。因此,直接在private变量前加上dynamic进行函数测试。测试代码如下:

class DemoTest {
    
    var testVariable = "testVariable"
    dynamic private var privateTestVariable = "privateTestVariable"
    
    
    func testCall() {
        self.testFunction("")
        
        self.privateTestFunction("")
    }
    
    func testFunction(string:String) -> Void {
        print("Origin Function: \(#function) Line: \(#line)");
        print("Test data output: \(self.testVariable)")
    }
    
    private func privateTestFunction(string:String) -> Void {
        print("Origin Function: \(#function) Line: \(#line)");
        print("Test data output: \(self.privateTestVariable)")
    }
}```

defineClass('DemoProject.DemoTest', {

testFunction: function(string) {

    var data = self.testVariable()     
    console.log('testVariable output: ' + data.toJS())

    self.setTestVariable('JSPatch')

    var data = self.testVariable()     
    console.log('testVariable output: ' + data.toJS())
},

privateTestFunction: function(string) {

    var privateData = self.privateTestVariable()     
    console.log('privateTestVariable output: ' + privateData.toJS())

    self.setPrivateTestVariable('Private JSPatch')

    var privateData = self.privateTestVariable()     
    console.log('privateTestVariable output: ' + privateData.toJS())
}

})

编译运行之后,直接出现了崩溃,崩溃日志如下:

2016-04-17 14:11:53.283 DemoProject[64822:3373602] NSForwarding: warning: object 0x100e8a918 of class 'DemoProject.DemoTest' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector +[DemoProject.DemoTest copyWithZone:]

查看崩溃的源码之后发现,是因为JSPatch在进行方法替换记录时,使用了NSCopying协议,而不继承NSObject的实例是没有这个方法的,所以产生了崩溃。
我也在github上给JSPatch的作者提交了issue,暂时没有什么解决办法,希望swift 3.0出来之后能够有好的解决办法。[issue地址](https://github.com/bang590/JSPatch/issues/317)

static void _initJPOverideMethods(Class cls) {
if (!_JSOverideMethods) {
_JSOverideMethods = [[NSMutableDictionary alloc] init];
}
if (!_JSOverideMethods[cls]) {
//因为调用了NSCopying协议,所以替换Swift时会崩溃
_JSOverideMethods[(id<NSCopying>)cls] = [[NSMutableDictionary alloc] init];
}
}

#三、通用性测试

我们写一段稍微复杂的程序,试试JSPatch对Swift的支持。里面主要涉及block的使用,view的简单动画。运行结果我就不贴出来了,运行情况是可以达到原生的动画效果。JSPatch对Struct的支持请参照这里:[JSPatch对Struct的支持](https://github.com/bang590/JSPatch/wiki/%E6%B7%BB%E5%8A%A0-struct-%E7%B1%BB%E5%9E%8B%E6%94%AF%E6%8C%81)

override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)

let delay = dispatch_time(DISPATCH_TIME_NOW, Int64(3 * Double(NSEC_PER_SEC)))
dispatch_after(delay, dispatch_get_main_queue()) {
    self.view .addSubview(self.testView)
    UIView.animateWithDuration(4, animations: {
        self.testView.center = CGPointMake(100, 100)
        }, completion: { (finished:Bool) in
    })
}

}


require('UIView')

defineClass('DemoProject.DemoTest', {
viewDidAppear: function(animated) {
self.super().viewDidAppear(animated);

    self.view().addSubview(self.testView())
    console.log('!!!!!!!!!!!!!!!!!!!!')

    dispatch_after(1.0, function(){
        UIView.animateWithDuration_animations_completion(4, block(function(){
             self.testView().setCenter({x: 100, y: 400})
        }), block("Bool", function(finished){

        }))
    })
},

})


#四、总结
这篇文章主要针对Swift中Class的动态性研究,包括继承于NSObject的类和原生的Swift的类,而关于Protocol和C++函数的动态性研究还没测试。有时间,我会给出其他方面的动态性研究。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容