前言
上一篇文章中我们给出Swift中使用Method Swizzling有几个原则:
- 继承自NSObject的Swift类,其继承自父类的方法具有动态性,其他自定义方法、属性需要加dynamic修饰才可以获得动态性。
- 若方法的参数、属性类型为Swift特有、无法映射到Objective-C的类型(如Character、Tuple),则此方法、属性无法添加dynamic修饰(会编译错误)。
- 纯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
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
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
}
}
#三、通用性测试
我们写一段稍微复杂的程序,试试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++函数的动态性研究还没测试。有时间,我会给出其他方面的动态性研究。