我們通過什么途徑從Objective-C編程語言過渡到Swift語言呢?
1.從簡單的部分入手
我們決定從盡可能簡單的部分入手,例如一些獨(dú)立的可以自測和內(nèi)部調(diào)用的類。我們最初選擇的部分是簡單的UI控制代碼、實(shí)用函數(shù)以及已有類的擴(kuò)展方法。
例如在最初加入的Swift組件中,我們寫了一個(gè)String的擴(kuò)展方法來使本地化字符串讀起來更舒服:
extension String {
var localized: String! {
let localizedString = NSLocalizedString(self, comment: "")
return localizedString
}
}
有趣的是我們本可以用Objective-C的分類來實(shí)現(xiàn)相同功能。然而我們團(tuán)隊(duì)再也不會(huì)去使用陳舊的NSLocalizedString(@"MyText", @"")。許多新的思路隨著運(yùn)用一門新語言而浮出水面。所以從使用Swift開發(fā)的第一天開始我們的Swift字符串都是寫成"MyText".localized格式。
2.從Swift中調(diào)用已有的Objective-C代碼
在寫了一些獨(dú)立的Swift組件并對(duì)它們進(jìn)行單元測試后,我們開始用Swift調(diào)用已有的Objective-C的類。一切都開始變得實(shí)際起來。
要從Swift調(diào)用Objective-C的類你需要定義Swift橋接頭文件。這是一個(gè).h文件,用來定義所有能暴露給Swift調(diào)用的Objective-C頭文件。在文件的開頭,需要修改編譯設(shè)置來讓編譯器在編譯的時(shí)候把它加進(jìn)去。當(dāng)這一切完成后,這些Objective-C的類就被導(dǎo)入進(jìn)Swift的世界,能被Swift很方便地調(diào)用。
當(dāng)你在Swift中調(diào)用Objective-C類時(shí),你可能會(huì)收到一個(gè)警告:pointer is missing a nullability type specifier。當(dāng)Objective-C代碼被導(dǎo)入到Swift,編譯器會(huì)檢查nullability compatibility--如果沒有任何關(guān)于nullability的信息,這個(gè)提示就會(huì)出現(xiàn)。要做這個(gè)檢查是因?yàn)樵?/span>Swift中nullability信息都是顯式申明的,不論是非空類型還是可選類型。
我們唯一需要做的僅僅是在被調(diào)用的Objective C頭文件中加入nullability信息來消除這些編譯器警告。我們使用新的nullable和nonnull標(biāo)注來達(dá)成這一目的。重置工作僅僅只需要花幾個(gè)小時(shí),因?yàn)槲覀冎恍枰薷奈覀冊(cè)?/span>Swift中用到的類,無非是幾百行的代碼。然而,做這些修改時(shí)我們絞盡腦汁思考在已有代碼庫中哪些可以或不能被設(shè)置成nil,但當(dāng)我們把這些代碼暴露給Swift時(shí)我們無法避免的要做出一個(gè)明確的抉擇。
在大部分情況下,重構(gòu)涉及到一些像這樣的修改:
// 在.h文件中原來的方法簽名
@property (nonatomic, strong, readonly) THSession *session;
// 新的,Swift友好型的方法簽名
@property (nonatomic, strong, readonly, nullable) THSession *session;
在方法簽名中存在block時(shí),修改會(huì)略微復(fù)雜,但是沒有不可掌控的:
// 在.h文件中原來的方法簽名
- (NSURLSessionDataTask *)updateWithSuccess: (void(^)())success error:( void(^)(NSError * error))error;
// 新的,Swift友好型的方法簽名
- (nonnull NSURLSessionDataTask *)updateWithSuccess: (nullable void(^)())success error:(nullable void(^)(nonnull NSError *error))error;
3.在Objective C調(diào)用Swift代碼
在有不少復(fù)雜度適中的使用了Objective-C類的Swift組件后,是時(shí)候在Objective C里面調(diào)用這些組件了。從Objective-C中調(diào)用Swift組件更直接,因?yàn)椴恍枰~外的橋接頭文件。
唯一需要對(duì)現(xiàn)有Swift文件做修改的是繼承NSObject或給我們希望暴露的Swift類添加@objc屬性。有一些特殊的Swift類Objective-C無法使用,像結(jié)構(gòu)體(structures),元組(tuples),和泛型(generics),以及一些其他的類。這些限制不會(huì)造成什么影響,因?yàn)槲覀儾幌氚讶魏涡碌慕Y(jié)構(gòu)暴露給Objective-C。唯一我們需要做些額外處理的特例是枚舉類型。要使用Swift中定義的枚舉類型,需要在Swift中明確申明Int值類型:
@objc enum FlightCabinClass: Int {
case Economy = 1, PremiumEconomy, Business, First, PrivateJet
}
4.從Swift角度重拾單元測試和依賴注入
當(dāng)我們有更多復(fù)雜的模塊使用了依賴時(shí),我們遇到了一個(gè)沒有明確解決方案的問題:單元測試。不像Objective-C,Swift不支持讀寫反射。簡單來說,Swift中沒有與OCMock等價(jià)的庫,事實(shí)上mocking框架并不存在。
這里有一個(gè)令我們抓狂的例子。我們希望測試當(dāng)我們?cè)谝粋€(gè)頁面點(diǎn)擊提交按鈕時(shí),saveTrip方法能被view對(duì)象的viewModel屬性調(diào)用。在Objective-C中,使用OCMock,測試起來非常輕松:
// 測試當(dāng)點(diǎn)擊了提交按鈕,ViewModel上的一個(gè)方法被調(diào)用
- (void)test_whenPressingSubmitButton_thenInvokesSaveTripOnViewModel {
// given
TripViewController *view = [TripViewController alloc] init];
id viewModelMock = OCMPartialMock(view.viewModel);
OCMExpect([viewModelMock saveTrip]);
// when
[view.submitButton press];
// then
OCMVerifyAll(viewModelMock);
}
在Swift中這個(gè)方法不起作用。Objective-C單元測試常依賴于像OCMock這樣完善的模擬框架。依賴注入是一個(gè)很好的實(shí)踐,但因?yàn)?span>OCMock讓單元測試變得非常簡單,甚至不需要顯式的依賴注入,我們大部分的Objective-C依賴是隱式的。而在Swift中,像OCMock這樣的動(dòng)態(tài)模擬庫并不存在。在Swift中唯一可以寫出能測試代碼的方式是讓依賴變得顯式和可注入。一旦這個(gè)解決后,你需要自己寫模擬對(duì)象去驗(yàn)證行為。
重新審視前一個(gè)例子:在Swift中需要做修改,讓viewModel可以作為view的依賴被傳遞。只要viewModel實(shí)現(xiàn)了協(xié)議,或者繼承viewModel就能實(shí)現(xiàn)。測試類需要定義用來傳遞的模擬對(duì)象:
func test_whenPressingSubmitButton_thenInvokesSaveTripOnViewModel() {
// given
let viewModelMock = TripViewModelMock()
let view = TripViewController(viewModelMock)
// when
view.submitButton.press()
// then
XCTAssertEqual(viewModelMock.saveTripInvoked)
}
class TripViewModelMock: TripViewModel {
var saveTripInvoked = false
override func saveTrip() {
self.saveTripInvoked = true
}
}
Swift測試代碼看起來要比Objective C版本更加冗長。事實(shí)上,顯示依賴注入模式會(huì)讓我們更關(guān)注盡可能減少代碼的耦合度。在遷移到Swift之前,我們認(rèn)為我們的Objective -C代碼耦合度很低了。寫了幾周Swift代碼后,'老'代碼和'新'代碼之間的差異越來越凸顯。遷移到Swift并正確地測試代碼讓我們的代碼庫耦合度更低。
漸入佳境
在找到依賴注入的竅門并用這個(gè)方法來寫我們自己的模擬對(duì)象后,我們?cè)?span>Swift中挖掘得更深入了,開始挑選一些別人未嘗涉入的技術(shù)。在前一個(gè)例子我展示了如何重建Objective C的OCMPartialMock功能。一個(gè)更清晰的方法是用pure mocks而非partial mocks。在Swift中寫靈活的,低耦合代碼的更好方法是使用協(xié)議,以及面向協(xié)議編程技術(shù)。我們很快選擇了這個(gè)方向,代碼變得耦合度更低且更易測試。
新語言就有新的語言特性,像guard和defer和泛型generics、用do-catch、嵌套類型(nested types)、where clause和@testable關(guān)鍵字進(jìn)行的錯(cuò)誤處理(error handling),這些僅僅是冰山一角。即便如此Swift依舊很容易上手,有很多內(nèi)容值得深入研究。
除了學(xué)習(xí)一門新語言外,我們還從遷移到Swift學(xué)到了什么呢?
更易讀的代碼:
// Objective C
[self addConstraint:[NSLayoutConstraint constraintWithItem: myButton
attribute: NSLayoutAttributeCenterX
relatedBy: NSLayoutRelationEqual
toItem: myView
multiplier: 1.0
constant: 0.0]];
// 用Swift寫的同樣的代碼
self.addConstraint(NSLayoutConstraint(item: myButton,
attribute: .CenterX,
relatedBy: .Equal,
toItem: myView,
attribute: .CenterX,
multiplier: 1.0,
constant: 0.0))
與Objective-C相比,更苛刻的編譯時(shí)檢查。除了類型安全和編譯時(shí)間因此獲益外,Swift編譯器還對(duì)一些像if表達(dá)式不允許只有一行(not allowing single line if statements )或強(qiáng)制switch表達(dá)式把情況列舉全面(enforcing exhaustive switch statements)等條件做了額外檢查。
擺脫頭文件。以及不再需要在.h和.m文件之間切換來復(fù)制方法申明。
當(dāng)然還有學(xué)習(xí)和使用新語言特性帶來的恐懼
相對(duì)優(yōu)勢,劣勢的地方可以說是出人意料的少。一個(gè)明顯的不便是我們的一些第三方依賴--像JSONModel--是建立在Objective-C動(dòng)態(tài)特性上的,而這在Swift上并不管用。另一個(gè)問題是我們需要維護(hù)現(xiàn)存的Objective-C代碼,這意味著額外的環(huán)境轉(zhuǎn)換和動(dòng)機(jī)去不斷把更多的Objective-C代碼轉(zhuǎn)換成Swift代碼。
當(dāng)然Swift仍是一門新語言,代碼的基礎(chǔ)上,從Objective-C過渡到Swift沒有絲毫降低我們的進(jìn)度。
原文來自:cocoachina