讓Cocoa框架支援Swift語言的工作,給了我們一個全新的機會來審視裡面的眾多API。我們發現大多數不適合Swift編碼風格的類,基本都是優先考慮安全性的。比如,一些關係到動態方法呼叫的類在Swift中沒有暴露出來,它們NSInvocation
和NSMethodSignature
這兩個類。
我們最近收到一個bug報告,是從一個注意到這個現象的開發者發來的。他曾經使用Objective-C中的NSMethodSignature
來在執行時檢查方法引數的型別,但在遷移到Swift的過程中發現NSMethodSignature
方法失效了。實際分析一下,被遷移的程式碼能夠接收不同簽名的HTTP處理程式,比如:
1 2 |
func handleRequest(request: HTTPRequest, queryStringArguments: [String: String]) { } func handleRequest(request: HTTPRequest, jsonBody: JSON) { } |
在Objective-C裡,NSMethodSignature
能被用來檢測API引數型別,第一個方法的API必須接收一個[String: String]
型別的引數,第二個方法需要接收一個JSON
型別。然而,Swift足夠強大,能夠簡單的處理這種狀況而無需動用NSMethodSignature
,並且在某種程度上,還能降低對編譯器提供的型別提示和記憶體安全的破壞。
下面是在Swift用另一種方法來解決這個問題的程式碼:
1 2 3 4 5 6 7 8 9 10 |
struct HTTPRequest { // ... } protocol HTTPHandlerType { typealias Data /// :returns: true if the request was handled; false otherwise func handle(request: HTTPRequest, data: Data) -> Bool } |
首先,我們使用協議來定義一個介面,任何想要處理我們的HTTPRequest
的程式都必須通過這個藉口。這個協議非常簡單,裡面只包含一種方法。
這裡為什麼使用協議而不是HTTPHandler
的子類呢?因為協議更靈活,能夠將實現的細節交給客戶端去做。如果使用HTTPHandler
子類,我們需要讓客戶端同樣使用它,並且強制客戶端使用相同的引用型別。然而使用協議的話,客戶端能夠自行決定在程式碼中使用適合的型別,無論它們是類、結構體甚至是列舉型別。
1 2 3 4 5 6 7 8 9 10 11 12 |
class HTTPServer { func addHandler(handler: T) { handlers.append { (request: HTTPRequest, args: Any) -> Bool in if let typedArgs = args as? T.Data { return handler.handle(request, data: typedArgs) } return false } } // ... } |
然後,我們的HTTPServer
類擁有一個泛型方法,它接收一個HTTPHandlerType
型別作為引數。使用處理程式的關聯型別,它能執行args
引數的條件式向下轉換(conditional downcast),來檢測這個處理程式是否應該處理該http請求。現在我們能看到定義HTTPHandlerType
作為協議的好處了,HTTPServer
不需要知道處理程式如何響應請求,甚至不需要知道處理程式本身,它只需要知道能夠處理請求的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class HTTPServer { // ... private var handlers: [(HTTPRequest, Any) -> Bool] = [] func dispatch(req: HTTPRequest, args: Any) -> Bool { for handler in handlers { if handler(req, args) { return true } } return false } } |
當我們的HTTPServer
接收一個請求時,它將遍歷一遍裡面的處理程式,看是否有程式能響應這個請求。
現在我們能很簡單的建立一個自定義的包含不同的引數型別的HTTPHandlerType
,並且將它註冊到HTTPServer
:
1 2 3 4 5 6 7 8 9 10 11 |
class MyHandler : HTTPHandlerType { func handle(request: HTTPRequest, data: Int) -> Bool { return data > 5 } } let server = HTTPServer() server.addHandler(MyHandler()) server.dispatch(HTTPRequest(...), args: "x") // returns false server.dispatch(HTTPRequest(...), args: 5) // returns false server.dispatch(HTTPRequest(...), args: 10) // returns true |
通過協議和泛型的結合,現在我們能優雅的編寫Swift程式碼來建立和註冊包含不同型別的HTTP處理程式。這個方法還能夠讓編譯器在保證執行時效能的同時確保型別安全。