精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面向協議編程與 Cocoa 的邂逅 (下)

網絡 網絡管理
本文 (下) 主要展示了一些筆者日常使用面向協議思想和 Cocoa 開發結合的示例代碼,并對其進行了一些解說。

[[403619]]

本文筆者在 MDCC 16 (移動開發者大會) 上 iOS 專場中的主題演講的文字整理。發布于 2016年11月29日 最后更新于 2020年10月22日

您可以在這里[1]找到演講使用的 Keynote,部分示例代碼可以在 MDCC 2016 的 官方 repo[2]中找到。

在上半部分[3]主要介紹了一些理論方面的內容,包括面向對象編程存在的問題,面向協議的基本概念和決策模型等。本文 (下) 主要展示了一些筆者日常使用面向協議思想和 Cocoa 開發結合的示例代碼,并對其進行了一些解說。

1. 在日常開發中使用協議

WWDC 2015 在 POP 方面有一個非常優秀的主題演講:#408 Protocol-Oriented Programming in Swift[4]。Apple 的工程師通過舉了畫圖表和排序兩個例子,來闡釋 POP 的思想。我們可以使用 POP 來解耦,通過組合的方式讓代碼有更好的重用性。不過在 #408 中,涉及的內容偏向理論,而我們每天的 app 開發更多的面臨的還是和 Cocoa 框架打交道。在看過 #408 以后,我們就一直在思考,如何把 POP 的思想運用到日常的開發中?

我們在這個部分會舉一個實際的例子,來看看 POP 是如何幫助我們寫出更好的代碼的。

1.1 基于 Protocol 的網絡請求

網絡請求層是實踐 POP 的一個理想場所。我們在接下的例子中將從零開始,用最簡單的面向協議的方式先構建一個不那么完美的網絡請求和模型層,它可能包含一些不合理的設計和耦合,但是卻是初步最容易得到的結果。

然后我們將逐步捋清各部分的所屬,并用分離職責的方式來進行重構。最后我們會為這個網絡請求層進行測試。通過這個例子,我希望能夠設計出包括類型安全,解耦合,易于測試和良好的擴展性等諸多優秀特性在內的 POP 代碼。

  • Talk is cheap, show me the code.

1.1.1 初步實現

