前言
上一篇文章中我們給出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 <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??纯磿l生什么?
//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++函數的動態性研究還沒測試。有時間,我會給出其他方面的動態性研究。