精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

久久亚洲一区二区三区明星换脸| 熟女少妇内射日韩亚洲| 极品魔鬼身材女神啪啪精品| 免费成人在线电影| 久久99国产精品尤物| 亚洲精品在线91| 懂色中文一区二区三区在线视频| 调教驯服丰满美艳麻麻在线视频| 爱看av在线| 99热在线成人| 一本大道久久精品懂色aⅴ| 91成人在线看| 99久久久免费精品| h1515四虎成人| 久久中文娱乐网| 国产精品免费网站| 国产高清一区二区三区四区| 少妇视频一区| 国产精品久久久久久户外露出| 5566日本婷婷色中文字幕97| 欧产日产国产精品98| 欧洲黄色一区| 成人免费黄色在线| 欧美精品久久久久久久| 亚洲熟女一区二区三区| 欧美videosex性欧美黑吊| 国产亚洲污的网站| 国产精品盗摄久久久| 男人舔女人下部高潮全视频| 9l视频自拍九色9l视频成人| 亚洲精品高清视频在线观看| 亚洲自拍偷拍福利| 精品99在线观看| 999在线精品| 欧美日韩国产美| 老司机av福利| 国产情侣av在线| 欧美日韩视频一区二区三区| 日韩欧美你懂的| 免费人成自慰网站| 黄色小视频免费观看| 99成人在线| 亚洲欧美日韩天堂一区二区| 日本xxxxxxx免费视频| 成人免费在线观看| 国产在线日韩欧美| 欧美高清激情视频| 黄色污在线观看| 性国裸体高清亚洲| 中文字幕欧美激情一区| 国产欧美日韩丝袜精品一区| 全网免费在线播放视频入口| 精品淫伦v久久水蜜桃| 亚洲成av人片一区二区三区| 久久久久久久久久久久久9999| 国产午夜精品久久久久| 久久中文字幕二区| 日韩一区二区免费在线电影| 18禁裸男晨勃露j毛免费观看| 国产在线一区二区视频| 成人av综合一区| 国产成人av在线播放| 久久av红桃一区二区禁漫| 成人免费在线观看av| 精品久久久久久久人人人人传媒 | 国内精品久久久久久99蜜桃| 欧美日韩中文字幕一区二区| www.欧美黄色| 国产视频福利在线| 国产黄人亚洲片| 日本午夜人人精品| 极品颜值美女露脸啪啪| 色综合综合网| 欧美xxx久久| 日本人添下边视频免费| 玖玖精品在线| 欧美日韩黄色大片| 天天干天天色天天爽| 日产精品久久久久久久性色| 国产一区二区三区免费看 | 欧美理论电影在线观看| 蜜臀av一区二区三区有限公司| 色综合久久久| 色国产综合视频| av在线播放天堂| 黄色污网站在线观看| 亚洲乱码国产乱码精品精98午夜 | 伊人久久大香线蕉精品组织观看| 国产视频自拍一区| 蜜桃久久精品成人无码av| 91欧美在线| 欧美激情综合亚洲一二区| 天天操天天摸天天干| 欧美区一区二| 国产精品美女在线观看直播| 久久久五月婷婷| 99影视tv| 一级做a爱片性色毛片| 六月婷婷一区| 97在线视频国产| 日韩高清dvd碟片| 亚洲无线视频| 欧美激情国产日韩精品一区18| 中文在线观看免费网站| 国产精品二区影院| 奇米四色中文综合久久| 久久久国产高清| 麻豆精品视频在线观看免费| 国产精品老女人精品视频| 国产视频一二三四区| 久久夜色精品国产欧美乱极品| 9999在线观看| 欧美电影网站| 日韩欧美一区视频| 97超碰青青草| 国产精品伦理| 色偷偷成人一区二区三区91| 国产5g成人5g天天爽| 综合久草视频| 日韩视频在线你懂得| 搡老熟女老女人一区二区| 日本妇女一区| 精品五月天久久| 久久久久亚洲av片无码| 久久久久久久波多野高潮日日| 亚洲最大福利视频网| www在线播放| 欧美日韩免费在线| 最好看的中文字幕| 精品一区二区三区免费看| 欧美一区二区日韩| 美女露出粉嫩尿囗让男人桶| 激情小说亚洲图片| 美日韩在线视频| 欧美另类视频在线观看| 美国三级日本三级久久99| 欧美激情国产日韩| 福利视频在线播放| 狠狠躁18三区二区一区| xfplay5566色资源网站| 欧美日韩18| 亚洲a区在线视频| 嫩草在线视频| 亚洲一区二区影院| 免费无码av片在线观看| 国产成+人+综合+亚洲欧美| 日韩精品视频免费在线观看| 少妇av片在线观看| 噜噜噜在线观看免费视频日韩| 国产视色精品亚洲一区二区| 你懂得网站在线| 国产精品美女久久久久久久久| 免费看av软件| 国产亚洲人成a在线v网站 | 91夜夜未满十八勿入爽爽影院| 国产色综合视频| 1区2区3区精品视频| 国产小视频免费| 国产日韩电影| 亚洲欧美日韩中文在线制服| 国产黄网在线观看| 国产一区久久久| 中文字幕在线亚洲三区| 91av久久| 7777精品久久久大香线蕉| 少妇搡bbbb搡bbb搡打电话| 国产一区久久| 国产在线一区二区三区欧美| 松下纱荣子在线观看| 亚洲一区二区黄| 黄色一级片在线| 成人午夜伦理影院| 亚洲中文字幕无码专区| avtt久久| 欧美国产极速在线| 日韩中文字幕综合| 中文字幕电影一区| 中文字幕 欧美日韩| 蜜桃国内精品久久久久软件9| 日本一欧美一欧美一亚洲视频| 春暖花开成人亚洲区| 欧美精品 国产精品| 久久久亚洲av波多野结衣| 久久性天堂网| 国产在线精品一区二区三区》| 粉嫩一区二区| 成年人精品视频| 亚洲午夜无码久久久久| 99国产一区二区三精品乱码| 狠狠干视频网站| 久久这里只有精品一区二区| 国产激情久久久久| 国产黄色在线免费观看| 日韩欧美一区二区三区| 日韩在线观看免| 成人h精品动漫一区二区三区| 成人在线观看黄| 欧美成人高清| 亚洲精品免费在线视频| 17videosex性欧美| 日韩亚洲综合在线| 人妻中文字幕一区二区三区| 亚洲免费av在线| japanese中文字幕| 国产福利91精品一区二区三区| 亚洲中文字幕无码不卡电影| 欧美一区精品| 日日骚一区二区网站| av日韩亚洲| 欧美另类极品videosbest最新版本 | 午夜av在线免费观看| 欧美三区在线观看| 国产亚洲欧美久久久久| 国产馆精品极品| 久草综合在线观看| 精品亚洲成人| 国产精品入口尤物| 日韩电影免费看| 久久综合网hezyo| 成人影视在线播放| 日韩精品在线免费观看| 成人免费一级视频| 91精品国产综合久久精品图片 | 久久成人免费日本黄色| 九九九九免费视频| 亚洲午夜伦理| 国产一区二区三区在线免费| 91精品国偷自产在线电影| 5566中文字幕一区二区| 国产精品伦一区二区| 欧美在线一区二区视频| av在线中出| 久久男人的天堂| 日本精品999| 日韩欧美aaa| 亚洲 欧美 日韩 综合| 亚洲国产视频一区二区| 人妻 丝袜美腿 中文字幕| 久久99精品国产麻豆不卡| 亚洲一区二区蜜桃| 久久精品久久久| 亚洲一区二区精品在线观看| 欧美日韩黄色| 成人在线视频福利| 好看的中文字幕在线播放| 久久综合电影一区| 里番在线观看网站| 久久国产一区二区三区| 欧美自拍偷拍一区二区| 日韩一区二区三区免费观看| 中文字幕日本人妻久久久免费| 一区二区三区波多野结衣在线观看| 久久福利小视频| 99久久综合色| 一女二男3p波多野结衣| 亚洲美女色禁图| 成人综合视频在线| 日韩午夜免费视频| 亚洲欧洲日产国码无码久久99| 午夜在线精品| 日韩中文字幕亚洲精品欧美| 欧美日韩大片免费观看| 久久久国产精品一区二区三区| 亚洲区小说区图片区qvod按摩| 91精品国产综合久久香蕉的用户体验| 福利一区二区免费视频| 91久久久久久久久| 中文字幕一区日韩精品| 国产精品久久久久久久久久东京| 亚洲成人一区在线观看| 午夜精品三级视频福利| 樱花草涩涩www在线播放| 国产精品444| 91亚洲精品在看在线观看高清| 97人人模人人爽人人喊38tv| 国产精东传媒成人av电影| 久久亚洲午夜电影| 亚洲精品一区二区三区在线| 国产伦精品一区二区三区视频孕妇 | 精品国产精品| 国产91av视频在线观看| 国产尤物精品| 成人在线观看黄| 国产在线不卡一卡二卡三卡四卡| av天堂一区二区| 国产欧美一区二区三区网站| 男男一级淫片免费播放| 2021国产精品久久精品| 国产黄色片在线| 久久久久久久久久美女| 久久久99999| 亚洲第一搞黄网站| 天堂免费在线视频| 狠狠色噜噜狠狠狠狠97| 97超视频在线观看| 日韩成人在线视频网站| 青青青青在线| 欧美在线视频在线播放完整版免费观看| 国内欧美日韩| 精品一区二区三区免费毛片| jizz性欧美23| 色姑娘综合网| 精品91在线| 给我免费播放片在线观看| 欧美日韩中文| 久久久久久久少妇| 丁香六月综合激情| 91视频免费看片| 欧美日韩中文字幕| 性生活免费网站| 欧美精品一区在线观看| 亚洲产国偷v产偷v自拍涩爱| 国产一区二区三区视频免费| a√中文在线观看| 96sao精品视频在线观看| 欧美极品中文字幕| 亚洲精品蜜桃久久久久久| 国精产品一区一区三区mba桃花| 国产无色aaa| 国产精品99久久久久久有的能看| 亚洲天堂av一区二区三区| 久久网这里都是精品| 国产精品99re| 日韩欧美在线视频免费观看| 亚洲av无码片一区二区三区| 日韩色av导航| 全球最大av网站久久| 欧美大香线蕉线伊人久久| 亚洲久色影视| 国产超碰在线播放| 26uuu国产日韩综合| 欧美另类69xxxx| 一本到高清视频免费精品| 午夜性色福利视频| 国产香蕉一区二区三区在线视频| av2020不卡| 国产精品久久久久久久久婷婷| 亚洲女同另类| 午夜不卡福利视频| 国产精品狼人久久影院观看方式| 国产精品免费无遮挡无码永久视频| 精品视频www| 日韩av首页| 91青青草免费观看| 一二三区不卡| 下面一进一出好爽视频| 亚洲欧美日韩人成在线播放| 成人免费区一区二区三区| 欧美三级午夜理伦三级中视频| 国产三级电影在线| 国产精品久久久久久久久久久久| 欧美男同视频网| 亚洲 中文字幕 日韩 无码| 国产婷婷一区二区| 中文字幕欧美色图| 少妇激情综合网| 国产精品电影| 欧美精品v日韩精品v国产精品| 久久不射2019中文字幕| 西西444www无码大胆| 色婷婷av久久久久久久| youjizz在线播放| 92国产精品久久久久首页| 中文精品电影| 日本一区二区在线观看视频| 欧美日韩国内自拍| аⅴ资源新版在线天堂| 成人天堂噜噜噜| 欧美日韩天堂| 波多野结衣一本| 欧美日韩一区二区在线视频| 黄色av网站在线播放| 国产精品免费区二区三区观看| 免费在线观看成人av| 欧美巨胸大乳hitomi| 日韩欧美在线影院| 咪咪网在线视频| 亚洲人一区二区| 成人综合婷婷国产精品久久蜜臀| 国产精品乱子伦| 欧美va亚洲va在线观看蝴蝶网| 国产精选在线| 一级特黄录像免费播放全99| 大白屁股一区二区视频| 亚洲国产精品无码久久久| 日韩资源在线观看| 精品午夜电影| 国产九九热视频| 亚洲成在线观看| 自拍视频在线| 国产精品福利在线观看| 婷婷综合伊人| 人妻丰满熟妇av无码久久洗澡| 欧美视频中文字幕| 国产最新视频在线| 欧美一级在线亚洲天堂| 91麻豆国产自产在线观看亚洲| 中国极品少妇videossexhd| 欧美日韩国产一级二级| 国产免费拔擦拔擦8x高清在线人 |