首先,我們想要做的事情是從一個 API 請求一個 JSON,然后將它轉換為 Swift 中可用的實例。返回內容:

  1. {"name":"onevcat","message":"Welcome to MDCC 16!"

我們可以新建一個項目,并添加 User.swift 來作為模型:

  1. // User.swift 
  2. import Foundation 
  3.  
  4. struct User { 
  5.     let name: String 
  6.     let message: String 
  7.      
  8.     init?(data: Data) { 
  9.         guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Anyelse { 
  10.             return nil 
  11.         } 
  12.         guard let name = obj?["name"as? String else { 
  13.             return nil 
  14.         } 
  15.         guard let message = obj?["message"as? String else { 
  16.             return nil 
  17.         } 
  18.          
  19.         self.name = name 
  20.         self.message = message 
  21.     } 

User.init(data:) 將輸入的數據 (從網絡請求 API 獲取) 解析為 JSON 對象,然后從中取出 name 和 message,并構建代表 API 返回的 User 實例,非常簡單。

現在讓我們來看看有趣的部分,也就是如何使用 POP 的方式從 URL 請求數據,并生成對應的 User。首先,我們可以創建一個 protocol 來代表請求。對于一個請求,我們需要知道它的請求路徑,HTTP 方法,所需要的參數等信息。一開始這個協議可能是這樣的:

  1. enum HTTPMethod: String { 
  2.     case GET 
  3.     case POST 
  4.  
  5. protocol Request { 
  6.     var host: String { get } 
  7.     var path: String { get } 
  8.      
  9.     var method: HTTPMethod { get } 
  10.     var parameter: [String: Any] { get } 

將 host 和 path 拼接起來可以得到我們需要請求的 API 地址。為了簡化,HTTPMethod 現在只包含了 GET 和 POST 兩種請求方式,而在我們的例子中,我們只會使用到 GET 請求。

現在,可以新建一個 UserRequest 來實現 Request 協議:

  1. struct UserRequest: Request { 
  2.     let name: String 
  3.      
  4.     let host = "https://api.onevcat.com" 
  5.     var path: String { 
  6.         return "/users/\(name)" 
  7.     } 
  8.     let method: HTTPMethod = .GET 
  9.     let parameter: [String: Any] = [:] 

UserRequest 中有一個未定義初始值的 name 屬性,其他的屬性都是為了滿足協議所定義的。因為請求的參數用戶名 name 會通過 URL 進行傳遞,所以 parameter 是一個空字典就足夠了。有了協議定義和一個滿足定義的具體請求,現在我們需要發送請求。為了任意請求都可以通過同樣的方法發送,我們將發送的方法定義在 Request 協議擴展上:

  1. extension Request { 
  2.     func send(handler: @escaping (User?) -> Void) { 
  3.         // ... send 的實現 
  4.     } 

在 send(handler:) 的參數中,我們定義了可逃逸的 (User?) -> Void,在請求完成后,我們調用這個 handler 方法來通知調用者請求是否完成,如果一切正常,則將一個 User 實例傳回,否則傳回 nil。

我們想要這個 send 方法對于所有的 Request 都通用,所以顯然回調的參數類型不能是 User。通過在 Request 協議中添加一個關聯類型,我們可以將回調參數進行抽象。在 Request 最后添加:

  1. protocol Request { 
  2.     ... 
  3.     associatedtype Response 

然后在 UserRequest 中,我們也相應地添加類型定義,以滿足協議:

  1. struct UserRequest: Request { 
  2.     ... 
  3.     typealias Response = User 

現在,我們來重新實現 send 方法,現在,我們可以用 Response 代替具體的 User,讓 send 一般化。我們這里使用 URLSession 來發送請求:

  1. extension Request { 
  2.     func send(handler: @escaping (Response?) -> Void) { 
  3.         let url = URL(string: host.appending(path))! 
  4.         var request = URLRequest(url: url) 
  5.         request.httpMethod = method.rawValue 
  6.          
  7.         // 在示例中我們不需要 `httpBody`,實踐中可能需要將 parameter 轉為 data 
  8.         // request.httpBody = ... 
  9.          
  10.         let task = URLSession.shared.dataTask(with: request) { 
  11.             data, res, error in 
  12.             // 處理結果 
  13.             print(data) 
  14.         } 
  15.         task.resume() 
  16.     } 

通過拼接 host 和 path,可以得到 API 的 entry point。根據這個 URL 創建請求,進行配置,生成 data task 并將請求發送。剩下的工作就是將回調中的 data 轉換為合適的對象類型,并調用 handler 通知外部調用者了。對于 User 我們知道可以使用 User.init(data:),但是對于一般的 Response,我們還不知道要如何將數據轉為模型。我們可以在 Request 里再定義一個 parse(data:) 方法,來要求滿足該協議的具體類型提供合適的實現。這樣一來,提供轉換方法的任務就被“下放”到了 UserRequest:

  1. protocol Request { 
  2.     ... 
  3.     associatedtype Response 
  4.     func parse(data: Data) -> Response? 
  5.  
  6. struct UserRequest: Request { 
  7.     ... 
  8.     typealias Response = User 
  9.     func parse(data: Data) -> User? { 
  10.         return User(data: data) 
  11.     } 

有了將 data 轉換為 Response 的方法后,我們就可以對請求的結果進行處理了:

  1. extension Request { 
  2.     func send(handler: @escaping (Response?) -> Void) { 
  3.         let url = URL(string: host.appending(path))! 
  4.         var request = URLRequest(url: url) 
  5.         request.httpMethod = method.rawValue 
  6.          
  7.         // 在示例中我們不需要 `httpBody`,實踐中可能需要將 parameter 轉為 data 
  8.         // request.httpBody = ... 
  9.          
  10.         let task = URLSession.shared.dataTask(with: request) { 
  11.             data, _, error in 
  12.             if let data = data, let res = parse(data: data) { 
  13.                 DispatchQueue.main.async { handler(res) } 
  14.             } else { 
  15.                 DispatchQueue.main.async { handler(nil) } 
  16.             } 
  17.         } 
  18.         task.resume() 
  19.     } 

現在,我們來試試看請求一下這個 API:

  1. let request = UserRequest(name"onevcat"
  2. request.send { user in 
  3.     if let user = user { 
  4.         print("\(user.message) from \(user.name)"
  5.     } 
  6.  
  7. // Welcome to MDCC 16! from onevcat 

1.1.2 重構,關注點分離

雖然能夠實現需求,但是上面的實現可以說非常糟糕。讓我們看看現在 Request 的定義和擴展:

  1. protocol Request { 
  2.     var host: String { get } 
  3.     var path: String { get } 
  4.      
  5.     var method: HTTPMethod { get } 
  6.     var parameter: [String: Any] { get } 
  7.      
  8.     associatedtype Response 
  9.     func parse(data: Data) -> Response? 
  10.  
  11. extension Request { 
  12.     func send(handler: @escaping (Response?) -> Void) { 
  13.         ... 
  14.     } 

這里最大的問題在于,Request 管理了太多的東西。一個 Request 應該做的事情應該僅僅是定義請求入口和期望的響應類型,而現在 Request 不光定義了 host 的值,還對如何解析數據了如指掌。最后 send方法被綁死在了 URLSession 的實現上,而且是作為 Request 的一部分存在。

這是很不合理的,因為這意味著我們無法在不更改請求的情況下更新發送請求的方式,它們被耦合在了一起。這樣的結構讓測試變得異常困難,我們可能需要通過 stub 和 mock 的方式對請求攔截,然后返回構造的數據,這會用到 NSURLProtocol 的內容,或者是引入一些第三方的測試框架,大大增加了項目的復雜度。在 Objective-C 時期這可能是一個可選項,但是在 Swift 的新時代,我們有好得多的方法來處理這件事情。

讓我們開始著手重構剛才的代碼,并為它們加上測試吧。首先我們將 send(handler:) 從 Request 分離出來。我們需要一個單獨的類型來負責發送請求。這里基于 POP 的開發方式,我們從定義一個可以發送請求的協議開始:

  1. protocol Client { 
  2.     func send(_ r: Request, handler: @escaping (Request.Response?) -> Void) 
  3.  
  4. // 編譯錯誤 

從上面的聲明從語義上來說是挺明確的,但是因為 Request 是含有關聯類型的協議,所以它并不能作為獨立的類型來使用,我們只能夠將它作為類型約束,來限制輸入參數 request。正確的聲明方式應當是:

  1. protocol Client { 
  2.     func send<T: Request>(_ r: T, handler: @escaping (T.Response?) -> Void) 
  3.  
  4.     var host: String { get } 

除了使用 <T: Request> 這個泛型方式以外,我們還將 host 從 Request 移動到了 Client 里,這是更適合它的地方。現在,我們可以把含有 send 的 Request 協議擴展刪除,重新創建一個類型來滿足 Client 了。和之前一樣,它將使用 URLSession 來發送請求:

  1. struct URLSessionClient: Client { 
  2.     let host = "https://api.onevcat.com" 
  3.      
  4.     func send<T: Request>(_ r: T, handler: @escaping (T.Response?) -> Void) { 
  5.         let url = URL(string: host.appending(r.path))! 
  6.         var request = URLRequest(url: url) 
  7.         request.httpMethod = r.method.rawValue 
  8.          
  9.         let task = URLSession.shared.dataTask(with: request) { 
  10.             data, _, error in 
  11.             if let data = data, let res = r.parse(data: data) { 
  12.                 DispatchQueue.main.async { handler(res) } 
  13.             } else { 
  14.                 DispatchQueue.main.async { handler(nil) } 
  15.             } 
  16.         } 
  17.         task.resume() 
  18.     } 

現在發送請求的部分和請求本身分離開了,而且我們使用協議的方式定義了 Client。除了 URLSessionClient 以外,我們還可以使用任意的類型來滿足這個協議,并發送請求。這樣網絡層的具體實現和請求本身就不再相關了,我們之后在測試的時候會進一步看到這么做所帶來的好處。

現在這個的實現里還有一個問題,那就是 Request 的 parse 方法。請求不應該也不需要知道如何解析得到的數據,這項工作應該交給 Response 來做。而現在我們沒有對 Response 進行任何限定。接下來我們將新增一個協議,滿足這個協議的類型將知道如何將一個 data 轉換為實際的類型:

  1. protocol Decodable { 
  2.     static func parse(data: Data) -> Self? 

Decodable 定義了一個靜態的 parse 方法,現在我們需要在 Request 的 Response 關聯類型中為它加上這個限制,這樣我們可以保證所有的 Response 都可以對數據進行解析,原來 Request 中的 parse 聲明也就可以移除了:

  1. // 最終的 Request 協議 
  2. protocol Request { 
  3.     var path: String { get } 
  4.     var method: HTTPMethod { get } 
  5.     var parameter: [String: Any] { get } 
  6.      
  7.     // associatedtype Response 
  8.     // func parse(data: Data) -> Response? 
  9.     associatedtype Response: Decodable 

最后要做的就是讓 User 滿足 Decodable,并且修改上面 URLSessionClient 的解析部分的代碼,讓它使用 Response 中的 parse 方法:

  1. extension User: Decodable { 
  2.     static func parse(data: Data) -> User? { 
  3.         return User(data: data) 
  4.     } 
  5.  
  6. struct URLSessionClient: Client { 
  7.     func send<T: Request>(_ r: T, handler: @escaping (T.Response?) -> Void) { 
  8.         ... 
  9.      // if let data = data, let res = parse(data: data) { 
  10.         if let data = data, let res = T.Response.parse(data: data) { 
  11.             ... 
  12.         } 
  13.     } 

最后,將 UserRequest 中不再需要的 host 和 parse 等清理一下,一個類型安全,解耦合的面向協議的網絡層就呈現在我們眼前了。想要調用 UserRequest 時,我們可以這樣寫:

  1. URLSessionClient().send(UserRequest(name"onevcat")) { user in 
  2.     if let user = user { 
  3.         print("\(user.message) from \(user.name)"
  4.     } 

當然,你也可以為 URLSessionClient 添加一個單例來減少請求時的創建開銷,或者為請求添加 Promise 的調用方式等等。在 POP 的組織下,這些改動都很自然,也不會牽扯到請求的其他部分。你可以用和 UserRequest 類型相似的方式,為網絡層添加其他的 API 請求,只需要定義請求所必要的內容,而不用擔心會觸及網絡方面的具體實現。

1.1.3 網絡層測試

將 Client 聲明為協議給我們帶來了額外的好處,那就是我們不在局限于使用某種特定的技術 (比如這里的 URLSession) 來實現網絡請求。利用 POP,你只是定義了一個發送請求的協議,你可以很容易地使用像是 AFNetworking 或者 Alamofire 這樣的成熟的第三方框架來構建具體的數據并處理請求的底層實現。我們甚至可以提供一組“虛假”的對請求的響應,用來進行測試。這和傳統的 stub & mock 的方式在概念上是接近的,但是實現起來要簡單得多,也明確得多。我們現在來看一看具體應該怎么做。

我們先準備一個文本文件,將它添加到項目的測試 target 中,作為網絡請求返回的內容:

  1. // 文件名:users:onevcat 
  2. {"name":"Wei Wang""message""hello"

接下來,可以創建一個新的類型,讓它滿足 Client 協議。但是與 URLSessionClient 不同,這個新類型的 send 方法并不會實際去創建請求,并發送給服務器。我們在測試時需要驗證的是一個請求發出后如果服務器按照文檔正確響應,那么我們應該也可以得到正確的模型實例。所以這個新的 Client 需要做的事情就是從本地文件中加載定義好的結果,然后驗證模型實例是否正確:

  1. struct LocalFileClient: Client { 
  2.     func send<T : Request>(_ r: T, handler: @escaping (T.Response?) -> Void) { 
  3.         switch r.path { 
  4.         case "/users/onevcat"
  5.             guard let fileURL = Bundle(for: ProtocolNetworkTests.self).url(forResource: "users:onevcat", withExtension: ""else { 
  6.                 fatalError() 
  7.             } 
  8.             guard let data = try? Data(contentsOf: fileURL) else { 
  9.                 fatalError() 
  10.             } 
  11.             handler(T.Response.parse(data: data)) 
  12.         default
  13.             fatalError("Unknown path"
  14.         } 
  15.     } 
  16.      
  17.     // 為了滿足 `Client` 的要求,實際我們不會發送請求 
  18.     let host = "" 

LocalFileClient 做的事情很簡單,它先檢查輸入請求的 path 屬性,如果是 /users/onevcat (也就是我們需要測試的請求),那么就從測試的 bundle 中讀取預先定義的文件,將其作為返回結果進行 parse,然后調用 handler。如果我們需要增加其他請求的測試,可以添加新的 case 項。另外,加載本地文件資源的部分應該使用更通用的寫法,不過因為我們這里只是示例,就不過多糾結了。

在 LocalFileClient 的幫助下,現在可以很容易地對 UserRequest 進行測試了:

  1. func testUserRequest() { 
  2.     let client = LocalFileClient() 
  3.     client.send(UserRequest(name"onevcat")) { 
  4.         user in 
  5.         XCTAssertNotNil(user
  6.         XCTAssertEqual(user!.name"Wei Wang"
  7.     } 

通過這種方法,我們沒有依賴任何第三方測試庫,也沒有使用 url 代理或者運行時消息轉發等等這些復雜的技術,就可以進行請求測試了。保持簡單的代碼和邏輯,對于項目維護和發展是至關重要的

1.1.4 可擴展性

因為高度解耦,這種基于 POP 的實現為代碼的擴展提供了相對寬松的可能性。我們剛才已經說過,你不必自行去實現一個完整的 Client,而可以依賴于現有的網絡請求框架,實現請求發送的方法即可。

也就是說,你也可以很容易地將某個正在使用的請求方式替換為另外的方式,而不會影響到請求的定義和使用。類似地,在 Response 的處理上,現在我們定義了 Decodable,用自己手寫的方式在解析模型。我們完全也可以使用任意的第三方 JSON 解析庫,來幫助我們迅速構建模型類型,這僅僅只需要實現一個將 Data 轉換為對應模型類型的方法即可。

如果你對 POP 方式的網絡請求和模型解析感興趣的話,不妨可以看看 APIKit[5] 這個框架,我們在示例中所展示的方法,正是這個框架的核心思想。

使用協議幫助改善代碼設計

通過面向協議的編程,我們可以從傳統的繼承上解放出來,用一種更靈活的方式,搭積木一樣對程序進行組裝。每個協議專注于自己的功能,特別得益于協議擴展,我們可以減少類和繼承帶來的共享狀態的風險,讓代碼更加清晰。

高度的協議化有助于解耦、測試以及擴展,而結合泛型來使用協議,更可以讓我們免于動態調用和類型轉換的苦惱,保證了代碼的安全性。

提問環節

主題演講后有幾位朋友提了一些很有意義的問題,在這里我也稍作整理。有可能問題和回答與當時的情形會有小的出入,僅供參考。

我剛才在看 demo 的時候發現,你都是直接先寫 protocol,而不是 struct 或者 class。是不是我們在實踐 POP 的時候都應該直接先定義協議?

  • 我直接寫 protocol 是因為我已經對我要做什么有充分的了解,并且希望演講不要超時。但是實際開發的時候你可能會無法一開始就寫出合適的協議定義。建議可以像我在 demo 中做的那樣,先“粗略”地進行定義,然后通過不斷重構來得到一個最終的版本。當然,你也可以先用紙筆勾勒一個輪廓,然后再去定義和實現協議。當然了,也沒人規定一定需要先定義協議,你完全也可以從普通類型開始寫起,然后等發現共通點或者遇到我們之前提到的困境時,再回頭看看是不是面向協議更加合適,這需要一定的 POP 經驗。

既然 POP 有這么多好處,那我們是不是不再需要面向對象,可以全面轉向面向協議了?

  • 答案可能讓你失望。在我們的日常項目中,每天打交道的 Cocoa 其實還是一個帶有濃厚 OOP 色彩的框架。也就是說,可能一段時期內我們不可能拋棄 OOP。不過 POP 其實可以和 OOP “和諧共處”,我們也已經看到了不少使用 POP 改善代碼設計的例子。另外需要補充的是,POP 其實也并不是銀彈,它有不好的一面。最大的問題是協議會增加代碼的抽象層級 (這點上和類繼承是一樣的),特別是當你的協議又繼承了其他協議的時候,這個問題尤為嚴重。在經過若干層的繼承后,滿足末端的協議會變得困難,你也難以確定某個方法究竟滿足的是哪個協議的要求。這會讓代碼迅速變得復雜。如果一個協議并沒有能描述很多共通點,或者說能讓人很快理解的話,可能使用基本的類型還會更簡單一些。

謝謝你的演講,想問一下你們在項目中使用 POP 的情況

  • 我們在項目里用了很多 POP 的概念。上面 demo 里的網絡請求的例子就是從實際項目中抽出來的,我們覺得這樣的請求寫起來非常輕松,因為代碼很簡單,新人進來交接也十分愜意。除了模型層之外,我們在 view 和 view controller 層也用了一些 POP 的代碼,比如從 nib 創建 view 的 NibCreatable,支持分頁請求 tableview controller 的 NextPageLoadable,空列表時顯示頁面的 EmptyPage 等等。因為時間有限,不可能展開一一說明,所以這里我只挑選了一個具有代表性,又不是很復雜的網絡的例子。其實每個協議都讓我們的代碼,特別是 View Controller 變短,而且使測試變為可能。可以說,我們的項目從 POP 受益良多,而且我們應該會繼續使用下去。

本文轉載自微信公眾號「Swift 社區」,可以通過以下二維碼關注。轉載本文請聯系Swift 社區公眾號。

 

責任編輯:姜華 來源: Swift 社區
相關推薦

2021-06-03 08:55:58

面向協議編程

2016-12-12 15:22:41

編程

2022-07-30 23:41:53

面向過程面向對象面向協議編程

2011-08-11 15:46:55

CocoaCocoa Touch框架

2011-07-08 18:03:30

Cocoa Touch 網絡

2011-05-11 15:27:58

Windows OOPCocoa MVCCocoa

2018-07-23 15:55:28

協議自定義viewSwift

2011-07-22 15:50:06

Cocoa MVC 視圖

2009-04-22 09:20:26

Erlang并發函數式

2015-10-16 09:59:52

SwiftCocoa

2013-07-30 09:42:41

實現編程接口編程對象編程

2011-08-15 15:56:29

Cocoa編程模塊

2015-03-20 09:54:44

網絡編程面向連接無連接

2010-07-09 11:12:09

UDP協議

2024-01-03 13:38:00

C++面向對象編程OOP

2014-05-08 14:13:00

Java面向GC

2009-06-16 15:02:18

面向對象編程PHP異常PHP代理

2011-07-18 10:03:18

CocoaQt

2011-09-07 15:33:33

CocoaiOSObjective-C

2011-07-28 18:11:18

Objective-C Cocoa 編程
點贊
收藏

51CTO技術棧公眾號

天天操夜夜操av| 欧美午夜寂寞| 亚洲欧美日本日韩| 欧美成人a∨高清免费观看| 99在线免费视频观看| 久久久久久女乱国产| 日韩精品欧美精品| 美女久久久久久久| 国产精品揄拍100视频| 日韩久久99| 亚洲综合丝袜美腿| 狠狠色综合一区二区| 久久精品99北条麻妃| 日本一区二区在线看| 亚洲第一天堂无码专区| 午夜宅男在线视频| 99riav视频在线观看| 中文字幕高清不卡| 精品国产_亚洲人成在线| av免费在线播放网站| 日韩 国产 欧美| 欧美在线国产| 欧美女孩性生活视频| 99re国产视频| 最近中文字幕在线观看视频| 夜夜躁日日躁狠狠久久av| 99精品在线视频观看| 国产视频一区三区| 久久成人一区二区| 久久久久无码精品国产sm果冻| 精品亚洲精品| 日韩欧美一级精品久久| 欧美精品久久久久久久久25p| 午夜视频福利在线观看| 雨宫琴音一区二区在线| 不用播放器成人网| 激情无码人妻又粗又大| 久久91麻豆精品一区| 亚洲精品在线网站| 91丨porny丨九色| 国产亚洲欧美日韩精品一区二区三区 | 国产原创av在线| 成人精品视频一区二区三区| 97netav| 国产农村老头老太视频| 紧缚奴在线一区二区三区| 国产精品免费久久久久影院| 精品久久久久久久久久久久久久久久久久| 日本一区二区乱| 亚洲一区av在线| 久久观看最新视频| а√天堂在线官网| 亚洲视频一区二区在线| 色撸撸在线观看| 伊人免费在线| 国产精品久久福利| 中文精品视频一区二区在线观看| 欧美一区二区三区| 五月激情丁香网| 色戒汤唯在线观看| 天天色综合天天| 缅甸午夜性猛交xxxx| 国产蜜臀av在线播放| 久久久噜噜噜久久中文字幕色伊伊| 国产欧美欧洲| 欧美日韩在线精品一区二区三区激情综| 91小视频在线| 精品日本高清在线播放| 精品免费视频123区| 精品视频在线观看免费| 少妇精品导航| 日韩激情视频在线播放| 天天摸天天舔天天操| 伊人影院在线视频| 黄网站色欧美视频| 国产天堂在线播放| 岛国在线大片| caoporm超碰国产精品| 91九色蝌蚪国产| 亚洲成人一二三区| 91色综合久久久久婷婷| 亚洲精品9999| 欧美日韩一区二区视频在线| 无码人妻精品一区二区三区不卡 | 久久在线中文字幕| 免费a级毛片在线观看| 国产精品免费aⅴ片在线观看| 日韩美女在线播放| 在线免费观看av片| 高清不卡在线观看av| 欧美国产综合视频| 国产传媒在线播放| 日本中文字幕一区二区视频| 一区二区免费看| 免费观看成人在线| 97超碰在线公开在线看免费| 欧美日韩精品在线| 另类小说色综合| 在线成人视屏| 欧美视频一区二区三区…| 成人日韩在线视频| 亚洲精选av| 欧美午夜精品久久久久久超碰| 男女视频在线观看网站| 亚洲免费专区| 日本二三区不卡| 伊人国产在线视频| 久久免费视频66| 日韩资源在线观看| 天天操夜夜操视频| 国产福利一区在线观看| 先锋在线资源一区二区三区| 啊啊啊久久久| 日韩一区国产二区欧美三区| 久久夜色撩人精品| 国产精华一区二区三区| 秋霞视频一区二区| 成人欧美一区二区三区小说| 国产精品一级久久久| 中文字幕在线播出| 免费久久99精品国产自在现线| 国产精品一区二区久久久| 一二三四区在线| 91伊人久久大香线蕉| 欧美在线播放一区| 国产精品一级伦理| 国产日韩欧美麻豆| 国产素人在线观看| 成人自拍在线| 操91在线视频| 国产女人爽到高潮a毛片| 欧美国产激情二区三区| 99色精品视频| 中文字幕在线视频区| 欧美三级网站| 亚洲免费av在线| 三上悠亚在线一区| 成人精品久久| 国产视频精品久久久| 久久99久久98精品免观看软件 | 亚洲成人xxx| 国产稀缺精品盗摄盗拍| 久久99精品视频| 精品国产乱码久久久久久久软件 | 成人国产在线观看| 成人在线观看毛片| 日韩精品视频在线看| 久久人人爽人人爽人人片亚洲| 中文字幕一区二区人妻| 中文字幕乱码亚洲精品一区| 大香煮伊手机一区| 韩日一区二区三区| 欧美ab在线视频| 中文字幕日韩视频| 中文字幕+乱码+中文字幕明步| 国产三级欧美三级日产三级99| 337p粉嫩大胆噜噜噜鲁| 成人爽a毛片免费啪啪红桃视频| 久久久噜噜噜久久| 欧美日韩国产一二三区| 波多野结衣一区| 国产999在线| 日本视频www色| 成人欧美一区二区三区在线播放| 免费不卡av网站| 亚洲啊v在线免费视频| 中文字幕不卡在线视频极品| 一级片视频网站| 亚洲乱码一区二区三区在线观看| 性一交一黄一片| 一本色道久久| 色一情一乱一伦一区二区三欧美| 免费视频观看成人| 国产91精品一区二区麻豆网站| 日韩视频欧美视频| jizz中国女人| 亚洲成a天堂v人片| 欧美日在线观看| aa片在线观看视频在线播放| 首页综合国产亚洲丝袜| 伊人色综合久久天天五月婷| 99国产精品免费网站| 欧洲亚洲女同hd| av免费在线不卡| 欧美性高跟鞋xxxxhd| www.久久av.com| 欧美日韩国产传媒| 九九久久久久久久久激情| 亚洲av成人无码久久精品老人 | 欧美日韩一区二区视频在线| 青青久久精品| 亚州精品天堂中文字幕| 91在线高清| 中文字幕va一区二区三区| 日本精品在线视频| 欧美黄色小说| 欧美mv日韩mv| 五月婷婷综合在线观看| 国产美女主播视频一区| 欧洲一区二区在线观看| 日韩中文一区二区| 伊人激情综合网| 午夜精品在线播放| 91搞黄在线观看| 疯狂撞击丝袜人妻| 久久人人爽爽爽人久久久| 午夜久久福利视频| 亚洲综合好骚| 艳母动漫在线免费观看| 在线三级中文| 亚洲激情自拍图| 99精品在线看| 欧美日本免费一区二区三区| 国产专区第一页| 一区二区三区四区精品在线视频 | 天天操天天插天天射| 欧美裸体一区二区三区| 精品国产免费人成电影在线观...| 日产精品久久久久久久性色| 91成人免费网站| 精品无码一区二区三区电影桃花| 亚洲欧美在线另类| 亚洲av毛片基地| 久久这里都是精品| 日韩av综合在线观看| 日韩高清在线免费观看| 热门国产精品亚洲第一区在线| 欧美jizz18性欧美| 亚洲精品一区二区久| 亚洲国产999| 日韩女优制服丝袜电影| 国产原创中文av| 欧美日韩一区国产| 国产91国语对白在线| 欧美性感美女h网站在线观看免费| 日本三级片在线观看| 亚洲国产美国国产综合一区二区| 欧美人与禽zozzo禽性配| 亚洲色图都市小说| 青青操在线播放| 亚洲人妻一区二区| 粉嫩av一区二区三区| 性生交大片免费看l| 国产一区二区三区av电影| 肉大捧一出免费观看网站在线播放| 日韩高清影视在线观看| 久久久久久精| 都市激情亚洲| www久久99| 亚洲不卡在线| 国产精品日韩二区| 欧美美女黄色| 综合欧美国产视频二区| 国产精品51麻豆cm传媒| 国产精品美日韩| 国产伦理在线观看| 暴力调教一区二区三区| 免费在线观看成年人视频| 久久日韩粉嫩一区二区三区| 久久久久久国产免费a片| 中文字幕欧美日韩一区| 97精品在线播放| 亚洲综合在线免费观看| 亚洲欧美国产精品| 国产三级在线观看视频| 日韩精品一区二区三区老鸭窝 | 亚洲a∨日韩av高清在线观看| 亚洲福利影视| 国产精品久久久久久久久男| 国产精品亚洲综合在线观看| 成人av免费看| 久草在线新免费首页资源站| 久久精品人人爽| 日韩另类在线| 国内免费久久久久久久久久久| 漫画在线观看av| 国产精品高潮粉嫩av| 粉嫩一区二区三区在线观看| 国产一区二区三区黄| 精品久久影视| 国产乱人伦精品一区二区三区| 国产女优一区| 午夜一区二区视频| 成人在线一区二区三区| 97人妻人人揉人人躁人人| 亚洲欧美日韩国产综合在线| 亚欧洲精品在线视频| 欧美三片在线视频观看| 18岁网站在线观看| 美女网站在线看| 国产亚洲欧洲高清| 午夜国产福利在线| 欧美老少配视频| 日日av拍夜夜添久久免费| 九九热最新视频//这里只有精品| 伊人久久国产| 欧美性大战久久久久久久| 亚洲免费视频二区| 日韩国产中文字幕| wwwav在线| 7777免费精品视频| 青草综合视频| 日本在线观看不卡| 国产成人av| 一区二区三区四区久久| 欧美 日韩 国产 一区| 日本成人中文字幕在线| 成人h动漫精品| 欧美手机在线观看| 欧美性生活大片视频| 亚洲aaaaaaa| 欧美国产精品va在线观看| 欧美在线se| 在线成人免费视频| 国产乱了高清露脸对白| 99久久久国产精品| 天天躁日日躁aaaa视频| 中文字幕一区二区三区在线不卡| 国产女主播喷水高潮网红在线| 亚洲成人免费av| 91国内精品视频| 欧美成人精品3d动漫h| 成人福利在线| 青青久久av北条麻妃黑人| 91精品丝袜国产高跟在线| 精品视频导航| 视频在线不卡免费观看| 国产一区免费在线| 超碰在线成人| 白白操在线视频| 日本91福利区| 亚洲精品91在线| 欧美影院一区二区三区| 国产在线资源| 国产ts一区二区| 国产99久久| 妞干网在线免费视频| 26uuu色噜噜精品一区| 日韩毛片一区二区三区| 欧美在线观看天堂一区二区三区| 国产午夜福利100集发布| 日韩成人av在线资源| 久久免费视频观看| 9999精品| 国产在线精品一区二区三区| 在线成人av| 欧美视频亚洲图片| 久久久综合精品| 视频一区亚洲| 欧美激情麻豆| 国产福利在线免费| 国产亚洲精品中文字幕| 国内精品久久99人妻无码| 91精品办公室少妇高潮对白| 在线观看毛片av| 日韩激情在线视频| 香蕉视频亚洲一级| 久久精品magnetxturnbtih| 亚洲欧美日韩国产一区二区| 中文字幕免费高清| 欧美日免费三级在线| 国产在线高潮| 国产精品av一区| 欧美一级网站| 手机看片国产日韩| 日韩一区二区三区高清免费看看| 丁香花在线观看完整版电影| 久久riav二区三区| 日韩高清欧美激情| 中文字幕天堂网| 黄色成人免费网| 国模私拍一区二区三区| 欧美13videosex性极品| 欧美福利精品| 美女久久网站| 久久久久久久久久97| 91精品国产91久久久久久最新毛片 | 久久精品美女视频网站 | 少妇特黄a一区二区三区| 国产一区二区三区的电影 | 一区二区三区日韩在线观看| 污视频网站免费观看| 成人福利网站在线观看| 亚洲黄色免费| 在线观看天堂av| 亚洲国产精品视频在线观看| 亚洲 小说区 图片区 都市| 久久99热精品| 国内在线免费高清视频| 欧美性生交xxxxxdddd| 在线免费观看黄色| 97在线观看免费| 日韩欧美自拍| 日韩片在线观看| 色综合久久综合网欧美综合网 | 欧美一级xxxx| 亚洲国产精品久久久男人的天堂| 国产精品怡红院| 国产91精品高潮白浆喷水| 爽成人777777婷婷|