久久精品国产精品国产精品污,男人扒开添女人下部免费视频,一级国产69式性姿势免费视频,夜鲁夜鲁很鲁在线视频 视频,欧美丰满少妇一区二区三区,国产偷国产偷亚洲高清人乐享,中文 在线 日韩 亚洲 欧美,熟妇人妻无乱码中文字幕真矢织江,一区二区三区人妻制服国产

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

swift 组件化_打造完备的iOS组件化方案:如何面向接口进行模块解耦?

發布時間:2025/3/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 swift 组件化_打造完备的iOS组件化方案:如何面向接口进行模块解耦? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者 | 黑超熊貓zuik,一個修行中的 iOS 開發,喜歡搞點別人沒搞過的東西,鉆研過逆向工程、VIPER 架構和組件化。

關于組件化的探討已經有不少了,在之前的文章?iOS VIPER架構實踐(三):面向接口的路由設計[1]?中,綜合比較了各種方案后,我傾向于使用面向接口的方式進行組件化。

這是一篇從代碼層面講解模塊解耦的文章,會全方位地展示如何實踐面向接口的思想,盡量全面地探討在模塊管理和解耦的過程中,需要考慮到的各種問題,并且給出實際的解決方案,以及對應的模塊管理開源工具:ZIKRouter[2]。你也可以根據本文的內容改造自己現有的方案,即使你的項目不進行組件化,也可以參考本文進行代碼解耦。
文章主要內容:

? 如何衡量模塊解耦的程度
? 對比不同方案的優劣
? 在編譯時進行靜態路由檢查,避免使用不存在的模塊
? 如何進行模塊解耦,包括模塊重用、模塊適配、模塊間通信、子模塊交互
? 模塊的接口和依賴管理
? 管理界面跳轉邏輯

什么是組件化

將模塊單獨抽離、分層,并制定模塊間通信的方式,從而實現解耦,以及適應團隊開發。

為什么需要組件化

主要有4個原因:

? 模塊間解耦
? 模塊重用
? 提高團隊協作開發效率
? 單元測試

當項目越來越大的時候,各個模塊之間如果是直接互相引用,就會產生許多耦合,導致接口濫用,當某天需要進行修改時,就會牽一發而動全身,難以維護。

問題主要體現在:

? 修改某個模塊的功能時,需要修改許多其他模塊的代碼,因為這個模塊被其他模塊引用

? 模塊對外的接口不明確,外部甚至會調用不應暴露的私有接口,修改時會耗費大量時間

? 修改的模塊涉及范圍較廣,很容易影響其他團隊成員的開發,產生代碼沖突

? 當需要抽離模塊到其他地方重用時,會發現耦合導致根本無法單獨抽離

? 模塊間的耦合導致接口和依賴混亂,難以編寫單元測試

所以需要減少模塊之間的耦合,用更規范的方式進行模塊間交互。這就是組件化,也可以叫做模塊化。

你的項目是否需要組件化

組件化也不是必須的,有些情況下并不需要組件化:

? 項目較小,模塊間交互簡單,耦合少

? 模塊沒有被多個外部模塊引用,只是一個單獨的小模塊

? 模塊不需要重用,代碼也很少被修改

? 團隊規模很小

? 不需要編寫單元測試

組件化也是有一定成本的,你需要花時間設計接口,分離代碼,所以并不是所有的模塊都需要組件化。

不過,當你發現這幾個跡象時,就需要考慮組件化了:

? 模塊邏輯復雜,多個模塊間頻繁互相引用

? 項目規模逐漸變大,修改代碼變得越來越困難

? 團隊人數變多,提交的代碼經常和其他成員沖突

? 項目編譯耗時較大

? 模塊的單元測試經常由于其他模塊的修改而失敗

組件化方案的8條指標

決定了要開始組件化之路后,就需要思考我們的目標了。一個組件化方案需要達到怎樣的效果呢?我在這里給出8個理想情況下的指標:

1) 模塊間沒有直接耦合,一個模塊內部的修改不會影響到另一個模塊

2) 模塊可以被單獨編譯

3) 模塊間能夠清晰地進行數據傳遞

4) 模塊可以隨時被另一個提供了相同功能的模塊替換

5) 模塊的對外接口容易查找和維護

6) 當模塊的接口改變時,使用此模塊的外部代碼能夠被高效地重構

7) 盡量用最少的修改和代碼,讓現有的項目實現模塊化

8) 支持 Objective-C 和 Swift,以及混編

前4條用于衡量一個模塊是否真正解耦,后4條用于衡量在項目實踐中的易用程度。最后一條必須支持 Swift,是因為 Swift 是一個必然的趨勢,如果你的方案不支持 Swift,說明這個方案在將來的某個時刻必定要改進改變,而到時候所有基于這個方案實現的模塊都會受到影響。

基于這8個指標,我們就能在一定程度上對我們的方案做出衡量了。

方案對比

現在主要有3種組件化方案:URL 路由、target-action、protocol 匹配。

接下來我們就比較一下這幾種組件化方案,看看它們各有什么優缺點。這部分在之前的文章中已經探討過,這里再重新比較一次,補充一些細節。必須要先說明的是,沒有一個完美的方案能滿足所有場景下的需求,需要根據每個項目的需求選擇最適合的方案。

URL 路由

目前 iOS 上絕大部分的路由工具都是基于 URL 匹配的,或者是根據命名約定,用 runtime 方法進行動態調用。

這些動態化的方案的優點是實現簡單,缺點是需要維護字符串表,或者依賴于命名約定,無法在編譯時暴露出所有問題,需要在運行時才能發現錯誤。
代碼示例:

// 注冊某個URL
[URLRouter registerURL:@"app://editor" handler:^(NSDictionary *userInfo) {
UIViewController *editorViewController = [[EditorViewController alloc] initWithParam:userInfo];
return editorViewController;
}];
// 調用路由
[URLRouter openURL:@"app://editor/?debug=true" completion:^(NSDictionary *info) {

}];

URL router 的優點:

? 極高的動態性,適合經常開展運營活動的 app,例如電商
? 方便地統一管理多平臺的路由規則
? 易于適配 URL Scheme

URL router 的缺點:

? 傳參方式有限,并且無法利用編譯器進行參數類型檢查,因此所有的參數都只能從字符串中轉換而來
? 只適用于界面模塊,不適用于通用模塊
? 不能使用 designated initializer 聲明必需參數
? 要讓 view controller 支持 url,需要為其新增初始化方法,因此需要對模塊做出修改
? 不支持 storyboard
? 無法明確聲明模塊提供的接口,只能依賴于接口文檔,重構時無法確保修改正確
? 依賴于字符串硬編碼,難以管理
? 無法保證所使用的模塊一定存在
? 解耦能力有限,url 的"注冊"、"實現"、"使用"必須用相同的字符規則,一旦任何一方做出修改都會導致其他方的代碼失效,并且重構難度大

字符串解耦的問題

如果用上面的8個指標來衡量,URL 路由只能滿足"支持模塊單獨編譯"、"支持 OC 和 Swift"兩條。它的解耦程度非常一般。

所有基于字符串的解耦方案其實都可以說是偽解耦,它們只是放棄了編譯依賴,但是當代碼變化之后,即便能夠編譯運行,邏輯仍然是錯誤的。

例如修改了模塊定義時的 URL:

// 注冊某個URL
[URLRouter registerURL:@"app://editorView" handler:^(NSDictionary *userInfo) {
...
}];

那么調用者的 URL 也必須修改,代碼仍然是有耦合的,只不過此時編譯器無法檢查而已。這會導致維護更加困難,一旦 URL 中的參數有了增減,或者決定替換為另一個模塊,參數命名有了變化,幾乎沒有高效的方式來重構代碼。可以使用宏定義來管理字符串,不過這要求所有模塊都使用同一個頭文件,并且也無法解決參數類型和數量變化的問題。

URL 路由適合用來做遠程模塊的網絡協議交互,而在管理本地模塊時,最大的甚至是唯一的優勢,就是適合經常跨多端運營活動的 app,因為可以由運營人員統一管理多平臺的路由規則。

代表框架

? routable-ios
? JLRoutes
? MGJRouter
? HHRouter

改進:避免字符串管理

改進 URL 路由的方式,就是避免使用字符串,通過接口管理模塊。

參數可以通過 protocol 直接傳遞,能夠利用編譯器檢查參數類型,并且在 ZIKRouter 中,能通過路由聲明和編譯檢查,保證所使用的模塊一定存在。在為模塊創建路由時,也無需修改模塊的代碼。

但是必須要承認的是,盡管 URL 路由缺點多多,但它在跨平臺路由管理上的確是最適合的方案。因此 ZIKRouter 也對 URL 路由做出了支持,在用 protocol 管理的同時,可以通過字符串匹配 router,也能和其他 URL router 框架對接。

Target-Action 方案

有一些模塊管理工具基于 Objective-C 的 runtime、category 特性動態獲取模塊。例如通過NSClassFromString獲取類并創建實例,通過performSelector: NSInvocation動態調用方法。

例如基于 target-action 模式的設計,大致是利用 category 為路由工具添加新接口,在接口中通過字符串獲取對應的類,再用 runtime 創建實例,動態調用實例的方法。

示例代碼:

// 模塊管理者,提供了動態調用 target-action 的基本功能
@interface Mediator : NSObject

+ (instancetype)sharedInstance;

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;

@end
// 在 category 中定義新接口
@interface Mediator (ModuleActions)
- (UIViewController *)Mediator_editorViewController;
@end

@implementation Mediator (ModuleActions)

- (UIViewController *)Mediator_editorViewController {
// 使用字符串硬編碼,通過 runtime 動態創建 Target_Editor,并調用 Action_viewController:
UIViewController *viewController = [self performTarget:@"Editor" action:@"viewController" params:@{@"key":@"value"}];
return viewController;
}

@end

// 調用者通過 Mediator 的接口調用模塊
UIViewController *editor = [[Mediator sharedInstance] Mediator_editorViewController];
// 模塊提供者提供 target-action 的調用方式
@interface Target_Editor : NSObject
- (UIViewController *)Action_viewController:(NSDictionary *)params;
@end

@implementation Target_Editor

- (UIViewController *)Action_viewController:(NSDictionary *)params {
// 參數通過字典傳遞,無法保證類型安全
EditorViewController *viewController = [[EditorViewController alloc] init];
viewController.valueLabel.text = params[@"key"];
return viewController;
}

@end

優點:

? 利用 category 可以明確聲明接口,進行編譯檢查
? 實現方式輕量

缺點:

? 需要在 mediator 和 target 中重新添加每一個接口,模塊化時代碼較為繁瑣

? 在 category 中仍然引入了字符串硬編碼,內部使用字典傳參,一定程度上也存在和 URL 路由相同的問題

? 無法保證所使用的模塊一定存在,target 模塊在修改后,使用者只有在運行時才能發現錯誤

? 過于依賴 runtime 特性,無法應用到純 Swift 上。在 Swift 中擴展 mediator 時,無法使用純 Swift 類型的參數

? 可能會創建過多的 target 類

使用 runtime 相關的接口調用任意類的任意方法,需要注意別被蘋果的審核誤傷。參考:Are performSelector and respondsToSelector banned by App Store?[3]

字典傳參的問題

字典傳參時無法保證參數的數量和類型,只能依賴調用約定,就和字符串傳參一樣,一旦某一方做出修改,另一方也必須修改。

相比于 URL 路由,target-action 通過 category 的接口把字符串管理的問題縮小到了 mediator 內部,不過并沒有完全消除,而且在其他方面仍然有很多改進空間。上面的8個指標中其實只能滿足第2個"支持模塊單獨編譯",另外在和接口相關的第3、5、6點上,比 URL 路由要有改善。

代表框架

? CTMediator

改進:避免字典傳參

Target-Action 方案最大的優點就是整個方案實現輕量,并且也一定程度上明確了模塊的接口。只是這些接口都需要通過 Target-Action 封裝一次,并且每個模塊都要創建一個 target 類,既然如此,直接用 protocol 進行接口管理會更加簡單。

ZIKRouter 避免使用 runtime 獲取和調用模塊,因此可以適配 OC 和 swift。同時,基于 protocol 匹配的方式,避免引入字符串硬編碼,能夠更好地管理模塊,也避免了字典傳參。

基于 protocol 匹配的方案

有一些模塊管理工具或者依賴注入工具,也實現了基于接口的管理方式。實現思路是將 protocol 和對應的類進行字典匹配,之后就可以用 protocol 獲取 class,再動態創建實例。

BeeHive 示例代碼:

// 注冊模塊 (protocol-class 匹配)
[[BeeHive shareInstance] registerService:@protocol(EditorViewProtocol) service:[EditorViewController class]];// 獲取模塊 (用 runtime 創建 EditorViewController 實例)
id editor = [[BeeHive shareInstance] createService:@protocol(EditorViewProtocol)];

優點:

? 利用接口調用,實現了參數傳遞時的類型安全

? 直接使用模塊的 protocol 接口,無需再重復封裝

缺點:

? 由框架來創建所有對象,創建方式有限,例如不支持外部傳入參數,再調用自定義初始化方法

? 用 OC runtime 創建對象,不支持 Swift

? 只做了 protocol 和 class 的匹配,不支持更復雜的創建方式和依賴注入

? 無法保證所使用的 protocol 一定存在對應的模塊,也無法直接判斷某個 protocol 是否能用于獲取模塊

相比直接 protocol-class 匹配的方式,protocol-block 的方式更加易用。例如 Swinject。

Swinject 示例代碼:

let container = Container()

// 注冊模塊
container.register(EditorViewProtocol.self) { _ in
return EditorViewController()
}
// 獲取模塊
let editor = container.resolve(EditorViewProtocol.self)!

代表框架

? BeeHive

? Swinject

改進:離散式管理

BeeHive 這種方式和 ZIKRouter 的思路類似,但是所有的模塊在注冊后,都是由 BeeHive 單例來創建,使用場景十分有限,例如不支持純 Swift 類型,不支持使用自定義初始化方法以及額外的依賴注入。

ZIKRouter 進行了進一步的改進,并不是直接對 protocol 和 class 進行匹配,而是將 protocol 和 router 子類或者 router 對象進行匹配,在 router 子類中再提供創建模塊的實例的方式。這時,模塊的創建職責就從 BeeHive 單例上轉到了每個單獨的 router 上,從集約型變成了離散型,擴展性進一步提升。

Protocol-Router 匹配方案

變成 protocol-router 匹配后,代碼將會變成這樣:

一個 router 父類提供基礎的方法:

class ZIKViewRouter: NSObject {
...
// 獲取模塊
public class func makeDestination -> Any? {
let router = self.init(with: ViewRouteConfig())
return router.destination(with: router.configuration)
}

// 讓子類重寫
public func destination(with configuration: ViewRouteConfig) -> Any? {
return nil
}
}

每個模塊各自編寫自己的 router 子類:

// editor 模塊的 router
class EditorViewRouter: ZIKViewRouter {
// 子類重寫,創建模塊
override func destination(with configuration: ViewRouteConfig) -> Any? {
let destination = EditorViewController()
return destination
}
}

把 protocol 和 router 類進行注冊綁定:

EditorViewRouter.register(RoutableView())

然后就可以用 protocol 獲取 router 類,再進一步獲取模塊:

// 獲取模塊的 router 類
let routerClass = Router.to(RoutableView())// 獲取 EditorViewProtocol 模塊let destination = routerClass?.makeDestination()

加了一層 router 中間層之后,解耦能力一下子就增強了:

? 可以在 router 上添加許多通用的擴展接口,例如創建模塊、依賴注入、界面跳轉、界面移除,甚至增加 URL 路由支持

? 在每個 router 子類中可以進行更詳細的依賴注入和自定義操作

? 可以自定義創建對象的方式,例如自定義初始化方法、工廠方法,在重構時可以直接搬運現有的創建代碼,無需在原來的類上增加或修改接口,減少模塊化過程中的工作量

? 可以讓多個 protocol 和同一個模塊進行匹配

? 可以讓模塊進行接口適配,允許外部做完適配后,為 router 添加新的 protocol,解決編譯依賴的問題

? 返回的對象只需符合 protocol,不再和某個單一的類綁定。因此可以根據條件,返回不同的對象,例如適配不同系統版本時,返回不同的控件,讓外部只關注接口

動態化的風險

大部分組件化方案都會帶來一個問題,就是減弱甚至拋棄編譯檢查,因為模塊已經變得高度動態化了。

當調用一個模塊時,怎么能保證這個模塊一定存在?直接引用類時,如果類不存在,編譯器會給出引用錯誤,但是動態組件就無法在靜態時檢查了。

例如 URL 地址變化了,但是代碼中的某些 URL 沒有及時更新;使用 protocol 獲取模塊時,protocol 并沒有注冊對應的模塊。這些問題都只能在運行時才能發現。

那么有沒有一種方式,可以讓模塊既高度解耦,又能在編譯時保證調用的模塊一定存在呢?

答案是 YES。

靜態路由檢查

ZIKRouter 最特別的功能,就是能夠保證所使用的 protocol 一定存在,在編譯階段就能防止使用不存在的模塊。這個功能可以讓你更安全、更簡單地管理所使用的路由接口,不必再用其他復雜的方式進行檢查和維護。

當使用了錯誤的 protocol 時,會產生編譯錯誤。

Swift 中使用未聲明的 protocol:

Objective-C 中使用未聲明的 protocol:

這個特性通過兩個機制來實現:

? 只有被聲明為可路由的 protocol 才能用于路由,否則會產生編譯錯誤

? 可路由的 protocol 必定有一個對應的模塊存在

下面就一步步講解,怎么在保持動態解耦特性的同時,實現一套完備的靜態類型檢查的機制。

路由聲明

怎么才能聲明一個 protocol 是可以用于路由的呢?

要實現第一個機制,關鍵就是要為 protocol 添加特殊的屬性或者類型,使用時,如果 protocol 不符合特定類型,就產生編譯錯誤。

原生 Xcode 并不支持這樣的靜態檢查,這時候就要考驗我們的創造力了。

Objective-C:protocol 繼承鏈

在 Objective-C 中,可以要求 protocol 必須繼承自某個特定的父 protocol,并且通過宏定義 + protocol 限定,對 protocol 的父 protocol 繼承鏈進行靜態檢查。

例如 ZIKRouter 中獲取 router 類的方法是這樣的:

@protocol ZIKViewRoutable
@end

@interface ZIKViewRouter()
@property (nonatomic, class, readonly) ZIKViewRouterType *(^toView)(Protocol *viewProtocol);@end

toView用類屬性的方式提供,以方便鏈式調用,這個 block 接收一個Protocol?*類型的 protocol,返回對應的 router 類。

Protocol?*表示這個 protocol 必須繼承自ZIKViewRoutable。普通 protocol 的類型是Protocol *,所以如果傳入@protocol(EditorViewProtocol)就會產生編譯警告。

而如果用宏定義再給 protocol 變量加上一個 protocol 限定,進行一次類型轉換,就可以利用編譯器檢查 protocol 的繼承鏈:

// 聲明時繼承自 ZIKViewRoutable
@protocol EditorViewProtocol @end// 宏定義,為 protocol 變量添加 protocol 限定
#define ZIKRoutable(RoutableProtocol) (Protocol*)@protocol(RoutableProtocol)// 用 protocol 獲取 router
ZIKViewRouter.toView(ZIKRoutable(EditorViewProtocol))

ZIKRoutable(EditorViewProtocol)展開后是(Protocol *)@protocol(EditorViewProtocol),類型為Protocol *。在 Objective-C 中Protocol *是Protocol *的子類型,編譯器將不會有警告。
但是當傳入的 protocol 沒有繼承自ZIKViewRoutable時,例如ZIKRoutable(UndeclaredProtocol)的類型是Protocol *,編譯器在檢查 protocol 的繼承鏈時,由于UndeclaredProtocol沒有繼承自ZIKViewRoutable,因此Protocol *不是Protocol *的子類型,編譯器會給出類型錯誤的警告。在Build Settings中可以把incompatible pointer types警告變成編譯錯誤。

最后,把ZIKViewRouter.toView(ZIKRoutable(EditorViewProtocol))用宏定義簡化一下,變成ZIKViewRouterToView(EditorViewProtocol),就能在獲取 router 的時候方便地靜態檢查 protocol 的類型了。

Swift:條件擴展

Swift 中不支持宏定義,也不能隨意進行類型轉換,因此需要換一種方式來進行編譯檢查。

可以用 struct 的泛型傳遞 protocol,然后用條件擴展為特定泛型的 struct 添加初始化方法,從而讓沒有聲明過的泛型類型不能直接創建 struct。

例如:

// 用 RoutableView 的泛型來傳遞 protocol
struct RoutableView<Protocol> {
// 禁止默認的初始化方法
@available(*, unavailable, message: "Protocol is not declared as routable")
public init() { }
}
// 泛型為 EditorViewProtocol 的擴展
extension RoutableView where Protocol == EditorViewProtocol {
// 允許初始化
init() { }
}
// 泛型為 EditorViewProtocol 時可以初始化
RoutableView()// 沒有聲明過的泛型無法初始化,會產生編譯錯誤
RoutableView()

此時 Xcode 還可以給出自動補全,列出所有聲明過的 protocol:

路由檢查

通過路由聲明,我們做到了在編譯時對所使用的 protocol 做出限制。下一步就是保證聲明過的 protocol 必定有對應的模塊,類似于程序在 link 階段,會檢查頭文件中聲明過的類必定有對應的實現。

這一步是無法直接在編譯階段實現的,不過可以參考 iOS 在啟動時檢查動態庫的方式,我們可以在啟動階段實現這個功能。

Objective-C: protocol 遍歷

在 app 以 DEBUG 模式啟動時,我們可以遍歷所有繼承自 ZIKViewRoutable 的 protocol,在注冊表中檢查是否有對應的 router,如果沒有,就給出斷言錯誤。

另外,還可以讓 router 同時注冊創建模塊時用到類:

EditorViewRouter.registerView(EditorViewController.self)

從而進一步檢查 router 中的 class 是否遵守對應的 protocol。這時整個類型檢查過程就完整了。

Swift: 符號遍歷

但是 Swift 中的 protocol 是靜態類型,并不能通過 OC runtime 直接遍歷。是不是就無法動態檢查了呢?其實只要發揮創造力,一樣能做到。

Swift 的泛型名會在符號名中體現出來。例如上面聲明的 init 方法:

// MyApp 中,泛型為 EditorViewProtocol 的擴展
extension RoutableView where Protocol == EditorViewProtocol {
// 允許初始化
init() { }
}

在還原符號后就是(extension in MyApp):ZRouter.RoutableView.init() -> ZRouter.RoutableView。

此時我們可以遍歷 app 的符號表,來查找 RoutableView 的所有擴展,從而提取出所有聲明過的 protocol 類型,再去檢查是否有對應的 router。

Swift Runtime 和 ABI

但是如果要進一步檢查 router 中的 class 是否遵守 router 中的 protocol,就會遇到問題了。在 Swift 中怎么檢查某個任意的 class 遵守某個 Swift protocol ?

Swift 中沒有直接提供class_conformsToProtocol這樣的函數,不過我們可以通過 Swift Runtime 提供的標準函數和 Swift ABI 中定義的內存結構,完成同樣的功能。

這部分的實現可以參考代碼:_swift_typeIsTargetType[4]。之后我會寫幾篇文章詳細講解 Swift ABI 的底層內容。

路由檢查這部分只在 DEBUG 模式下進行,因此可以放開折騰。

自動推斷返回值類型

還有最后一個問題,在 BeeHive 中使用[[BeeHive shareInstance] createService:@protocol(EditorViewProtocol)]獲取模塊時,返回值是一個id類型,使用者需要手動指定返回變量的類型,在 Swift 中更是需要手動類型轉換,而這一步是可能出錯的,并且編譯器無法檢查。要實現最完備的類型檢查,就不能忽視這個問題。

有沒有一種方式能讓返回值的類型和 protocol 的類型對應呢?OC 中的泛型在這時候就發揮作用了。

可以在 router 上聲明模塊的泛型:

@interface ZIKViewRouter<__covariant destination __covariant>RouteConfig: ZIKViewRouteConfiguration *> : NSObject@end

這里使用了兩個泛型參數 Destination 和 RouteConfig,分別表示此 router 所管理的模塊類型和路由 config 的類型。__covariant則表示這個泛型支持協變,也就是子類型可以和父類型一樣使用。

聲明了泛型參數后,我們可以在方法中的參數聲明中使用泛型:

@interface ZIKViewRouter<__covariant destination __covariant>RouteConfig: ZIKViewRouteConfiguration *> : NSObject
- (nullable Destination)makeDestination;
- (nullable Destination)destinationWithConfiguration:(RouteConfig)configuration;@end

此時在獲取 router 時,就可以把 protocol 的類型作為 router 的泛型參數:

#define ZIKRouterToView(ViewProtocol) [ZIKViewRouter,ZIKViewRouteConfiguration *> toView](ZIKRoutable(ViewProtocol))

使用ZIKRouterToView(EditorViewProtocol)獲取的 router 類型就是ZIKViewRouter,ZIKViewRouteConfiguration *>。在這個 router 上調用makeDestination時,返回值的類型就是id,從而實現了完整的類型傳遞。

而在 Swift 中,直接用函數泛型就能實現:

class Router {

static func to(_ routableView: RoutableView<Protocol>) -> ViewRouter<Protocol, ViewRouteConfig>?

}

使用Router.to(RoutableView())時,獲得的 router 類型就是ViewRouter?,在調用makeDestination時,返回值類型就是EditorViewProtocol,無需手動類型轉換。

如果你使用協議組合,還能同時指明多個類型:

typealias EditorViewProtocol = UIViewController & EditorViewInput

并且在 router 子類中重寫對應方法時,也能用泛型進一步確保類型正確:

class EditorViewRouter: ZIKViewRouter<EditorViewProtocol, ZIKViewRouteConfiguration> {

override func destination(with configuration: ZIKViewRouteConfiguration) -> EditorViewProtocol? {
// 函數重寫時,參數類型會和泛型一致,實現時能確保返回值的類型是正確的
return EditorViewController()
}

}

現在我們完成了一套完備的類型檢查機制,而且這套檢查同時支持 OC 和 Swift。

至此,一個基于接口的、類型安全的模塊管理工具就完成了。使用 makeDestination 創建模塊只是最基本的功能,我們可以在父類 router 中進行許多有用的功能擴展,例如依賴注入、界面跳轉、接口適配,來更好地進行面向接口的開發。

模塊解耦

那么在面向接口編程時,我們還需要哪些功能呢?在擴展之前,我們先來討論一下如何使用接口進行模塊解耦,首先從理論層面梳理,再把理論轉化為工具。

模塊分類

不同模塊對解耦的要求是不同的。模塊從層級上可以從低到高分類:

? 底層功能模塊,功能單一,有一定通用性,例如各種功能組件(日志、數據庫)。底層模塊的主要目的是復用

? 中間層的通用業務模塊,可以在不同項目中通用。會引用各種底層模塊,以及和其他業務模塊通信

? 中間層的特殊功能模塊,提供了獨特的功能,沒有通用性,可能會引用一些底層模塊,例如性能監控模塊。這種模塊可以被其他模塊直接引用,不用太多考慮模塊間解耦的問題

? 上層的專有業務模塊,屬于某個項目中獨有的業務。會引用各種底層模塊,以及和其他業務模塊通信,和中間層的差別就是上層的解耦要求沒有中間層那么高

什么是解耦

首先明確一下什么才是解耦,梳理這個問題能夠幫助我們明確目標。

解耦的目的基本上就是兩個:提高代碼的可維護性、模塊重用。指導思想就是面向對象的設計原則。

解耦也有不同的程度,從低到高,差不多可以分為3層:

1) 模塊間使用抽象接口交互,沒有直接類型耦合,一個模塊內部的修改不會影響到另一個模塊 (單一職責、依賴倒置)

2) 模塊可重用,可以被單獨編譯 (接口隔離、依賴倒置、控制反轉)

3) 模塊可以隨時被另一個提供了相同功能的模塊替換 (開閉原則、依賴倒置、控制反轉)

第一層:抽象接口,提取依賴關系

第一層解耦,是為了減少不同代碼間的依賴關系,讓代碼更容易維護。例如把類替換為 protocol,隔絕模塊的私有接口,把依賴關系最小化。

解耦的整個過程,就是梳理和管理依賴的過程。因此模塊的內聚性越高越好,外部依賴越少越好,這樣維護起來才更簡單。

如果模塊不需要重用,那在這一層基本上就夠了。

第二層:模塊重用,管理模塊間通信

第二層解耦,是把代碼單獨抽離,做到了模塊重用,可以交給不同的成員維護,對模塊間通信提出了更高的要求。模塊需要在接口中聲明外部依賴,去除對特定類型的耦合。

此時影響最大的地方就是模塊間通信的方式,有時候即便是能夠單獨編譯了,也不意味著解耦。例如 URL 路由,只是放棄了編譯檢查,耦合關系還是存在于 URL 字符串中,一方的 URL 改變,其他方的代碼邏輯就會出錯,所以邏輯上仍然是耦合的。因此所有基于某種隱式調用約定的方案(例如字符串匹配),都只是解除編譯檢查,而不是真正的解耦。

有人說使用 protocol 進行模塊間通信,會導致模塊和 protocol 耦合。這個觀點是錯誤的。protocol 恰恰是把模塊的依賴明確地提取出來,是一種更高效的方法。否則完全用隱式約定來進行通信,沒有編譯器的輔助,一旦模塊的接口名、參數類型、參數數量需要更新,將會非常難以維護。

而且,通過設計模式,是可以解除對特定 protocol 的依賴的,下文將會對此進行講解。

第三層:去除隱式約定

第三層解耦,模塊間做到了真正的解耦,只要兩個模塊提供了相同的功能,就可以無縫替換,并且調用方無需任何修改。被替換的模塊只需要提供相同功能的接口,通過適配器對接即可,沒有其他任何限制,不存在任何其他的隱式調用約定。

一般有這種解耦要求的,都是那些跨項目的通用模塊,而項目內專有的業務模塊則沒有這么高的要求。不過那些跨多端的模塊和遠程模塊無法做到這樣的解耦,因為跨多端時沒有統一的定義接口的方式,因此只能通過隱式約定或者網絡協議定義接口,例如 URL 路由。

總的來說,解耦的過程就是職責分離、依賴管理(依賴聲明和注入)、模塊通信 這三大部分。

模塊重用

要做到模塊重用,模塊需要盡量減少外部依賴,并且把依賴提取出來,體現到模塊的接口上,讓調用者主動注入。同時,把模塊的各種事件也提取出來,讓調用者進行處理。

這樣一來,模塊就只需要負責自身的邏輯,不需要關心調用者如何使用模塊。那些每個應用各自專有的應用層邏輯也就從模塊中分離出來了。

因此,要想做好模塊解耦,管理好依賴是非常重要的。而 protocol 接口就是管理依賴的最高效的方式。

依賴管理

依賴,就是模塊中用到的外部數據和外部模塊。接下來討論如何使用 protocol 管理依賴,并且演示如何用 router 實現。

依賴注入

先來復習一下依賴注入的概念。依賴注入和依賴查找是實現控制反轉思想的具體方式。

控制反轉是將對象依賴的獲取從主動變為被動,從對象內部直接引用并獲取依賴,變為由外部向對象提供對象所要求的依賴,把不屬于自己的職責移交出去,從而讓對象和其依賴解耦。此時控制流的主動權從內部轉移到了外部,因此稱為控制反轉。

依賴注入就是指外部向對象傳入依賴。

一個類 A 在接口中體現出內部需要用到的一些依賴(例如內部需要用到類B的實例),從而讓使用者從外部注入這些依賴,而不是在類內部直接引用依賴并創建類 B。依賴可以用 protocol 的方式聲明,這樣就可以使類 A 和所使用的依賴類 B 進行解耦。

分離模塊創建和配置

那么如何用 router 進行依賴注入呢?

模塊創建了實例后,經常還需要進行一些配置。模塊管理工具應該從設計上提供配置功能。

最簡單的方式,就是在destinationWithConfiguration:中創建 destination 時進行配置。但是我們還可以更進一步,把 destination 的創建和配置分離開。分離之后,router 就可以單獨提供配置功能,去配置那些不是由 router 創建的 destination,例如 storyboard 中創建的 view、各種接口回調中返回的實例對象。這樣就可以覆蓋更多現存的使用場景,減少代碼修改。

Prepare Destination

可以在 router 子類中的prepareDestination:configuration:中進行模塊配置,也就是依賴注入,而模塊的調用者無需關心這部分依賴是如何配置的:

// router 父類
class ZIKViewRouter<Destination, RouteConfig>: NSObject {
...
public class func makeDestination -> Destination? {
let router = self.init(with: ViewRouteConfig())
let destination = router.destination(with: router.configuration)
if let destination = destination {
// router 父類中調用模塊配置方法
router.prepareDestination(destination, configuration: router.configuration)
}
return destination
}

// 模塊創建,讓子類重寫
public func destination(with configuration: ViewRouteConfig) -> Destination? {
return nil
}
// 模塊配置,讓子類重寫
func prepareDestination(_ destination: Destination, configuration: RouteConfig) {

}
}

// editor 模塊的 router
class EditorViewRouter: ZIKViewRouter<EditorViewController, ViewRouteConfig> {

override func destination(with configuration: ViewRouteConfig) -> EditorViewController? {
let destination = EditorViewController()
return destination
}
// 配置模塊,注入靜態依賴
override func prepareDestination(_ destination: EditorViewController, configuration: ViewRouteConfig) {
// 注入 service 依賴
destination.storageService = Router.makeDestination(to: RoutableService<EditorStorageServiceInput>())
// 其他配置
destination.title = "默認標題"
}
}

此時調用者中如果有某些對象不是創建自 router的,就可以直接用對應的 router 進行配置,執行依賴注入:

var destination: EditorViewProtocol = ...
Router.to(RoutableView())?.prepare(destination: destination, configuring: { (config, _) in
})

獨立的配置功能在某些場景下是非常有用的,尤其是在重構現有代碼的時候。有一些系統接口的設計就是在接口中返回對象,但是這些對象是由系統自動創建的,而不是通過 router 創建的,因此需要通過 router 對其進行配置,例如 storyboard 中創建的 view controller。此時將 view controller 模塊化后,依然可以保持現有代碼,只需要調用一句prepareDestination:configuration:配置即可,模塊化的過程中就能讓代碼的修改最小化。

可選依賴:屬性注入和方法注入

當依賴是可選的,并不是創建對象所必需的,可以用屬性注入和方法注入。

屬性注入是指外部設置對象的屬性。方法注入是指外部調用對象的方法,從而傳入依賴。

protocol PersonType {
var wife: Person? { get set } // 可選的屬性依賴
func addChild(_ child: Person) -> Void // 可選的方法注入
}
protocol Child {
var parent: Person { get }
}

class Person: PersonType {
var wife: Person? = nil
var childs: Set<Child> = []
func addChild(_ child: Child) {
childs.insert(child)
}
}

在 router 里,可以注入一些默認的依賴:

class PersonRouter: ZIKServiceRouter<Person, PerformRouteConfig> {
...
override func destination(with configuration: PerformRouteConfig) -> Person? {
let person = Person()
return person
}

// 配置模塊,注入靜態依賴
override func prepareDestination(_ destination: Person, configuration: PerformRouteConfig) {
if destination.wife != nil {
return
}
//設置默認值
let wife: Person = ...
person.wife = wife
}
}

模塊間參數傳遞

在執行路由操作的同時,調用者也可以用PersonType動態地注入依賴,也就是向模塊傳參。

configuration 就是用來進行各種功能擴展的。Router 可以在 configuration 上提供prepareDestination,讓調用者設置,就能讓調用者配置 destination。

let wife: Person = ...
let child: Child = ...
let person = Router.makeDestination(to: RoutableService(), configuring: { (config, _) in// 獲取模塊的同時進行配置
config.prepareDestination = { destination in
destination.wife = wife
destination.addChild(child)
}
})

封裝一下就能變成更簡單的接口:

let wife: Person = ...
let child: Child = ...
let person = Router.makeDestination(to: RoutableService(), preparation: { destination in
destination.wife = wife
destination.addChild(child)
})

必需依賴:工廠方法

有一些參數是在 destination 類創建前就需要傳入的必需參數,例如初始化方法中的參數,就是必需依賴。

class Person: PersonType {
let name: String
// 初始化方法,需要必需參數
init(name: String) {
self.name = name
}
}

這些必需參數有時候是由調用者提供的。在 URL 路由中,這種"必需"特性就無法體現出來,而用接口的方式就能簡單地實現。

傳遞必需依賴需要用工廠模式,在工廠方法上聲明必需參數和模塊接口。

protocol PersonTypeFactory {
// 工廠方法,聲明了必需參數 name,返回 PersonType 類型的 destination
func makeDestinationWith(_ name: String) -> PersonType?
}

那么如何用 router 傳遞必需參數呢?

Router 的 configuration 可以用來進行自定義參數擴展。可以把必需參數保存到 configuration 上,或者更直接點,由 configuration 來提供工廠方法,然后使用工廠方法的 protocol 來獲取模塊:

// 通用 configuration,可以提供自定義工廠方法
class PersonModuleConfiguration: PerformRouteConfig, PersonTypeFactory {
// 工廠方法
public func makeDestinationWith(_ name: String) -> PersonType? {
self.makedDestination = Person(name: name)
return self.makedDestination
}
// 由工廠方法創建的 destination,提供給 router
public var makedDestination: Destination?
}

在 router 中使用自定義 configuration:

class PersonRouter: ZIKServiceRouter<Person, PersonModuleConfiguration> {
// 重寫 defaultRouteConfiguration,使用自定義 configuration
override class func defaultRouteConfiguration() -> PersonModuleConfiguration {
return PersonModuleConfiguration()
}

override func destination(with configuration: PersonModuleConfiguration) -> Person? {
// 使用工廠方法創建的 destination
return config.makedDestination
}
}

然后把PersonTypeFactory協議和 router 進行注冊:

PersonRouter.register(RoutableServiceModule())

就可以用PersonTypeFactory獲取模塊了:

let name: String = ...
Router.makeDestination(to: RoutableServiceModule(), configuring: { (config, _) in// config 遵守 PersonTypeFactory
config.makeDestinationWith(name)
})

用泛型代替 configuration 子類

如果你不需要在 configuration 上保存其他自定義參數,也不想創建過多的 configuration 子類,可以用一個通用的泛型類來實現子類重寫的效果。

泛型可以自定義參數類型,此時可以直接把工廠方法用 block 保存在 configuration 的屬性上。

// 通用 configuration,可以提供自定義工廠方法
class ServiceMakeableConfigurationConstructor>: PerformRouteConfig {
public var makeDestinationWith: Constructor
public var makedDestination: Destination?
}

在 router 中使用自定義 configuration:

class PersonRouter: ZIKServiceRouter<Person, PerformRouteConfig> {

// 重寫 defaultRouteConfiguration,使用自定義 configuration
override class func defaultRouteConfiguration() -> PerformRouteConfig {
let config = ServiceMakeableConfiguration<PersonType, (String) -> PersonType>({ _ in})
// 設置工廠方法,讓調用者使用
config.makeDestinationWith = { [unowned config] name in
config.makedDestination = Person(name: name)
return config.makedDestination
}
return config
}

override func destination(with configuration: PerformRouteConfig) -> Person? {
if let config = configuration as? ServiceMakeableConfiguration<PersonType, (String) -> PersonType> {
// 使用工廠方法創建的 destination
return config.makedDestination
}
return nil
}
}

// 讓對應泛型的 configuration 遵守 PersonTypeFactory
extension ServiceMakeableConfiguration: PersonTypeFactory where Destination == PersonType, Constructor == (String) -> PersonType {

}

避免接口污染

除了必需依賴,還有一些參數是不屬于 destination 類的,而是屬于模塊內其他組件的,也不能通過 destination 的接口來傳遞。例如 MVVM 和 VIPER 架構中,model 參數不能傳給 view,而是應該交給 view model 或者 interactor。此時可以使用相同的模式。

protocol EditorViewModuleInput {
// 工廠方法,聲明了參數 note,返回 EditorViewInput 類型的 destination
func makeDestinationWith(_ note: Note) -> EditorViewInput?
}
class EditorViewRouter: ZIKViewRouter<EditorViewInput, ViewRouteConfig> {

// 重寫 defaultRouteConfiguration,使用自定義 configuration
override class func defaultRouteConfiguration() -> ViewRouteConfig {
let config = ViewMakeableConfiguration<EditorViewInput, (Note) -> EditorViewInput>({ _ in})
// 設置工廠方法,讓調用者使用
config.makeDestinationWith = { [unowned config] note in
config.makedDestination = self.makeDestinationWith(note: note)
return config.makedDestination
}
return config
}

class func makeDestinationWith(note: Note) -> EditorViewInput {
let view = EditorViewController()
let presenter = EditorViewPresenter(view)
let interactor = EditorInteractor(Presenter)
// 把 model 傳遞給數據管理者,view 不接觸 model
interactor.note = note
return view
}

override func destination(with configuration: ViewRouteConfig) -> EditorViewInput? {
if let config = configuration as? ViewMakeableConfiguration<EditorViewInput, (Note) -> EditorViewInput> {
// 使用工廠方法創建的 destination
return config.makedDestination
}
return nil
}
}

就可以用EditorViewModuleInput獲取模塊了:

let note: Note = ...
Router.makeDestination(to: RoutableViewModule(), configuring: { (config, _) in// config 遵守 EditorViewModuleInput
config.makeDestinationWith(note)
})

依賴查找

當模塊的必需依賴很多時,如果把依賴都放在初始化接口中,就會出現一個非常長的方法。

除了讓模塊把依賴聲明在接口中,模塊內部也可以用模塊管理工具動態查找依賴,例如用 router 查找 protocol 對應的模塊。如果要使用這種模式,那么所有模塊都需要統一使用相同的模塊管理工具。

代碼如下:

class EditorViewController: UIViewController {
lazy var storageService: EditorStorageServiceInput {
return Router.makeDestination(to: RoutableService())!
}
}

循環依賴

使用依賴注入時,有些特殊情況需要處理,例如循環依賴的無限遞歸問題。
循環依賴是指兩個對象互相依賴。

在 router 內部動態注入依賴時,如果注入的依賴同時依賴于被注入的對象,則必須在 protocol 中聲明。

protocol Parent {
// Parent 依賴 Child
var child: Child { get set }
}

protocol Child {
// Child 依賴 Parent
var parent: Parent { get set }
}

class ParentObject: Parent {
var child: Child!
}

class ChildObject: Child {
var parent: Parent!
}
class ParentRouter: ZIKServiceRouter<ParentObject, PerformRouteConfig> {

override func destination(with configuration: PerformRouteConfig) -> ParentObject? {
return ParentObject()
}
override func prepareDestination(_ destination: ParentObject, configuration: PerformRouteConfig) {
guard destination.child == nil else {
return
}
// 只有在外部沒有設置 child 時,才去主動尋找依賴
let child = Router.makeDestination(to RoutableService<Child>(), preparation { child in
// 設置 child 的依賴,防止 child 內部再去尋找 parent 依賴,導致循環
child.parent = destination
})
destination.child = child
}
}

class ChildRouter: ZIKServiceRouter<ChildObject, PerformRouteConfig> {

override func destination(with configuration: PerformRouteConfig) -> ChildObject? {
return ChildObject()
}
override func prepareDestination(_ destination: ChildObject, configuration: PerformRouteConfig) {
guard destination.parent == nil else {
return
}
// 只有在外部沒有設置 parent 時,才去主動尋找依賴
let parent = Router.makeDestination(to RoutableService<Parent>(), preparation { parent in
// 設置 parent 的依賴,防止 parent 內部再去尋找 child 依賴,導致循環
parent.child = destination
})
destination.parent = parent
}
}

這樣就能避免循環依賴導致的無限遞歸問題。

模塊適配器

當使用 protocol 管理模塊時,protocol 必定會出現在多個模塊中。那么此時如何讓每個模塊單獨編譯呢?

一個方式是把 protocol 在每個用到的模塊里復制一份,而且無需修改 protocol 名,Xcode 不會報錯。

另一個方式是使用適配器模式,可以讓不同模塊使用各自不同的 protocol 和同一個模塊交互。

required protocol 和 provided protocol

你可以為同一個 router 注冊多個 protocol。

根據依賴關系,接口可以分為required protocol和provided protocol。模塊本身提供的接口是provided protocol,模塊的調用者需要使用的接口是required protocol。

required protocol是provided protocol的子集,調用者只需要聲明自己用到的那些接口,不必引入整個provided protocol,這樣可以讓模塊間的耦合進一步減少。

在 UML 的組件圖中,就很明確地表現出了這兩者的概念。下圖中的半圓就是Required Interface,框外的圓圈就是Provided Interface:

那么如何實施Required Interface和Provided Interface?從架構分層上看,所有的模塊都是依附于一個更上層的宿主 app 環境存在的,應該由使用這些模塊的宿主 app 在一個 adapter 里進行接口適配,從而使得調用者可以繼續在內部使用required protocol,adapter 負責把required protocol和修改后的provided protocol進行適配。整個過程模塊都無感知。

這時候,調用者中定義的required protocol就相當于是在聲明自己所依賴的外部模塊。

為provided模塊添加required protocol

模塊適配的工作全部由模塊的使用和裝配者 App Context 完成,最少時只需要兩行代碼。

例如,某個模塊需要展示一個登陸界面,而且這個登陸界面可以顯示一段自定義的提示語。

調用者模塊示例:

// 調用者中聲明的依賴接口,表明自身依賴一個登陸界面
protocol RequiredLoginViewInput {
var message: String? { get set } //顯示在登陸界面上的自定義提示語
}

// 調用者中調用 login 模塊
Router.makeDestination(to: RoutableView<RequiredLoginViewInput>(), preparation: {
destination.message = "請登錄"
})

實際登陸界面提供的接口則是ProvidedLoginViewInput:

// 實際登陸界面提供的接口
protocol ProvidedLoginViewInput {
var message: String? { get set }
}

適配的代碼由宿主 app 實現,讓登陸界面支持?

RequiredLoginViewInput:
// 讓模塊支持 required protocol,只需要添加一個 protocol 擴展即可
extension LoginViewController: RequiredLoginViewInput {
}

并且讓登陸界面的 router 也支持 RequiredLoginViewInput:

// 如果可以獲取到 router 類,可以直接為 router 添加 RequiredLoginViewInput
LoginViewRouter.register(RoutableView())
// 如果不能得到對應模塊的 router,可以用 adapter 進行轉發
ZIKViewRouteAdapter.register(adapter: RoutableView(), forAdaptee: RoutableView())

適配之后,RequiredLoginViewInput就能和ProvidedLoginViewInput一樣使用,獲取到同一個模塊了:

調用者模塊示例:

Router.makeDestination(to: RoutableView(), preparation: {
destination.message = "請登錄"
})// ProvidedLoginViewInput 和 RequiredLoginViewInput 能獲取到同一個 router
Router.makeDestination(to: RoutableView(), preparation: {
destination.message = "請登錄"
})

接口適配

有時候ProvidedLoginViewInput和RequiredLoginViewInput的接口名可能會稍有不同,此時需要用 category、extension、子類、proxy 類等方式進行接口適配。

protocol ProvidedLoginViewInput {
var notifyString: String? { get set } // 接口名不同
}

適配時需要進行接口轉發,讓登陸界面支持 RequiredLoginViewInput:

extension LoginViewController: RequiredLoginViewInput {
var message: String? {
get {
return notifyString
}
set {
notifyString = newValue
}
}
}

用中介者轉發接口

如果不能直接為模塊添加required protocol,比如 protocol 里的一些 delegate 需要兼容:

protocol RequiredLoginViewDelegate {
func didFinishLogin() -> Void
}
protocol RequiredLoginViewInput {
var message: String? { get set }
var delegate: RequiredLoginViewDelegate { get set }
}

而模塊里的 delegate 接口不一樣:

protocol ProvidedLoginViewDelegate {
func didLogin() -> Void
}
protocol ProvidedLoginViewInput {
var notifyString: String? { get set }
var delegate: ProvidedLoginViewDelegate { get set }
}

相同方法有不同參數類型時,可以用一個新的 router 代替真正的 router,在新的 router 里插入一個中介者,負責轉發接口:

class ReqiredLoginViewRouter: ProvidedLoginViewRouter {

override func destination(with configuration: ZIKViewRouteConfiguration) -> RequiredLoginViewInput? {
let realDestination: ProvidedLoginViewInput = super.destination(with configuration)
// proxy 負責把 RequiredLoginViewInput 轉發為 ProvidedLoginViewInput
let proxy: RequiredLoginViewInput = ProxyForDestination(realDestination)
return proxy
}
}

對于普通OC類,proxy 可以用 NSProxy 來實現。對于 UIKit 中的那些復雜的 UI 類,或者 Swift 類,可以用子類,然后在子類中重寫方法,進行模塊適配。

聲明式依賴

利用之前的靜態路由檢查機制,模塊只需要聲明 required 接口,就能保證對應的模塊必定存在。

模塊無需在自己的接口里聲明依賴,如果模塊需要新增依賴,只需要創建新的 required 接口即可,無需修改接口本身。這樣也能避免依賴變動導致的接口變化,減少接口維護的成本。

模塊提供默認的依賴配置

每次引入模塊,宿主 app 都需要寫一份適配代碼,雖然大多數情況下只有兩行,但是我們想盡量減少宿主 app 的維護職責。

此時,可以讓模塊提供一份默認的依賴,用宏定義包裹,繞過編譯檢查。

#if USE_DEFAULT_DEPENDENCY

import ProvidedLoginModule

public func registerDefaultDependency() {
ZIKViewRouteAdapter.register(adapter: RoutableView<RequiredLoginViewInput>(), forAdaptee: RoutableView<ProvidedLoginViewInput>())
}

extension ProvidedLoginViewController: RequiredLoginViewInput {

}

#endif

如果宿主 app 要使用默認依賴,就在.xcconfig里設置Preprocessor Macros,開啟宏定義:

GCC_PREPROCESSOR_DEFINITIONS = $(inherited) USE_DEFAULT_DEPENDENCY=1

如果是 Swift 模塊,需要在模塊的 target 里設置Active Compilation Conditions,添加編譯宏USE_DEFAULT_DEPENDENCY。

宿主 app 直接調用默認的適配代碼即可,不用再負責維護:

public func registerAdapters() {
// 注冊默認的依賴
registerDefaultDependency()
...
}

如果宿主 app 需要替換使用另一個 provided 模塊,可以關閉宏定義,再寫一份另外的適配代碼,即可替換依賴。

模塊化

區分了required protocol和provided protocol后,就可以實現真正的模塊化。在調用者聲明了所需要的required protocol后,被調用模塊就可以隨時被替換成另一個相同功能的模塊。

參考 demo 中的ZIKLoginModule示例模塊,登錄模塊依賴于一個彈窗模塊,而這個彈窗模塊在ZIKRouterDemo和ZIKRouterDemo-macOS中是不同的,而在切換彈窗模塊時,登錄模塊中的代碼不需要做任何改變。

使用 adapter 的規范

一般來說,并不需要立即把所有的 protocol 都分離為required protocol和provided protocol。調用模塊和目的模塊可以暫時共用 protocol,或者只是簡單地改個名字,讓required protocol作為provided protocol的子集,在第一次需要替換模塊的時候再用 category、extension、proxy、subclass 等技術進行接口適配。

接口適配也不能濫用,因為成本比較高,而且并非所有的接口都能適配,例如同步接口和異步接口就難以適配。

對于模塊間耦合的處理,有這么幾條建議:

? 如果依賴的是提供特定功能的模塊,沒有通用性,直接引用類即可

? 如果是依賴某些簡單的通用模塊(例如日志模塊),可以在模塊的接口上把依賴交給外部來設置,例如 block 的形式

? 大部分需要解耦的模塊都是需要重用的業務模塊,如果你的模塊不需要重用,并且也不需要分工開發,直接引用對應類即可

? 大部分情況下建議共用 protocol,或者讓required protocol作為provided protocol的子集,接口名保持一致

? 只有在你的業務模塊的確允許使用者使用不同的依賴模塊時,才進行多個接口間的適配。例如需要跨平臺的模塊,例如登錄界面模塊允許不同的 app 使用不同的登陸 service 模塊

通過required protocol和provided protocol,我們就實現了模塊間的完全解耦。

模塊間通信

模塊間通信有多種方式,解耦程度也各有不同。這里只討論接口交互的方式。

控制流 input 和 output

模塊的對外接口可以分為 input 和 output。兩者的區別主要是控制流的主動權歸屬不同。

Input 是由外部主動調用的接口,控制流的發起者在外部,例如外部調用 view 的 UI 修改接口。

Output 是模塊內部主動調用外部實現的接口,控制流的發起者在內部,需要外部實現 output 所要求的方法。例如輸出 UI 事件、事件回調、獲取外部的 dataSource。iOS 中常用的 delegate 模式,也是一種 output。

設置 input 和 output

模塊設計好 input 和 output,然后在模塊創建的時候,設置好模塊之間的 input 和 output 關系,即可配置好模塊間通信,同時充分解耦。

class NoteListViewController: UIViewController, EditorViewOutput {
func showEditor() {
let destination = Router.makeDestination(to: RoutableView<EditorViewInput>(), preparation: { [weak self] destination in
destination.output = self
})
present(destination, animated: true)
}
}

protocol EditorViewInput {
weak var output: EditorViewOutput? { get set }
}

子模塊

大部分方案都沒有討論子模塊存在的情況。如果使用了 MVVM 或者 VIPER 架構,此時一個 view controller 使用了 child view controller,那多個模塊的 view model 和 interactor 之間如何交互?子模塊由誰初始化、由誰管理?

有些方案是直接在父 view model 里創建和使用子 view model,但是這樣就導致了 view 的實現方式影響了view model 的實現,如果父 view 里替換使用了另一個子 view,那父 view model 里的代碼也需要修改。

子模塊的來源

子模塊的來源有:

? 父 view 引用了一個封裝好的子 view 控件,連帶著引入了子 view 的整個 MVVM 或者 VIPER 模塊

? View model 或者 interactor 里使用了一個 Service

通信方式

子 view 可能是一個 UIView,也可能是一個 Child UIViewController。因此子 view 有可能需要向外部請求數據,也可能獨立完成所有任務,不需要依賴父模塊。

如果子 view 可以獨立,那在子模塊里不會出現和父模塊交互的邏輯,只有把一些事件通過 output 傳遞出去的接口。這時只需要把子 view 的 input 接口封裝在父 view 的 input 接口里即可,父 view model / presenter / interactor 是不知道父 view 提供的這幾個接口是通過子 view 實現的。

如果父模塊需要調用子模塊的業務接口,或接收子模塊的數據或業務事件,并且不想影響 view 的接口,可以把子 view model / presenter / interactor 作為父 view model / presenter / interactor 的一個 service,在引入子模塊時,注入到父 view model / presenter / interactor,從而繞過 view 層。這樣子模塊和父模塊就能通過 service 的形式進行通信了,而這時,父模塊也不知道這個 service 是來自子模塊里的。

在這樣的設計下,子模塊和父模塊是不知道彼此的存在的,只是通過接口進行交互。好處是父 view 如果想要更換為另一個相同功能的子 view 控件,就只需要在父 view 里修改,不會影響其他的 view model / presenter / interactor。

父模塊:

class EditorViewController: UIViewController {
var viewModel: EditorViewModel!

func addTextView() {
let textViewController = Router.makeDestination(to: RoutableView<TextViewInput>()) { (destination) in
// 設置模塊間交互
// 原本父 view 是無法接觸到子模塊的 view model / presenter / interactor
// 此時子模塊是把這些內部組件作為業務 input 開放給了外部
self.viewModel.textService = destination.viewModel
destination.viewModel.output = self.viewModel
}

addChildViewController(textViewController)
view.addSubview(textViewController.view)
textViewController.didMove(toParentViewController: self)
}
}

子模塊:

protocol TextViewInput {
weak var output: TextViewModuleOutput? { get set }
var viewModel: TextViewModel { get }
}

class TextViewController: UIViewController, TextViewInput {
weak var output: TextViewModuleOutput?
var viewModel: TextViewModel!
}

Output 的適配

在使用 output 時,模塊適配會帶來一定麻煩。

例如這樣一對 required-provided protocol:

protocol RequiredEditorViewInput {
weak var output: RequiredEditorViewOutput? { get set }
}

protocol ProvidedEditorViewInput {
weak var output: ProvidedEditorViewOutput? { get set }
}

由于 output 的實現者不是固定的,因此無法讓所有的 output 類都同時適配RequiredEditorViewOutput和ProvidedEditorViewOutput。此時建議直接使用對應的 protocol,不使用 required-provided 模式。

如果你仍然想要使用 required-provided 模式,那就需要用工廠模式來傳遞 output ,在內部用 proxy 進行適配。

實際模塊的 router:

protocol ProvidedEditorViewModuleInput {
var makeDestinationWith(_ output: ProvidedEditorViewOutput?) -> ProvidedEditorViewInput? { get set }
}

class ProvidedEditorViewRouter: ZIKViewRouter<EditorViewController, ViewRouteConfig> {

override class func registerRoutableDestination() {
register(RoutableViewModule<ProvidedEditorViewModuleInput>())
}

override class func defaultRouteConfiguration() -> ViewRouteConfig {
let config = ViewMakeableConfiguration<ProvidedViewInput, (ProvidedEditorViewOutput?) -> ProvidedViewInput?>({ _ in})
config.makeDestinationWith = { [unowned config] output in
// 設置 output
let viewModel = EditorViewModel(output: output)
config.makedDestination = EditorViewController(viewModel: viewModel)
return config.makedDestination
}
return config
}

override func destination(with configuration: ViewRouteConfig) -> EditorViewController? {
if let config = configuration as? ViewMakeableConfiguration<ProvidedViewInput, (ProvidedEditorViewOutput?) {
return config.makedDestination
}
return nil
}
}

適配代碼:

protocol RequiredEditorViewModuleInput {
var makeDestinationWith(_ output: RequiredEditorViewOutput?) -> RequiredEditorViewInput? { get set }
}

// 用于適配的 required router
class RequiredEditorViewRouter: ProvidedEditorViewRouter {

override class func registerRoutableDestination() {
register(RoutableViewModule<RequiredEditorViewModuleInput>())
}

// 兼容 configuration
override class func defaultRouteConfiguration() -> PerformRouteConfig {
let config = super.defaultRouteConfiguration()
let makeDestinationWith = config.makeDestinationWith

config.makeDestinationWith = { requiredOutput in
// proxy 負責把 RequiredEditorViewOutput 轉為 ProvidedEditorViewOutput
let providedOutput = EditorOutputProxy(forwarding: requiredOutput)
return makeDestinationWith(providedOutput)
}
return config
}
}

class EditorOutputProxy: ProvidedEditorViewOutput {
let forwarding: RequiredEditorViewOutput
// 實現 ProvidedEditorViewOutput,轉發給 forwarding
}

可以看到,output 的適配有些繁瑣。因此除非你的模塊是通用模塊,有實際的解耦需求,否則直接使用 provided protocol 即可。

功能擴展

總結完使用接口進行模塊解耦和依賴管理的方法,我們可以進一步對 router 進行擴展了。上面使用 makeDestination 創建模塊是最基本的功能,使用 router 子類后,我們可以進行許多有用的功能擴展,這里給出一些示范。

自動注冊

編寫 router 代碼時,需要注冊 router 和 protocol 。在 OC 中可以在 +load 方法中注冊,但是 Swift 里已經不能使用 +load 方法,而且分散在 +load 中的注冊代碼也不好管理。BeeHive 中通過宏定義和__attribute((used, section("__DATA,""BeehiveServices"""))),把注冊信息添加到了 mach-O 中的自定義區域,然后在啟動時讀取并自動注冊,可惜這種方式在 Swift 中也無法使用了。

我們可以把注冊代碼寫在 router 的+registerRoutableDestination方法里,然后逐個調用每個 router 類的+registerRoutableDestination方法即可。還可以更進一步,用 runtime 技術遍歷 mach-O 中的__DATA,__objc_classlist區域的類列表,獲取所有的 router 類,自動調用所有的+registerRoutableDestination方法。

把注冊代碼統一管理之后,如果不想使用自動注冊,也能隨時切換為手動注冊。

// editor 模塊的 router
class EditorViewRouter: ZIKViewRouter {

override class func registerRoutableDestination() {
registerView(EditorViewController.self)
register(RoutableView())
}
}
復制代碼Objective-C Sample
@interface EditorViewRouter : ZIKViewRouter
@end
@implementation EditorViewRouter
+ (void)registerRoutableDestination {[self registerView:[EditorViewController class]];[self registerViewProtocol:ZIKRoutable(EditorViewProtocol)];
}@end

封裝界面跳轉

iOS 中模塊間耦合的原因之一,就是界面跳轉的邏輯是通過 UIViewController 進行的,跳轉功能被限制在了 view controller 上,導致數據流常常都繞不開 view 層。要想更好地管理跳轉邏輯,就需要進行封裝。

封裝界面跳轉可以屏蔽 UIKit 的細節,此時界面跳轉的代碼就可以放在非 view 層(例如 presenter、view model、interactor、service),并且能夠跨平臺,也能輕易地通過配置切換跳轉方式。

如果是普通的模塊,就用ZIKServiceRouter,而如果是界面模塊,例如 UIViewController 和 UIView,就可以用ZIKViewRouter,在其中封裝了界面跳轉功能。

封裝界面跳轉后,使用方式如下:

class TestViewController: UIViewController {

//直接跳轉到 editor 界面
func showEditor() {
Router.perform(to: RoutableView<EditorViewProtocol>(), path: .push(from: self))
}

//跳轉到 editor 界面,跳轉前用 protocol 配置界面
func prepareAndShowEditor() {
Router.perform(
to: RoutableView<EditorViewProtocol>(),
path: .push(from: self),
preparation: { destination in
// 跳轉前進行配置
// destination 自動推斷為 EditorViewProtocol
})
}
}

可以用 ViewRoutePath 一鍵切換不同的跳轉方式:

enum ViewRoutePath {
case push(from: UIViewController)
case presentModally(from: UIViewController)
case presentAsPopover(from: UIViewController, configure: ZIKViewRoutePopoverConfigure)
case performSegue(from: UIViewController, identifier: String, sender: Any?)
case show(from: UIViewController)
case showDetail(from: UIViewController)
case addAsChildViewController(from: UIViewController, addingChildViewHandler: (UIViewController, @escaping () -> Void) -> Void)
case addAsSubview(from: UIView)
case custom(from: ZIKViewRouteSource?)
case makeDestination
case extensible(path: ZIKViewRoutePath)
}

而且在界面跳轉后,還可以根據跳轉時的跳轉方式,一鍵回退界面,無需再手動區分 dismiss、pop 等各種情況:

class TestViewController: UIViewController {
var router: DestinationViewRouter<EditorViewProtocol>?

func showEditor() {
// 持有 router
router = Router.perform(to: RoutableView<EditorViewProtocol>(), path: .push(from: self))
}

// Router 會對 editor view controller 執行 pop 操作,移除界面
func removeEditor() {
guard let router = router, router.canRemove else {
return
}
router.removeRoute()
router = nil
}
}

自定義跳轉

有些界面的跳轉方式很特殊,例如 tabbar 上的界面,需要通過切換 tabbar item 來進行。也有的界面有自定義的跳轉動畫,此時可以在 router 子類中重寫對應方法,進行自定義跳轉。

class EditorViewRouter: ZIKViewRouter<EditorViewController, ViewRouteConfig> {

override func destination(with configuration: ViewRouteConfig) -> Any? {
return EditorViewController()
}

override func canPerformCustomRoute() -> Bool {
return true
}

override func performCustomRoute(onDestination destination: EditorViewController, fromSource source: Any?, configuration: ViewRouteConfig) {
beginPerformRoute()
// 自定義跳轉
CustomAnimator.transition(from: source, to: destination) {
self.endPerformRouteWithSuccess()
}
}

override func canRemoveCustomRoute() -> Bool {
return true
}

override func removeCustomRoute(onDestination destination: EditorViewController, fromSource source: Any?, removeConfiguration: ViewRemoveConfig, configuration: ViewRouteConfig) {
beginRemoveRoute(fromSource: source)
// 移除自定義跳轉
CustomAnimator.dismiss(destination) {
self.endRemoveRouteWithSuccess(onDestination: destination, fromSource: source)
}
}

override class func supportedRouteTypes() -> ZIKViewRouteTypeMask {
return [.custom, .viewControllerDefault]
}
}

支持 storyboard

很多項目使用了 storyboard,在進行模塊化時,肯定不能要求所有使用 storyboard 的模塊都改為使用代碼。因此我們可以 hook 一些 storyboard 相關的方法,例如-prepareSegue:sender:,在其中調用prepareDestination:configuring:即可。

URL 路由

雖然之前列出了 URL 路由的許多缺點,但是如果你的模塊需要從 h5 界面調用,例如電商 app 需要實現跨平臺的動態路由規則,那么 URL 路由就是最佳的方案。

但是我們并不想為了實現 URL 路由,使用另一套框架再重新封裝一次模塊。只需要在 router 上擴展 URL 路由的功能,即可同時用接口和 URL 管理模塊。

你可以給 router 注冊 url:

class EditorViewRouter: ZIKViewRouter<EditorViewProtocol, ViewRouteConfig> {
override class func registerRoutableDestination() {
// 注冊 url
registerURLPattern("app://editor/:title")
}
}

之后就可以用相應的 url 獲取 router:

ZIKAnyViewRouter.performURL("app://editor/test_note", path: .push(from: self))

以及處理 URL Scheme:

public func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
let urlString = url.absoluteString
if let _ = ZIKAnyViewRouter.performURL(urlString, fromSource: self.rootViewController) {
return true
} else if let _ = ZIKAnyServiceRouter.performURL(urlString) {
return true
}
return false
}

每個 router 子類還能各自對 url 進行進一步處理,例如處理 url 中的參數、通過 url 執行對應方法、執行路由后發送返回值給調用者等。

每個項目對 URL 路由的需求都不一樣,基于 ZIKRouter 強大的可擴展性,你也可以按照項目需求實現自己的 URL 路由規則。

用 router 對象代替 router 子類

除了創建 router 子類,也可以使用通用的 router 實例對象,在每個對象的 block 屬性中提供和 router 子類一樣的功能,因此不必擔心類過多的問題。原理就和用泛型 configuration 代替 configuration 子類一樣。

ZIKViewRoute 對象通過 block 屬性實現子類重寫的效果,代碼可以用鏈式調用:

ZIKViewRoute
.make(withDestination: EditorViewController.self, makeDestination: ({ (config, router) -> EditorViewController? inreturn EditorViewController()
}))
.prepareDestination({ (destination, config, router) in
}).didFinishPrepareDestination({ (destination, config, router) in
})
.register(RoutableView())

簡化 router 實現

基于 ZIKViewRoute 對象實現的 router,可以進一步簡化 router 的實現代碼。

如果你的類很簡單,并不需要用到 router 子類,直接一行代碼注冊類即可:

ZIKAnyViewRouter.register(RoutableView(), forMakingView: EditorViewController.self)

或者用 block 自定義創建對象的方式:

ZIKAnyViewRouter.register(RoutableView(),forMakingView: EditorViewController.self) { (config, router) -> EditorViewProtocol? inreturn EditorViewController()
}

或者指定用 C 函數創建對象:

function makeEditorViewController(config: ViewRouteConfig) -> EditorViewController? {
return EditorViewController()
}

ZIKAnyViewRouter.register(RoutableView(),
forMakingView: EditorViewController.self, making: makeEditorViewController)

事件處理

有時候模塊需要處理一些系統事件或者 app 的自定義事件,此時可以讓 router 子類實現,再進行遍歷分發。

class SomeServiceRouter: ZIKServiceRouter {
@objc class func applicationDidEnterBackground(_ application: UIApplication) {
// handle applicationDidEnterBackground event
}
}
class AppDelegate: NSObject, NSApplicationDelegate {

func applicationDidEnterBackground(_ application: UIApplication) {

Router.enumerateAllViewRouters { (routerType) in
if routerType.responds(to: #selector(applicationDidEnterBackground(_:))) {
routerType.perform(#selector(applicationDidEnterBackground(_:)), with: application)
}
}
Router.enumerateAllServiceRouters { (routerType) in
if routerType.responds(to: #selector(applicationDidEnterBackground(_:))) {
routerType.perform(#selector(applicationDidEnterBackground(_:)), with: application)
}
}
}

}

單元測試

借助于使用接口管理依賴的方案,我們在對模塊進行單元測試時,可以自由配置 mock 依賴,而且無需 hook 模塊內部的代碼。

例如這樣一個依賴于網絡模塊的登陸模塊:

// 登錄模塊
class LoginService {

func login(account: String, password: String, completion: (Result) -> Void) {
// 內部使用 RequiredNetServiceInput 進行網絡訪問
let netService = Router.makeDestination(to: RoutableService<RequiredNetServiceInput
>())
let request = makeLoginRequest(account: account, password: password)
netService?.POST(request: request, completion: completion)
}
}

// 聲明依賴
extension RoutableService where Protocol == RequiredNetServiceInput {
init() {}
}

在編寫單元測試時,不需要引入真實的網絡模塊,可以提供一個自定義的 mock 網絡模塊:

class MockNetService: RequiredNetServiceInput {
func POST(request: Request, completion: (Result) {
completion(.success)
}
}
// 注冊 mock 依賴
ZIKAnyServiceRouter.register(RoutableService(),
forMakingService: MockNetService.self) { (config, router) -> EditorViewProtocol? inreturn MockNetService()
}

對于那些沒有接口交互的外部依賴,例如只是簡單的跳轉到對應界面,則只需注冊一個空白的 proxy。

單元測試代碼:

class LoginServiceTests: XCTestCase {

func testLoginSuccess() {
let expectation = expectation(description: "end login")

let loginService = LoginService()
loginService.login(account: "account", password: "pwd") { result in
expectation.fulfill()
}

waitForExpectations(timeout: 5, handler: { if let error = $0 {print(error)}})
}

}

使用接口管理依賴,可以更容易 mock,剝除外部依賴對測試的影響,讓單元測試更穩定。

接口版本管理

使用接口管理模塊時,還有一個問題需要注意。接口是會隨著模塊更新而變化的,這個接口已經被很多外部使用了,要如何減少接口變化產生的影響?

此時需要區分新接口和舊接口,區分版本,推出新接口的同時,保留舊接口,并將舊接口標記為廢棄。這樣使用者就可以暫時使用舊接口,漸進式地修改代碼。

這部分可以參考 Swift 和 OC 中的版本管理宏。

接口廢棄,可以暫時使用,建議盡快使用新接口代替:

// Swift
@available(iOS, deprecated: 8.0, message: "Use new interface instead")
// Objective-C
API_DEPRECATED_WITH_REPLACEMENT("performPath:configuring:", ios(7.0, 7.0));

接口已經無效:

// Swift
@available(iOS, unavailable)
// Objective-C
NS_UNAVAILABLE

最終形態

最后,一個 router 的最終形態就是下面這樣:

// editor 模塊的 router
class EditorViewRouter: ZIKViewRouter<EditorViewController, ViewRouteConfig> {

override class func registerRoutableDestination() {
registerView(EditorViewController.self)
register(RoutableView<EditorViewProtocol>())
registerURLPattern("app://editor/:title")
}

override func processUserInfo(_ userInfo: [AnyHashable : Any] = [:], from url: URL) {
let title = userInfo["title"]
// 處理 url 中的參數
}

// 子類重寫,創建模塊
override func destination(with configuration: ViewRouteConfig) -> Any? {
let destination = EditorViewController()
return destination
}

// 配置模塊,注入靜態依賴
override func prepareDestination(_ destination: EditorViewController, configuration: ViewRouteConfig) {
// 注入 service 依賴
destination.storageService = Router.makeDestination(to: RoutableService<EditorStorageServiceInput>())
// 其他配置
// 處理來自 url 的參數
if let title = configuration.userInfo["title"] as? String {
destination.title = title
} else {
destination.title = "默認標題"
}
}

// 事件處理
@objc class func applicationDidEnterBackground(_ application: UIApplication) {
// handle applicationDidEnterBackground event
}
}

基于接口進行解耦的優勢

我們可以看到基于接口管理模塊的優勢:

? 依賴編譯檢查,實現嚴格的類型安全

? 依賴編譯檢查,減少重構時的成本

? 通過接口明確聲明模塊所需的依賴,允許外部進行依賴注入

? 保持動態特性的同時,進行路由檢查,避免使用不存在的路由模塊

? 利用接口,區分 required protocol 和 provided protocol,進行明確的模塊適配,實現徹底解耦

回過頭看之前的 8 個解耦指標,ZIKRouter 已經完全滿足。而 router 提供的多種模塊管理方式(makeDestination、prepareDestination、依賴注入、頁面跳轉、storyboard 支持),能夠覆蓋大多數現有的場景,從而實現漸進式的模塊化,減輕重構現有代碼的成本。

參考

[1]https://juejin.im/post/59cb629c5188253cb5016322?
[2]https://github.com/Zuikyo/ZIKRouter?
[3]https://stackoverflow.com/questions/42662028/are-performselector-and-respondstoselector-banned-by-app-store?
[4]https://github.com/Zuikyo/ZIKRouter/blob/acb923bcdd09c65672977b5a20f7c527e459ead5/ZIKRouter/Utilities/ZIKRouterRuntimeDebug.h#L41


推薦閱讀??聊聊AppDelegate解耦??iOS 原生 App 是怎么 deselectRow 的??動手制作一個簡易的iOS動態執行器??iOS 流量監控分析

總結

以上是生活随笔為你收集整理的swift 组件化_打造完备的iOS组件化方案:如何面向接口进行模块解耦?的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

强伦人妻一区二区三区视频18 | 男女性色大片免费网站 | 久青草影院在线观看国产 | 国产在线精品一区二区高清不卡 | 日本肉体xxxx裸交 | 午夜性刺激在线视频免费 | 午夜男女很黄的视频 | 婷婷五月综合激情中文字幕 | 国产成人亚洲综合无码 | 丰满少妇熟乱xxxxx视频 | 亚洲一区二区三区偷拍女厕 | 国产艳妇av在线观看果冻传媒 | 在线欧美精品一区二区三区 | 亚洲成av人在线观看网址 | 思思久久99热只有频精品66 | 亚洲欧美精品aaaaaa片 | 久久午夜无码鲁丝片午夜精品 | 内射爽无广熟女亚洲 | 国产人妻久久精品二区三区老狼 | 亚洲狠狠婷婷综合久久 | 国产成人无码一二三区视频 | 无码人妻久久一区二区三区不卡 | 亚洲熟妇色xxxxx亚洲 | 中文字幕乱码中文乱码51精品 | 欧美国产日产一区二区 | 中文字幕乱码中文乱码51精品 | 亚洲综合久久一区二区 | а天堂中文在线官网 | 国产9 9在线 | 中文 | 暴力强奷在线播放无码 | 亚洲男人av香蕉爽爽爽爽 | 永久黄网站色视频免费直播 | 大屁股大乳丰满人妻 | 牲欲强的熟妇农村老妇女 | 亚洲人成影院在线无码按摩店 | 黑人大群体交免费视频 | 免费播放一区二区三区 | 色婷婷av一区二区三区之红樱桃 | 自拍偷自拍亚洲精品10p | 国产乱码精品一品二品 | 中文字幕 亚洲精品 第1页 | 精品乱码久久久久久久 | 国产av人人夜夜澡人人爽麻豆 | 欧美日韩精品 | 国产成人无码a区在线观看视频app | 未满成年国产在线观看 | 少妇愉情理伦片bd | 欧美日本免费一区二区三区 | 伦伦影院午夜理论片 | 三上悠亚人妻中文字幕在线 | 中文字幕av伊人av无码av | 国产一区二区三区四区五区加勒比 | 国产成人精品无码播放 | 久久久www成人免费毛片 | 日韩欧美中文字幕在线三区 | 国产一区二区三区精品视频 | 久激情内射婷内射蜜桃人妖 | 99久久精品午夜一区二区 | 欧美性黑人极品hd | 久久久久久av无码免费看大片 | 娇妻被黑人粗大高潮白浆 | 狠狠色丁香久久婷婷综合五月 | 东京热男人av天堂 | 久久五月精品中文字幕 | 久久久久久久人妻无码中文字幕爆 | 人妻中文无码久热丝袜 | 天天综合网天天综合色 | 扒开双腿吃奶呻吟做受视频 | 成人三级无码视频在线观看 | 十八禁视频网站在线观看 | 中文字幕无码av激情不卡 | 国产精品久久久久影院嫩草 | 亚洲最大成人网站 | 狠狠色丁香久久婷婷综合五月 | 亚洲男女内射在线播放 | 在线视频网站www色 | 丝袜 中出 制服 人妻 美腿 | 人妻aⅴ无码一区二区三区 | 久久亚洲国产成人精品性色 | 好男人www社区 | 国产卡一卡二卡三 | 欧美日本日韩 | 无码成人精品区在线观看 | 无码免费一区二区三区 | 爱做久久久久久 | 98国产精品综合一区二区三区 | 日韩人妻少妇一区二区三区 | 中文字幕av伊人av无码av | 曰本女人与公拘交酡免费视频 | 亚洲中文字幕成人无码 | 好男人社区资源 | 国产人妻久久精品二区三区老狼 | 色婷婷香蕉在线一区二区 | 波多野42部无码喷潮在线 | 国产高清av在线播放 | 色婷婷综合激情综在线播放 | 四虎永久在线精品免费网址 | 亚洲综合色区中文字幕 | 亚洲精品一区二区三区大桥未久 | 动漫av网站免费观看 | 无码福利日韩神码福利片 | 国产午夜福利100集发布 | 任你躁国产自任一区二区三区 | 日日碰狠狠躁久久躁蜜桃 | 久久久久亚洲精品中文字幕 | 无码av免费一区二区三区试看 | 国产真实伦对白全集 | 久久五月精品中文字幕 | 欧美午夜特黄aaaaaa片 | 亚洲va欧美va天堂v国产综合 | 午夜精品久久久内射近拍高清 | 99精品久久毛片a片 | 日韩视频 中文字幕 视频一区 | 色情久久久av熟女人妻网站 | 综合人妻久久一区二区精品 | 天天拍夜夜添久久精品大 | 红桃av一区二区三区在线无码av | 日本一区二区三区免费高清 | 黑人巨大精品欧美黑寡妇 | 岛国片人妻三上悠亚 | 狠狠色噜噜狠狠狠7777奇米 | 一区二区传媒有限公司 | 亚洲日本在线电影 | 激情内射亚州一区二区三区爱妻 | 丝袜人妻一区二区三区 | 粉嫩少妇内射浓精videos | 国产精品99久久精品爆乳 | 中文字幕色婷婷在线视频 | 成在人线av无码免观看麻豆 | ass日本丰满熟妇pics | 牛和人交xxxx欧美 | 精品国产福利一区二区 | 无套内谢的新婚少妇国语播放 | 99麻豆久久久国产精品免费 | 亚洲乱码中文字幕在线 | 97se亚洲精品一区 | 久久精品国产精品国产精品污 | 2020最新国产自产精品 | 人人妻人人澡人人爽人人精品浪潮 | 亚洲无人区一区二区三区 | 成人免费视频一区二区 | 丝袜 中出 制服 人妻 美腿 | 精品无码国产一区二区三区av | 人人超人人超碰超国产 | 国产片av国语在线观看 | 欧美日韩精品 | 熟妇女人妻丰满少妇中文字幕 | 亚洲欧美日韩成人高清在线一区 | а√资源新版在线天堂 | 露脸叫床粗话东北少妇 | 任你躁在线精品免费 | 国产一区二区三区日韩精品 | 亚洲精品国偷拍自产在线麻豆 | 亚洲精品久久久久avwww潮水 | 在线亚洲高清揄拍自拍一品区 | 亚洲中文字幕成人无码 | 国产乱人伦av在线无码 | 欧美丰满少妇xxxx性 | av在线亚洲欧洲日产一区二区 | 青草青草久热国产精品 | 亚洲综合在线一区二区三区 | 小泽玛莉亚一区二区视频在线 | 久久久中文字幕日本无吗 | 亚洲呦女专区 | 成人三级无码视频在线观看 | 国产精品高潮呻吟av久久4虎 | 无码福利日韩神码福利片 | 久久精品国产一区二区三区 | 免费看男女做好爽好硬视频 | 亚洲欧洲日本无在线码 | 丰满肥臀大屁股熟妇激情视频 | 国产真实夫妇视频 | 国产成人精品必看 | 成熟人妻av无码专区 | 日本高清一区免费中文视频 | 国产乱人无码伦av在线a | 色婷婷av一区二区三区之红樱桃 | www国产亚洲精品久久网站 | 少妇邻居内射在线 | 国产艳妇av在线观看果冻传媒 | 水蜜桃亚洲一二三四在线 | 无码播放一区二区三区 | 久久久精品456亚洲影院 | 成人综合网亚洲伊人 | 无码一区二区三区在线观看 | 国产午夜无码精品免费看 | 国产免费久久精品国产传媒 | 亚洲男人av天堂午夜在 | av无码电影一区二区三区 | 日本一本二本三区免费 | 日本一卡二卡不卡视频查询 | 在线а√天堂中文官网 | 久久综合九色综合97网 | 51国偷自产一区二区三区 | 大地资源中文第3页 | 在线观看欧美一区二区三区 | 精品无码一区二区三区爱欲 | 亚洲色大成网站www国产 | 成人无码精品一区二区三区 | 亚洲中文字幕无码中文字在线 | 日韩精品a片一区二区三区妖精 | 精品久久久无码人妻字幂 | www成人国产高清内射 | 日日天日日夜日日摸 | 88国产精品欧美一区二区三区 | 人妻少妇精品无码专区二区 | 国内综合精品午夜久久资源 | 亚洲综合在线一区二区三区 | 中文字幕无码免费久久9一区9 | 麻豆国产97在线 | 欧洲 | 精品久久久久久亚洲精品 | 精品亚洲韩国一区二区三区 | 97无码免费人妻超级碰碰夜夜 | 双乳奶水饱满少妇呻吟 | 精品国产乱码久久久久乱码 | 亚洲精品久久久久中文第一幕 | 又大又硬又爽免费视频 | www国产亚洲精品久久久日本 | 精品国产乱码久久久久乱码 | 国产成人无码区免费内射一片色欲 | 国产黄在线观看免费观看不卡 | av香港经典三级级 在线 | 老熟妇乱子伦牲交视频 | 国产精品自产拍在线观看 | 在线观看国产午夜福利片 | 成人欧美一区二区三区 | 精品国产成人一区二区三区 | 无码人妻久久一区二区三区不卡 | 国产无套内射久久久国产 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 扒开双腿吃奶呻吟做受视频 | 强伦人妻一区二区三区视频18 | 无码国产色欲xxxxx视频 | 夜精品a片一区二区三区无码白浆 | 内射白嫩少妇超碰 | 激情人妻另类人妻伦 | 超碰97人人射妻 | 欧美xxxxx精品 | 少妇久久久久久人妻无码 | 99er热精品视频 | 伊人久久大香线蕉av一区二区 | 欧美阿v高清资源不卡在线播放 | 日本精品少妇一区二区三区 | 少妇太爽了在线观看 | 国产人妻精品午夜福利免费 | 麻豆md0077饥渴少妇 | 少妇一晚三次一区二区三区 | 樱花草在线播放免费中文 | 在线观看国产一区二区三区 | 2019午夜福利不卡片在线 | 国内揄拍国内精品人妻 | 欧美性猛交内射兽交老熟妇 | 日韩av无码中文无码电影 | 欧美日本日韩 | 国产成人综合色在线观看网站 | 1000部啪啪未满十八勿入下载 | 免费人成网站视频在线观看 | 久久午夜夜伦鲁鲁片无码免费 | 亚洲中文字幕在线无码一区二区 | 偷窥村妇洗澡毛毛多 | 午夜福利一区二区三区在线观看 | 久久久精品国产sm最大网站 | 天堂无码人妻精品一区二区三区 | 国产舌乚八伦偷品w中 | 76少妇精品导航 | 亚洲精品成a人在线观看 | 狠狠亚洲超碰狼人久久 | 日日麻批免费40分钟无码 | 伊在人天堂亚洲香蕉精品区 | 98国产精品综合一区二区三区 | aⅴ亚洲 日韩 色 图网站 播放 | 亚洲成在人网站无码天堂 | 久久99精品久久久久婷婷 | 国产精品成人av在线观看 | 中文无码成人免费视频在线观看 | 欧美一区二区三区视频在线观看 | 日本大乳高潮视频在线观看 | 国产极品美女高潮无套在线观看 | 青青青手机频在线观看 | 偷窥日本少妇撒尿chinese | 欧美性猛交xxxx富婆 | 欧美精品一区二区精品久久 | 欧美日本精品一区二区三区 | 中文无码成人免费视频在线观看 | 黄网在线观看免费网站 | 国产精品国产自线拍免费软件 | 兔费看少妇性l交大片免费 | 狠狠噜狠狠狠狠丁香五月 | 伊人久久婷婷五月综合97色 | 久久久无码中文字幕久... | 窝窝午夜理论片影院 | 亚洲精品一区三区三区在线观看 | av无码电影一区二区三区 | 中文字幕中文有码在线 | 无码人妻少妇伦在线电影 | 未满成年国产在线观看 | 欧美熟妇另类久久久久久多毛 | 日韩精品a片一区二区三区妖精 | 日韩av无码一区二区三区不卡 | 亚洲人交乣女bbw | 精品久久久久久人妻无码中文字幕 | 日日碰狠狠丁香久燥 | 无码av免费一区二区三区试看 | 极品嫩模高潮叫床 | 97久久精品无码一区二区 | 国产三级精品三级男人的天堂 | 欧美xxxx黑人又粗又长 | 国产香蕉97碰碰久久人人 | 亚洲人成人无码网www国产 | 久久亚洲中文字幕无码 | 亚洲一区二区三区无码久久 | 久久久久久九九精品久 | 亚洲精品国偷拍自产在线观看蜜桃 | 国产成人精品优优av | 欧美国产日韩久久mv | 久久综合给合久久狠狠狠97色 | 夫妻免费无码v看片 | 九月婷婷人人澡人人添人人爽 | 中文精品无码中文字幕无码专区 | 无码免费一区二区三区 | 又大又紧又粉嫩18p少妇 | 中文字幕av无码一区二区三区电影 | 国产麻豆精品精东影业av网站 | 免费网站看v片在线18禁无码 | 国产精品对白交换视频 | 天堂亚洲2017在线观看 | 亚洲小说春色综合另类 | 国产人妻精品一区二区三区不卡 | 亚洲精品成人福利网站 | 天干天干啦夜天干天2017 | 日日夜夜撸啊撸 | 精品久久久无码人妻字幂 | 久久视频在线观看精品 | 精品国偷自产在线 | 欧美freesex黑人又粗又大 | 激情亚洲一区国产精品 | 久久精品国产大片免费观看 | 国产精品久久久久7777 | 国产在线aaa片一区二区99 | 亚洲区小说区激情区图片区 | 免费人成在线观看网站 | 国产精品人人爽人人做我的可爱 | 亚洲一区二区三区在线观看网站 | 欧美 日韩 亚洲 在线 | 欧美野外疯狂做受xxxx高潮 | 色偷偷av老熟女 久久精品人妻少妇一区二区三区 | 亚洲成色在线综合网站 | 特黄特色大片免费播放器图片 | 国产色xx群视频射精 | 久久人妻内射无码一区三区 | 亚洲人成影院在线观看 | 色一情一乱一伦 | 麻豆精品国产精华精华液好用吗 | 大地资源中文第3页 | 久激情内射婷内射蜜桃人妖 | 美女张开腿让人桶 | 欧美黑人巨大xxxxx | 亚洲 高清 成人 动漫 | 色欲综合久久中文字幕网 | 亚洲熟妇自偷自拍另类 | 人妻少妇精品无码专区二区 | 无码av免费一区二区三区试看 | 亚洲国产日韩a在线播放 | 狠狠色噜噜狠狠狠7777奇米 | 乱人伦中文视频在线观看 | 中文字幕乱码人妻无码久久 | 国产成人无码区免费内射一片色欲 | 99久久亚洲精品无码毛片 | √天堂资源地址中文在线 | 日本精品人妻无码77777 天堂一区人妻无码 | 十八禁真人啪啪免费网站 | 色诱久久久久综合网ywww | 国产精品丝袜黑色高跟鞋 | 久久精品中文闷骚内射 | 久久99精品国产麻豆 | 中文精品无码中文字幕无码专区 | 国产成人一区二区三区别 | 性啪啪chinese东北女人 | 无码人妻精品一区二区三区下载 | 伊人色综合久久天天小片 | 国产午夜亚洲精品不卡 | 久久精品无码一区二区三区 | 国产莉萝无码av在线播放 | 成 人影片 免费观看 | 西西人体www44rt大胆高清 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 无码人妻丰满熟妇区五十路百度 | 精品国产一区av天美传媒 | 大地资源中文第3页 | 一个人看的www免费视频在线观看 | 97色伦图片97综合影院 | 国产精品高潮呻吟av久久4虎 | 国产无套内射久久久国产 | 日日摸天天摸爽爽狠狠97 | 人妻少妇精品久久 | 亚洲成在人网站无码天堂 | 少妇性l交大片欧洲热妇乱xxx | 色狠狠av一区二区三区 | 精品亚洲韩国一区二区三区 | 熟妇人妻激情偷爽文 | 亚拍精品一区二区三区探花 | 18黄暴禁片在线观看 | 精品国产乱码久久久久乱码 | 久久久久久九九精品久 | 国内精品人妻无码久久久影院 | 无码av中文字幕免费放 | 99久久精品国产一区二区蜜芽 | 亚洲成熟女人毛毛耸耸多 | 亚洲一区二区三区四区 | 水蜜桃亚洲一二三四在线 | 久久久久久a亚洲欧洲av冫 | 狠狠综合久久久久综合网 | 激情内射日本一区二区三区 | 日日橹狠狠爱欧美视频 | 夫妻免费无码v看片 | 精品国产一区二区三区四区在线看 | 国产人成高清在线视频99最全资源 | 国产一区二区三区影院 | 亚洲男人av香蕉爽爽爽爽 | 一本久道久久综合婷婷五月 | 久久精品国产99精品亚洲 | 亚洲无人区午夜福利码高清完整版 | 天堂亚洲免费视频 | 夜夜高潮次次欢爽av女 | 2020久久香蕉国产线看观看 | 国产精品第一区揄拍无码 | 伊人色综合久久天天小片 | 久久人妻内射无码一区三区 | 老太婆性杂交欧美肥老太 | 77777熟女视频在线观看 а天堂中文在线官网 | 成年美女黄网站色大免费全看 | 久久视频在线观看精品 | 大色综合色综合网站 | 中文精品久久久久人妻不卡 | 欧美丰满熟妇xxxx性ppx人交 | 天天av天天av天天透 | 国产精品无码一区二区桃花视频 | 夜夜影院未满十八勿进 | 欧美亚洲日韩国产人成在线播放 | 激情人妻另类人妻伦 | 大乳丰满人妻中文字幕日本 | 伊人久久大香线蕉亚洲 | 亚洲欧美精品aaaaaa片 | 99视频精品全部免费免费观看 | 亚洲高清偷拍一区二区三区 | www一区二区www免费 | 亚洲熟妇色xxxxx欧美老妇 | 国产成人无码区免费内射一片色欲 | 无码国产乱人伦偷精品视频 | 成人亚洲精品久久久久软件 | 免费无码午夜福利片69 | 久久午夜无码鲁丝片 | 精品久久久无码中文字幕 | 人人妻人人澡人人爽欧美一区九九 | 精品无码一区二区三区的天堂 | 国产午夜手机精彩视频 | 乌克兰少妇xxxx做受 | 99精品无人区乱码1区2区3区 | 精品国产一区av天美传媒 | 丰满人妻一区二区三区免费视频 | 日本爽爽爽爽爽爽在线观看免 | 少妇无套内谢久久久久 | 一区二区三区高清视频一 | 内射爽无广熟女亚洲 | 曰本女人与公拘交酡免费视频 | 荫蒂添的好舒服视频囗交 | 国产精华av午夜在线观看 | 亚洲精品午夜无码电影网 | 天天摸天天透天天添 | 人妻少妇精品视频专区 | 亚洲欧美综合区丁香五月小说 | 亚洲乱码国产乱码精品精 | 色一情一乱一伦一视频免费看 | 国产精品欧美成人 | 成人欧美一区二区三区黑人免费 | 欧美激情内射喷水高潮 | 2019午夜福利不卡片在线 | 少妇人妻大乳在线视频 | 国产性生大片免费观看性 | 亚洲阿v天堂在线 | 成 人 网 站国产免费观看 | 精品国产aⅴ无码一区二区 | 亚无码乱人伦一区二区 | 人妻中文无码久热丝袜 | 国产亚洲人成在线播放 | 亚洲成av人综合在线观看 | 中文无码伦av中文字幕 | 扒开双腿吃奶呻吟做受视频 | 伦伦影院午夜理论片 | 无套内射视频囯产 | 日本熟妇乱子伦xxxx | 女高中生第一次破苞av | 99riav国产精品视频 | 国产色视频一区二区三区 | 好男人www社区 | 中文字幕乱码人妻无码久久 | 国产高清不卡无码视频 | 国内揄拍国内精品人妻 | а√资源新版在线天堂 | 久久精品女人的天堂av | 无码国模国产在线观看 | 日本免费一区二区三区最新 | 在线看片无码永久免费视频 | 国产色xx群视频射精 | 超碰97人人射妻 | 中文精品无码中文字幕无码专区 | 国产午夜视频在线观看 | 亚洲国产综合无码一区 | 亚洲乱码日产精品bd | 少妇被粗大的猛进出69影院 | 日本精品久久久久中文字幕 | 免费乱码人妻系列无码专区 | 久久人人爽人人爽人人片ⅴ | 国产高潮视频在线观看 | 色综合久久久无码中文字幕 | 人人澡人人透人人爽 | 伊人久久大香线蕉av一区二区 | 午夜丰满少妇性开放视频 | 国产精品va在线播放 | 国产精华av午夜在线观看 | 国产 精品 自在自线 | 欧美成人高清在线播放 | 国产成人精品视频ⅴa片软件竹菊 | 99久久99久久免费精品蜜桃 | 久久国产36精品色熟妇 | 中文字幕人妻无码一区二区三区 | 中文字幕 亚洲精品 第1页 | 熟女少妇人妻中文字幕 | 国产情侣作爱视频免费观看 | 日韩亚洲欧美中文高清在线 | 精品厕所偷拍各类美女tp嘘嘘 | 无码av岛国片在线播放 | 国语精品一区二区三区 | 又色又爽又黄的美女裸体网站 | 国产明星裸体无码xxxx视频 | 日韩成人一区二区三区在线观看 | 又大又硬又黄的免费视频 | 窝窝午夜理论片影院 | 成人精品视频一区二区三区尤物 | 国产乱码精品一品二品 | av无码电影一区二区三区 | 久久这里只有精品视频9 | 国产成人精品必看 | 国产明星裸体无码xxxx视频 | 无码人妻丰满熟妇区毛片18 | 久久久精品欧美一区二区免费 | 国产精品视频免费播放 | 日韩少妇白浆无码系列 | 亚洲熟熟妇xxxx | 人人澡人人妻人人爽人人蜜桃 | 欧美老人巨大xxxx做受 | 无码帝国www无码专区色综合 | 国产精品高潮呻吟av久久4虎 | 在线视频网站www色 | 欧美zoozzooz性欧美 | 少妇被黑人到高潮喷出白浆 | www成人国产高清内射 | 色综合视频一区二区三区 | 夜夜影院未满十八勿进 | 欧美人与物videos另类 | 中文字幕乱码人妻二区三区 | 性生交片免费无码看人 | 日韩精品无码免费一区二区三区 | 久久久av男人的天堂 | 欧美精品国产综合久久 | 欧美人与禽zoz0性伦交 | 日本精品人妻无码免费大全 | 2019午夜福利不卡片在线 | 久久精品一区二区三区四区 | 亚洲国精产品一二二线 | 国产免费久久久久久无码 | 国产乱人伦偷精品视频 | 国产精品久久久久久久影院 | 成人片黄网站色大片免费观看 | 国产成人综合美国十次 | 一本精品99久久精品77 | 精品国产一区av天美传媒 | 丰满岳乱妇在线观看中字无码 | 中文字幕av日韩精品一区二区 | 国产无套粉嫩白浆在线 | 日产国产精品亚洲系列 | 国产av久久久久精东av | 国产成人精品三级麻豆 | 初尝人妻少妇中文字幕 | 欧美日本免费一区二区三区 | 午夜福利不卡在线视频 | 内射爽无广熟女亚洲 | 水蜜桃亚洲一二三四在线 | 国产精品美女久久久久av爽李琼 | 国产国产精品人在线视 | 日本熟妇大屁股人妻 | 亚洲乱码中文字幕在线 | 天堂久久天堂av色综合 | 真人与拘做受免费视频一 | 性生交大片免费看l | 日韩精品a片一区二区三区妖精 | 久久久久久国产精品无码下载 | 日本大乳高潮视频在线观看 | 国产色视频一区二区三区 | 5858s亚洲色大成网站www | 亚洲人成网站在线播放942 | 纯爱无遮挡h肉动漫在线播放 | 国产乱人无码伦av在线a | 一本加勒比波多野结衣 | 成人aaa片一区国产精品 | 亚洲a无码综合a国产av中文 | 国产三级久久久精品麻豆三级 | 亚洲精品中文字幕久久久久 | 国产精品无码成人午夜电影 | 国产精品丝袜黑色高跟鞋 | 国产亚洲精品久久久久久 | 中国大陆精品视频xxxx | 亚洲大尺度无码无码专区 | 国产精品久久久久久亚洲影视内衣 | 伊人色综合久久天天小片 | 台湾无码一区二区 | 国产精品怡红院永久免费 | 欧洲精品码一区二区三区免费看 | 免费人成网站视频在线观看 | 国产精品久久久久久久影院 | 久久亚洲国产成人精品性色 | 熟女少妇人妻中文字幕 | 国产又爽又猛又粗的视频a片 | 欧美精品在线观看 | 亚洲精品欧美二区三区中文字幕 | 午夜免费福利小电影 | 亚洲国产午夜精品理论片 | aⅴ亚洲 日韩 色 图网站 播放 | 国产精品多人p群无码 | 蜜桃视频插满18在线观看 | 老子影院午夜精品无码 | 任你躁国产自任一区二区三区 | 全球成人中文在线 | 成人精品一区二区三区中文字幕 | 成人三级无码视频在线观看 | 国产成人无码av一区二区 | 国产精品人妻一区二区三区四 | 久久人妻内射无码一区三区 | 国语自产偷拍精品视频偷 | 亚洲成av人片天堂网无码】 | 成人精品视频一区二区 | 久久99精品国产麻豆蜜芽 | 高潮喷水的毛片 | 午夜不卡av免费 一本久久a久久精品vr综合 | 国产区女主播在线观看 | 国产香蕉尹人视频在线 | 欧美精品在线观看 | 性欧美videos高清精品 | 亚洲精品中文字幕久久久久 | 中文字幕亚洲情99在线 | 免费视频欧美无人区码 | 亚洲日韩精品欧美一区二区 | 强辱丰满人妻hd中文字幕 | 国产精品久久久午夜夜伦鲁鲁 | 小泽玛莉亚一区二区视频在线 | 久久综合九色综合97网 | 在线a亚洲视频播放在线观看 | 亚洲精品国产品国语在线观看 | 国产又爽又黄又刺激的视频 | 久久综合给久久狠狠97色 | 亚洲国产精品一区二区美利坚 | 日韩精品无码一区二区中文字幕 | 天堂一区人妻无码 | 成 人 免费观看网站 | 久久精品人人做人人综合试看 | 婷婷六月久久综合丁香 | 少妇性l交大片欧洲热妇乱xxx | 亚洲色欲久久久综合网东京热 | 亚洲色无码一区二区三区 | 久久99精品久久久久久 | 国产精品毛片一区二区 | 一本色道久久综合亚洲精品不卡 | √8天堂资源地址中文在线 | 我要看www免费看插插视频 | 国产精品99爱免费视频 | 成人aaa片一区国产精品 | 欧美怡红院免费全部视频 | 亚洲成av人片天堂网无码】 | 国产亚av手机在线观看 | 中文无码精品a∨在线观看不卡 | 精品夜夜澡人妻无码av蜜桃 | 人人澡人摸人人添 | 少妇人妻偷人精品无码视频 | 99久久精品国产一区二区蜜芽 | 51国偷自产一区二区三区 | 亚洲精品久久久久中文第一幕 | 亚洲大尺度无码无码专区 | 伊在人天堂亚洲香蕉精品区 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 少妇邻居内射在线 | 超碰97人人射妻 | 亚洲精品中文字幕久久久久 | 中文字幕无码热在线视频 | 亚洲成av人片在线观看无码不卡 | 久久精品国产大片免费观看 | 六十路熟妇乱子伦 | 欧美亚洲日韩国产人成在线播放 | 中文字幕人妻无码一区二区三区 | 亚洲a无码综合a国产av中文 | 亚洲综合无码一区二区三区 | 色欲综合久久中文字幕网 | 久久国产自偷自偷免费一区调 | 精品国产一区av天美传媒 | 国语自产偷拍精品视频偷 | 色妞www精品免费视频 | 人人妻人人澡人人爽精品欧美 | 精品久久久无码人妻字幂 | 中文精品无码中文字幕无码专区 | 久久精品人人做人人综合 | www国产亚洲精品久久网站 | 久久精品中文字幕一区 | 国产一精品一av一免费 | 亚洲中文无码av永久不收费 | 激情国产av做激情国产爱 | 欧洲精品码一区二区三区免费看 | 国产精品福利视频导航 | 青青青爽视频在线观看 | 伊在人天堂亚洲香蕉精品区 | 精品无码国产一区二区三区av | 人人妻人人澡人人爽人人精品浪潮 | 亚洲中文字幕成人无码 | 97精品国产97久久久久久免费 | 国产乱人无码伦av在线a | 日韩精品无码一本二本三本色 | 狠狠cao日日穞夜夜穞av | 亚洲国产午夜精品理论片 | 欧美人与禽猛交狂配 | 51国偷自产一区二区三区 | 日韩在线不卡免费视频一区 | 亚洲日韩乱码中文无码蜜桃臀网站 | 欧美成人免费全部网站 | 国产精品久久国产三级国 | 图片区 小说区 区 亚洲五月 | 国产在线aaa片一区二区99 | 女人高潮内射99精品 | 亚洲日韩av一区二区三区四区 | 国产精品亚洲а∨无码播放麻豆 | 少妇无套内谢久久久久 | 色爱情人网站 | 精品国产aⅴ无码一区二区 | av人摸人人人澡人人超碰下载 | 国产suv精品一区二区五 | 内射老妇bbwx0c0ck | 国产精品无码永久免费888 | 欧美人与牲动交xxxx | 亚洲乱亚洲乱妇50p | 日韩av无码一区二区三区不卡 | 欧美熟妇另类久久久久久不卡 | 内射后入在线观看一区 | www一区二区www免费 | 久久久国产一区二区三区 | 国产亚洲美女精品久久久2020 | 日本欧美一区二区三区乱码 | 搡女人真爽免费视频大全 | 黑人巨大精品欧美一区二区 | 性史性农村dvd毛片 | 九九在线中文字幕无码 | 久久综合九色综合97网 | 国产一精品一av一免费 | 久久精品国产一区二区三区 | 噜噜噜亚洲色成人网站 | 99久久精品国产一区二区蜜芽 | 久久亚洲中文字幕无码 | 中文字幕亚洲情99在线 | 中文字幕乱码中文乱码51精品 | 撕开奶罩揉吮奶头视频 | 男人扒开女人内裤强吻桶进去 | 久久久精品欧美一区二区免费 | 国产成人精品视频ⅴa片软件竹菊 | 国产97人人超碰caoprom | 久久99精品久久久久婷婷 | 色妞www精品免费视频 | 国产激情综合五月久久 | 性啪啪chinese东北女人 | 久久久久av无码免费网 | 日日摸夜夜摸狠狠摸婷婷 | 欧美放荡的少妇 | 女人被爽到呻吟gif动态图视看 | 扒开双腿疯狂进出爽爽爽视频 | 扒开双腿吃奶呻吟做受视频 | 亚洲综合精品香蕉久久网 | 天天拍夜夜添久久精品大 | 久久无码中文字幕免费影院蜜桃 | 日韩亚洲欧美中文高清在线 | 国产精品亚洲一区二区三区喷水 | 18禁止看的免费污网站 | 未满小14洗澡无码视频网站 | 日韩在线不卡免费视频一区 | 男人的天堂2018无码 | 强开小婷嫩苞又嫩又紧视频 | 日欧一片内射va在线影院 | 无码人妻精品一区二区三区不卡 | 人妻插b视频一区二区三区 | 色综合天天综合狠狠爱 | 亚洲日韩一区二区 | 亚洲成av人综合在线观看 | 麻豆国产97在线 | 欧洲 | 奇米影视7777久久精品人人爽 | 久久综合狠狠综合久久综合88 | 国产欧美亚洲精品a | 久久久久久a亚洲欧洲av冫 | 午夜精品久久久久久久久 | 婷婷色婷婷开心五月四房播播 | 国产性生交xxxxx无码 | 色婷婷香蕉在线一区二区 | 国产精品亚洲五月天高清 | 亚洲gv猛男gv无码男同 | 国产熟妇另类久久久久 | 内射爽无广熟女亚洲 | 色 综合 欧美 亚洲 国产 | 人人妻人人澡人人爽欧美精品 | 亚洲中文字幕在线观看 | 男女猛烈xx00免费视频试看 | 欧美 日韩 人妻 高清 中文 | 台湾无码一区二区 | 无码成人精品区在线观看 | 女高中生第一次破苞av | 久久婷婷五月综合色国产香蕉 | 欧美熟妇另类久久久久久多毛 | 精品亚洲成av人在线观看 | 日本xxxx色视频在线观看免费 | 国产精品内射视频免费 | 97精品人妻一区二区三区香蕉 | 国产激情综合五月久久 | 牲交欧美兽交欧美 | 老子影院午夜伦不卡 | 亚洲综合另类小说色区 | 国产精品igao视频网 | 骚片av蜜桃精品一区 | 中文毛片无遮挡高清免费 | 久久亚洲日韩精品一区二区三区 | 性做久久久久久久免费看 | 欧美丰满老熟妇xxxxx性 | 成人av无码一区二区三区 | 又大又黄又粗又爽的免费视频 | 国产精品国产自线拍免费软件 | 暴力强奷在线播放无码 | 久久综合九色综合欧美狠狠 | 国产亚洲美女精品久久久2020 | 国产两女互慰高潮视频在线观看 | 久久 国产 尿 小便 嘘嘘 | 两性色午夜免费视频 | 任你躁在线精品免费 | 成人欧美一区二区三区黑人免费 | 狂野欧美激情性xxxx | 精品无码成人片一区二区98 | 夜夜夜高潮夜夜爽夜夜爰爰 | 日本一本二本三区免费 | 特黄特色大片免费播放器图片 | 久久综合久久自在自线精品自 | 国产精品无码一区二区桃花视频 | 两性色午夜视频免费播放 | 国产人妻精品午夜福利免费 | 欧美日韩视频无码一区二区三 | 无码福利日韩神码福利片 | 国产乱码精品一品二品 | 久久97精品久久久久久久不卡 | 久久久国产精品无码免费专区 | 日韩无码专区 | 午夜精品久久久久久久久 | 国产精品丝袜黑色高跟鞋 | 亚洲综合精品香蕉久久网 | 久久国产精品萌白酱免费 | 最近中文2019字幕第二页 | 久久综合香蕉国产蜜臀av | 国产精品无码一区二区桃花视频 | 国产真实夫妇视频 | 日韩精品成人一区二区三区 | 精品国偷自产在线视频 | √天堂中文官网8在线 | 亚洲中文字幕成人无码 | 成人欧美一区二区三区黑人 | 免费观看又污又黄的网站 | 国产亚洲视频中文字幕97精品 | 波多野结衣乳巨码无在线观看 | аⅴ资源天堂资源库在线 | 国产精品理论片在线观看 | 亚洲色大成网站www国产 | 欧美zoozzooz性欧美 | 国产一区二区不卡老阿姨 | 亚洲精品鲁一鲁一区二区三区 | 中文字幕 亚洲精品 第1页 | 色婷婷久久一区二区三区麻豆 | 性欧美牲交xxxxx视频 | 国产97在线 | 亚洲 | 日日天日日夜日日摸 | 一本精品99久久精品77 | 久久国产劲爆∧v内射 | 国产99久久精品一区二区 | 精品国产一区二区三区av 性色 | 性生交片免费无码看人 | 国产又爽又猛又粗的视频a片 | 一本无码人妻在中文字幕免费 | 色综合久久久无码网中文 | 国产精品办公室沙发 | 亚洲一区二区三区 | 熟妇人妻无码xxx视频 | 久久无码专区国产精品s | 亚洲熟妇自偷自拍另类 | 日本精品少妇一区二区三区 | 桃花色综合影院 | 亚洲七七久久桃花影院 | 国产激情综合五月久久 | 国产精品内射视频免费 | 欧美xxxxx精品 | 亚洲一区二区三区偷拍女厕 | 亚洲第一无码av无码专区 | 国产莉萝无码av在线播放 | 国产超级va在线观看视频 | 麻豆国产人妻欲求不满谁演的 | 无码人妻少妇伦在线电影 | 日韩av激情在线观看 | 亚洲一区二区三区含羞草 | 99久久久无码国产aaa精品 | 国产成人精品必看 | 免费无码av一区二区 | 精品国产av色一区二区深夜久久 | 老子影院午夜精品无码 | 亚洲日韩av一区二区三区四区 | 国产网红无码精品视频 | 久久久久久九九精品久 | 4hu四虎永久在线观看 | 骚片av蜜桃精品一区 | 日日橹狠狠爱欧美视频 | 自拍偷自拍亚洲精品10p | 日日碰狠狠躁久久躁蜜桃 | 免费中文字幕日韩欧美 | 亚洲欧美精品aaaaaa片 | 亚洲自偷精品视频自拍 | 色综合视频一区二区三区 | 亚洲中文字幕在线无码一区二区 | 久久综合激激的五月天 | 樱花草在线社区www | 亚洲国产av美女网站 | 天堂а√在线中文在线 | 久久人人爽人人人人片 | 乱人伦人妻中文字幕无码久久网 | 天天综合网天天综合色 | 在线a亚洲视频播放在线观看 | 日韩无套无码精品 | 国产麻豆精品一区二区三区v视界 | 人人妻人人澡人人爽欧美一区九九 | 精品国产一区二区三区av 性色 | 成人影院yy111111在线观看 | 男女下面进入的视频免费午夜 | 午夜免费福利小电影 | 97无码免费人妻超级碰碰夜夜 | 老子影院午夜伦不卡 | 日韩少妇内射免费播放 | 欧美午夜特黄aaaaaa片 | 成人动漫在线观看 | 中文字幕 亚洲精品 第1页 | 女人被男人爽到呻吟的视频 | 青草青草久热国产精品 | 久久久久成人片免费观看蜜芽 | 成人欧美一区二区三区黑人 | 国产人妻精品一区二区三区不卡 | 亚洲男女内射在线播放 | 男女下面进入的视频免费午夜 | 亚洲色欲色欲天天天www | 99精品久久毛片a片 | 日日天日日夜日日摸 | 久久99精品国产麻豆 | 欧美freesex黑人又粗又大 | 男女爱爱好爽视频免费看 | 日日天日日夜日日摸 | 中文字幕无码人妻少妇免费 | 麻豆国产丝袜白领秘书在线观看 | 夜先锋av资源网站 | 国产人妻精品午夜福利免费 | 亚洲精品无码国产 | 99精品久久毛片a片 | 九九久久精品国产免费看小说 | 国产精品无码一区二区桃花视频 | 中文无码伦av中文字幕 | 中文亚洲成a人片在线观看 | 最新国产麻豆aⅴ精品无码 | 国产成人人人97超碰超爽8 | 娇妻被黑人粗大高潮白浆 | 天干天干啦夜天干天2017 | 国产精品美女久久久久av爽李琼 | 亚洲 日韩 欧美 成人 在线观看 | 思思久久99热只有频精品66 | 好男人社区资源 | 国产成人综合在线女婷五月99播放 | 激情五月综合色婷婷一区二区 | 午夜福利电影 | 午夜精品久久久内射近拍高清 | 欧美兽交xxxx×视频 | 国产亚洲视频中文字幕97精品 | 中文字幕av伊人av无码av | 国产精品va在线观看无码 | 久久精品国产日本波多野结衣 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 国产av人人夜夜澡人人爽麻豆 | 中文字幕无码乱人伦 | 国产亚洲精品久久久ai换 | 人妻少妇精品视频专区 | 国产亚洲精品久久久久久久久动漫 | 亚洲爆乳精品无码一区二区三区 | av无码不卡在线观看免费 | 最近的中文字幕在线看视频 | 国产人妻精品一区二区三区 | 女人被男人爽到呻吟的视频 | 久久99精品久久久久久 | 扒开双腿吃奶呻吟做受视频 | 久久久久99精品成人片 | √天堂资源地址中文在线 | 亚洲区欧美区综合区自拍区 | 无码乱肉视频免费大全合集 | 人人妻人人藻人人爽欧美一区 | 色综合视频一区二区三区 | 国产亚洲精品久久久久久 | 人妻少妇精品视频专区 | 夜夜影院未满十八勿进 | 久久精品中文闷骚内射 | 无码人中文字幕 | 狠狠躁日日躁夜夜躁2020 | 99久久人妻精品免费一区 | 亚洲国产精品无码一区二区三区 | 女人被爽到呻吟gif动态图视看 | 蜜桃视频插满18在线观看 | 日日躁夜夜躁狠狠躁 | 熟妇人妻激情偷爽文 | 男女爱爱好爽视频免费看 | 精品无码国产一区二区三区av | 亚洲一区二区三区国产精华液 | 77777熟女视频在线观看 а天堂中文在线官网 | 亚洲区欧美区综合区自拍区 | 免费无码一区二区三区蜜桃大 | 久久久久国色av免费观看性色 | 日韩人妻无码一区二区三区久久99 | 日韩成人一区二区三区在线观看 | 国产精品怡红院永久免费 | 免费网站看v片在线18禁无码 | 国产午夜亚洲精品不卡下载 | 亚洲成av人影院在线观看 | 少妇无码av无码专区在线观看 | 久久精品99久久香蕉国产色戒 | 日韩精品一区二区av在线 | 国产精品久免费的黄网站 | 精品成人av一区二区三区 | 久在线观看福利视频 | 水蜜桃色314在线观看 | 久久久久免费精品国产 | 国产女主播喷水视频在线观看 | 一本久久a久久精品vr综合 | 国产精品亚洲lv粉色 | 18无码粉嫩小泬无套在线观看 | 欧美xxxxx精品 | 国产亚洲精品久久久久久国模美 | 天海翼激烈高潮到腰振不止 | 国产精品久久福利网站 | 2019午夜福利不卡片在线 | 国产人成高清在线视频99最全资源 | 99久久久无码国产aaa精品 | 99久久婷婷国产综合精品青草免费 | 伊在人天堂亚洲香蕉精品区 | 久久久久久a亚洲欧洲av冫 | 亚洲人交乣女bbw | 亚洲性无码av中文字幕 | 亚洲精品一区二区三区婷婷月 | 欧洲vodafone精品性 | 日韩少妇白浆无码系列 | 国产超碰人人爽人人做人人添 | 国产精品第一区揄拍无码 | 鲁鲁鲁爽爽爽在线视频观看 | 国产精品久久久久久久9999 | 波多野42部无码喷潮在线 | 无码人妻少妇伦在线电影 | 亚洲中文字幕va福利 | 日韩av无码一区二区三区 | 亚洲欧美精品伊人久久 | 久久 国产 尿 小便 嘘嘘 | 荫蒂被男人添的好舒服爽免费视频 | 中文字幕人妻无码一区二区三区 | 国产午夜精品一区二区三区嫩草 | 亚洲色欲色欲欲www在线 | 影音先锋中文字幕无码 | 一本久久a久久精品vr综合 | 精品国产麻豆免费人成网站 | 久久99精品久久久久婷婷 | 伊人久久大香线蕉亚洲 | 色一情一乱一伦一视频免费看 | 999久久久国产精品消防器材 | 97精品人妻一区二区三区香蕉 | 国产精品久久久久9999小说 | 亚洲欧美日韩国产精品一区二区 | 国产偷抇久久精品a片69 | 欧美性黑人极品hd | 无码av最新清无码专区吞精 | 国产真人无遮挡作爱免费视频 | 国产亚洲欧美在线专区 | 欧美三级a做爰在线观看 | 野外少妇愉情中文字幕 | 无套内谢老熟女 | 色妞www精品免费视频 | 少妇人妻偷人精品无码视频 | 草草网站影院白丝内射 | 亚洲乱码中文字幕在线 | 在线成人www免费观看视频 | 青青青手机频在线观看 | 人人妻在人人 | 欧美老人巨大xxxx做受 | 久久久久久av无码免费看大片 | 999久久久国产精品消防器材 | 成人一在线视频日韩国产 | 日日天日日夜日日摸 | 性色欲网站人妻丰满中文久久不卡 | 无码一区二区三区在线 | 成人影院yy111111在线观看 | 精品亚洲成av人在线观看 | 日韩精品成人一区二区三区 | 亚洲一区二区三区四区 | 亚洲成av人综合在线观看 | 精品国产一区二区三区四区在线看 | 日日夜夜撸啊撸 | 国产免费久久精品国产传媒 | 亚洲国产欧美日韩精品一区二区三区 | 国产三级精品三级男人的天堂 | 国产成人亚洲综合无码 | 国产人妻大战黑人第1集 | 国产欧美精品一区二区三区 | 日本va欧美va欧美va精品 | 精品无码国产自产拍在线观看蜜 | 成人精品一区二区三区中文字幕 | 国产97人人超碰caoprom | 在线播放亚洲第一字幕 | 2019午夜福利不卡片在线 | 日产精品高潮呻吟av久久 | 日日摸夜夜摸狠狠摸婷婷 | 久久精品国产精品国产精品污 | 粗大的内捧猛烈进出视频 | 国产片av国语在线观看 | 18精品久久久无码午夜福利 | 亚拍精品一区二区三区探花 | 国产偷自视频区视频 | 国产亚洲精品精品国产亚洲综合 | 久久99精品久久久久婷婷 | 欧美日本精品一区二区三区 | 亚洲日韩一区二区三区 | 亚洲中文字幕无码一久久区 | 狠狠色欧美亚洲狠狠色www | 黑人粗大猛烈进出高潮视频 | 亚洲日韩中文字幕在线播放 | 日本乱人伦片中文三区 | 蜜臀av在线播放 久久综合激激的五月天 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 欧美xxxxx精品 | 久久99精品国产.久久久久 | 在线播放免费人成毛片乱码 | 国产成人综合色在线观看网站 | 久久久久国色av免费观看性色 | 日韩精品一区二区av在线 | 国产精品自产拍在线观看 | 亚洲色欲色欲天天天www | 欧美一区二区三区 | a片在线免费观看 | 久久亚洲精品中文字幕无男同 | 国内精品一区二区三区不卡 | 又粗又大又硬又长又爽 | 久久精品中文闷骚内射 | 97精品人妻一区二区三区香蕉 | 中国女人内谢69xxxxxa片 | 亚洲熟妇色xxxxx欧美老妇 | 天天爽夜夜爽夜夜爽 | 一本精品99久久精品77 | 伊人久久大香线焦av综合影院 | 人妻无码αv中文字幕久久琪琪布 | 性欧美大战久久久久久久 | 色一情一乱一伦一区二区三欧美 | 人人澡人人透人人爽 | 亚洲精品一区二区三区大桥未久 | 亚洲国产精品毛片av不卡在线 | 人妻aⅴ无码一区二区三区 | 乌克兰少妇性做爰 | 欧美性猛交内射兽交老熟妇 | 国产舌乚八伦偷品w中 | 久久www免费人成人片 | 亚洲欧美日韩综合久久久 | 一本一道久久综合久久 | 天堂亚洲2017在线观看 | 最新版天堂资源中文官网 | 1000部啪啪未满十八勿入下载 | 少妇一晚三次一区二区三区 | 激情爆乳一区二区三区 | 亚洲高清偷拍一区二区三区 | 激情五月综合色婷婷一区二区 | 久久无码中文字幕免费影院蜜桃 | 精品熟女少妇av免费观看 | av无码久久久久不卡免费网站 | 国产做国产爱免费视频 | 装睡被陌生人摸出水好爽 | 国内精品九九久久久精品 | 亚洲一区二区三区在线观看网站 | 国产av一区二区三区最新精品 | 欧美日韩久久久精品a片 | 久久精品国产大片免费观看 | 亚洲一区二区观看播放 | 男人和女人高潮免费网站 | 人妻少妇精品视频专区 | 亚洲欧洲日本无在线码 | 在线观看欧美一区二区三区 | 亚洲精品欧美二区三区中文字幕 | 99riav国产精品视频 | 日韩无码专区 | 女人被男人躁得好爽免费视频 | 国产精品久久久av久久久 | 无码精品国产va在线观看dvd | 少妇人妻偷人精品无码视频 | 嫩b人妻精品一区二区三区 | 激情人妻另类人妻伦 | 思思久久99热只有频精品66 | 中文字幕无码av波多野吉衣 | 欧美黑人巨大xxxxx | 东京热一精品无码av | a在线亚洲男人的天堂 | 久久人人爽人人爽人人片ⅴ | 久久久久亚洲精品男人的天堂 | 亚洲精品中文字幕久久久久 | 十八禁视频网站在线观看 | 国产欧美熟妇另类久久久 | 欧美日本精品一区二区三区 | 美女张开腿让人桶 | 午夜嘿嘿嘿影院 | 欧美黑人性暴力猛交喷水 | 国产电影无码午夜在线播放 | 精品无人国产偷自产在线 | 国产成人无码av在线影院 | 人人爽人人爽人人片av亚洲 | 国产亚洲精品久久久久久久 | 亚洲一区二区三区播放 | 97夜夜澡人人爽人人喊中国片 | 久久 国产 尿 小便 嘘嘘 | 蜜臀av在线播放 久久综合激激的五月天 | 自拍偷自拍亚洲精品10p | 蜜桃视频韩日免费播放 | 国产明星裸体无码xxxx视频 | 久久综合九色综合97网 | 性色av无码免费一区二区三区 | 久久97精品久久久久久久不卡 | 日日麻批免费40分钟无码 | 老头边吃奶边弄进去呻吟 | 日本大香伊一区二区三区 | 久久久无码中文字幕久... | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 99久久精品日本一区二区免费 | 色狠狠av一区二区三区 | 国产av无码专区亚洲awww | 亚洲综合在线一区二区三区 | 国产香蕉尹人综合在线观看 | 国产手机在线αⅴ片无码观看 | 久精品国产欧美亚洲色aⅴ大片 | 玩弄少妇高潮ⅹxxxyw | 天天摸天天透天天添 | 国产明星裸体无码xxxx视频 | 88国产精品欧美一区二区三区 | 中文字幕精品av一区二区五区 | 丰满人妻被黑人猛烈进入 | 欧美一区二区三区 | 成年女人永久免费看片 | 亚洲精品国产a久久久久久 | 国产明星裸体无码xxxx视频 | 国产精品丝袜黑色高跟鞋 | 亚洲成a人片在线观看无码 | 成人精品天堂一区二区三区 | 日日躁夜夜躁狠狠躁 | 久久久久免费看成人影片 | 大肉大捧一进一出好爽视频 | 久久这里只有精品视频9 | 国产精品无码永久免费888 | 国产成人综合色在线观看网站 | 日韩人妻无码一区二区三区久久99 | 久久精品无码一区二区三区 | 55夜色66夜色国产精品视频 | 精品国产aⅴ无码一区二区 | 色一情一乱一伦一视频免费看 | 亚洲区欧美区综合区自拍区 | 中文无码成人免费视频在线观看 | 久久99热只有频精品8 | 久久天天躁夜夜躁狠狠 | 久久精品国产大片免费观看 | 天海翼激烈高潮到腰振不止 | 国产精品嫩草久久久久 | 精品久久久无码人妻字幂 | 国产精品无码mv在线观看 | 亚洲精品成人福利网站 | 丁香花在线影院观看在线播放 | 内射老妇bbwx0c0ck | 少女韩国电视剧在线观看完整 | 日本肉体xxxx裸交 | 天干天干啦夜天干天2017 | 国产成人亚洲综合无码 | 狂野欧美激情性xxxx | 久久精品人人做人人综合 | 免费看少妇作爱视频 | 亚洲色在线无码国产精品不卡 | 日本又色又爽又黄的a片18禁 | 内射白嫩少妇超碰 | 人人妻人人澡人人爽人人精品 | 久久无码中文字幕免费影院蜜桃 | 国内精品一区二区三区不卡 | 久久97精品久久久久久久不卡 | аⅴ资源天堂资源库在线 | 国产亚洲欧美日韩亚洲中文色 | 亚洲中文字幕在线无码一区二区 | 日本丰满熟妇videos | 国产精品久久久久久亚洲毛片 | 精品乱子伦一区二区三区 | 男女下面进入的视频免费午夜 | 婷婷五月综合缴情在线视频 | 天堂亚洲2017在线观看 | 日本乱人伦片中文三区 | 18禁止看的免费污网站 | 国产综合色产在线精品 | 国产精品亚洲综合色区韩国 | 亚洲热妇无码av在线播放 | 欧美一区二区三区 | 蜜桃无码一区二区三区 | 亚洲中文字幕乱码av波多ji | 欧美一区二区三区视频在线观看 | 夜夜高潮次次欢爽av女 | 久久精品无码一区二区三区 | 久久99国产综合精品 | 日本丰满护士爆乳xxxx | 久久久精品456亚洲影院 | 5858s亚洲色大成网站www | 亚洲熟妇色xxxxx亚洲 | 国精产品一品二品国精品69xx | 国产麻豆精品一区二区三区v视界 | 国产精品嫩草久久久久 | 亚洲日韩av一区二区三区中文 | 欧美三级不卡在线观看 | 色欲久久久天天天综合网精品 | 国产激情一区二区三区 | 国产成人一区二区三区在线观看 | 中文字幕色婷婷在线视频 | 午夜不卡av免费 一本久久a久久精品vr综合 | 偷窥日本少妇撒尿chinese | 国产精品久久久久影院嫩草 | 天堂无码人妻精品一区二区三区 | 欧美国产日产一区二区 | 久久久久久亚洲精品a片成人 | 欧美激情综合亚洲一二区 | 激情内射日本一区二区三区 | 亚洲精品国偷拍自产在线观看蜜桃 | 一区二区三区乱码在线 | 欧洲 | 欧美三级a做爰在线观看 | 77777熟女视频在线观看 а天堂中文在线官网 | 乱码午夜-极国产极内射 | 亚洲成av人影院在线观看 | 丝袜人妻一区二区三区 | 一本久久伊人热热精品中文字幕 | 久久久久久av无码免费看大片 | 欧美野外疯狂做受xxxx高潮 | 无码精品人妻一区二区三区av | 久久综合久久自在自线精品自 | 男人扒开女人内裤强吻桶进去 | 亚洲狠狠婷婷综合久久 | 国产人妻久久精品二区三区老狼 | 久久国产精品_国产精品 | 欧洲精品码一区二区三区免费看 | 欧美激情内射喷水高潮 | 中文字幕无码av激情不卡 | 亚洲人成网站免费播放 | 国产热a欧美热a在线视频 | 国产精品久久久久久久影院 | 成人精品视频一区二区三区尤物 | 撕开奶罩揉吮奶头视频 | 我要看www免费看插插视频 | 亚洲色成人中文字幕网站 | 亚洲国产精品美女久久久久 | 夜夜影院未满十八勿进 | 无码人妻丰满熟妇区五十路百度 | av在线亚洲欧洲日产一区二区 | 亚洲中文字幕va福利 | 激情人妻另类人妻伦 | 成 人影片 免费观看 | 亚洲呦女专区 | 色欲久久久天天天综合网精品 | 131美女爱做视频 | 亚洲天堂2017无码中文 | 亚洲精品中文字幕 | 日日干夜夜干 | 中文字幕+乱码+中文字幕一区 | 亚洲精品国产品国语在线观看 | 人妻aⅴ无码一区二区三区 | 欧美野外疯狂做受xxxx高潮 | 亚洲国产成人av在线观看 | 亚洲精品久久久久中文第一幕 | 俄罗斯老熟妇色xxxx | 中文精品久久久久人妻不卡 | 少妇性荡欲午夜性开放视频剧场 | 色 综合 欧美 亚洲 国产 | 成人一在线视频日韩国产 | 午夜精品久久久久久久久 | 亚洲人成无码网www | 精品成人av一区二区三区 | 国内揄拍国内精品少妇国语 | 免费观看黄网站 | 国产精品久久久久久久影院 | 日本www一道久久久免费榴莲 | 国内精品人妻无码久久久影院 | 网友自拍区视频精品 | 色情久久久av熟女人妻网站 | 真人与拘做受免费视频一 | 少妇邻居内射在线 | 精品久久综合1区2区3区激情 | 无码人妻丰满熟妇区五十路百度 | 熟妇激情内射com | 欧美日韩久久久精品a片 | 久久久久se色偷偷亚洲精品av | 无遮挡国产高潮视频免费观看 | 啦啦啦www在线观看免费视频 | 男女爱爱好爽视频免费看 | 鲁鲁鲁爽爽爽在线视频观看 | 99久久精品日本一区二区免费 | 一本色道婷婷久久欧美 | 美女黄网站人色视频免费国产 | 国产成人综合色在线观看网站 | 丰满少妇弄高潮了www | 丰满人妻一区二区三区免费视频 | 成 人 免费观看网站 | 激情国产av做激情国产爱 | 无码人妻少妇伦在线电影 | 久久精品99久久香蕉国产色戒 | 国产精品毛片一区二区 | 麻花豆传媒剧国产免费mv在线 | 300部国产真实乱 | 国产精品成人av在线观看 | 无遮挡国产高潮视频免费观看 | 亚洲熟妇自偷自拍另类 | 精品国偷自产在线视频 | 久久久久国色av免费观看性色 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 亚洲爆乳精品无码一区二区三区 | 无码午夜成人1000部免费视频 | 日本大香伊一区二区三区 | 亚洲国产精品成人久久蜜臀 | 桃花色综合影院 | 极品尤物被啪到呻吟喷水 | 中文字幕av伊人av无码av | 天天做天天爱天天爽综合网 | 国产人成高清在线视频99最全资源 | 亚洲精品国偷拍自产在线麻豆 | 国产一区二区三区四区五区加勒比 | 成人一在线视频日韩国产 | 久久99久久99精品中文字幕 | 欧美三级不卡在线观看 | 午夜精品一区二区三区的区别 | 蜜桃视频插满18在线观看 | 人人妻人人澡人人爽精品欧美 | 国产亚洲精品精品国产亚洲综合 | aⅴ亚洲 日韩 色 图网站 播放 | 亚洲精品美女久久久久久久 | 国产精品亚洲一区二区三区喷水 | 樱花草在线社区www | 久久精品国产日本波多野结衣 | 丝袜足控一区二区三区 | 国产成人一区二区三区在线观看 | 国产内射爽爽大片视频社区在线 | 久久精品一区二区三区四区 | 久久久久人妻一区精品色欧美 | 久久国产精品精品国产色婷婷 | 日日躁夜夜躁狠狠躁 | 亚洲国产欧美在线成人 | 在线视频网站www色 | 免费中文字幕日韩欧美 | 性色欲情网站iwww九文堂 | 又大又硬又黄的免费视频 | 日日摸夜夜摸狠狠摸婷婷 | 丰满人妻翻云覆雨呻吟视频 | 国产精品99久久精品爆乳 | 伊人色综合久久天天小片 | 亚洲阿v天堂在线 | 人人爽人人爽人人片av亚洲 | 51国偷自产一区二区三区 | 国产精品久久久久久亚洲影视内衣 | 国产sm调教视频在线观看 | 国产区女主播在线观看 | 伊在人天堂亚洲香蕉精品区 | 97精品人妻一区二区三区香蕉 | 熟女俱乐部五十路六十路av | 国产国语老龄妇女a片 | 沈阳熟女露脸对白视频 | 国产精品久久久久久久影院 | 亚洲综合无码一区二区三区 | 亚洲国产欧美国产综合一区 | av在线亚洲欧洲日产一区二区 | 国产办公室秘书无码精品99 | 奇米综合四色77777久久 东京无码熟妇人妻av在线网址 | 无码毛片视频一区二区本码 | 一本久道久久综合婷婷五月 | 亚洲一区av无码专区在线观看 | 成人无码影片精品久久久 | 国产乱人无码伦av在线a | 亚洲一区av无码专区在线观看 | 一本久久a久久精品亚洲 | 中文字幕亚洲情99在线 | 亚洲国产av精品一区二区蜜芽 | 中文字幕人妻无码一区二区三区 | 色欲综合久久中文字幕网 | 色情久久久av熟女人妻网站 | 国产舌乚八伦偷品w中 | 国产另类ts人妖一区二区 | 特大黑人娇小亚洲女 | 欧美人妻一区二区三区 | 亚洲无人区午夜福利码高清完整版 | 成人片黄网站色大片免费观看 | 在线看片无码永久免费视频 | 亚洲一区二区三区四区 | 亚洲 高清 成人 动漫 | aa片在线观看视频在线播放 | 国产精品人妻一区二区三区四 | 天堂无码人妻精品一区二区三区 | 久在线观看福利视频 | 午夜熟女插插xx免费视频 | 青草视频在线播放 | 无人区乱码一区二区三区 | 国产在线精品一区二区高清不卡 | 99久久99久久免费精品蜜桃 | 综合激情五月综合激情五月激情1 | 无码人妻少妇伦在线电影 | 97久久超碰中文字幕 | 亚洲乱码国产乱码精品精 | 亚洲中文字幕无码中文字在线 | 麻花豆传媒剧国产免费mv在线 | 三上悠亚人妻中文字幕在线 | 国产麻豆精品一区二区三区v视界 | 亚洲理论电影在线观看 | 国产人妻久久精品二区三区老狼 | 妺妺窝人体色www婷婷 | 国产成人无码a区在线观看视频app | 久久人人爽人人爽人人片ⅴ | 成人试看120秒体验区 | 亚洲人亚洲人成电影网站色 | 中文字幕精品av一区二区五区 | 欧美高清在线精品一区 | 亲嘴扒胸摸屁股激烈网站 | 日韩亚洲欧美中文高清在线 | 亚洲精品欧美二区三区中文字幕 | 无码国产激情在线观看 | 国产电影无码午夜在线播放 | 亚洲精品午夜国产va久久成人 | 天天躁日日躁狠狠躁免费麻豆 | 久久午夜无码鲁丝片 | 天堂久久天堂av色综合 | 国产精品亚洲一区二区三区喷水 | 久久人人爽人人爽人人片ⅴ | 亚洲人成无码网www | 九一九色国产 | 色综合久久久久综合一本到桃花网 | 在线播放免费人成毛片乱码 | 少妇厨房愉情理9仑片视频 | 精品无码国产一区二区三区av | 无码帝国www无码专区色综合 | 亚洲中文字幕无码一久久区 | 一个人看的www免费视频在线观看 | 亚洲综合色区中文字幕 | 亚洲精品午夜国产va久久成人 | 成熟妇人a片免费看网站 | 99国产精品白浆在线观看免费 | 午夜精品一区二区三区在线观看 | 国产精品多人p群无码 | 成年美女黄网站色大免费视频 | 国产手机在线αⅴ片无码观看 | 亚洲国产欧美日韩精品一区二区三区 | 国产婷婷色一区二区三区在线 | 人人妻人人藻人人爽欧美一区 | 色情久久久av熟女人妻网站 | 永久免费观看国产裸体美女 | 精品乱子伦一区二区三区 | 九九久久精品国产免费看小说 | 精品国产青草久久久久福利 | 无遮无挡爽爽免费视频 | 鲁鲁鲁爽爽爽在线视频观看 | 国产精品无码成人午夜电影 | 乱中年女人伦av三区 | 亚洲精品国偷拍自产在线观看蜜桃 | 国产美女精品一区二区三区 | 国产亚洲日韩欧美另类第八页 | 日本一区二区更新不卡 | 久久综合久久自在自线精品自 | 乱中年女人伦av三区 | 久久亚洲中文字幕无码 | 国产一区二区三区精品视频 | 亚洲中文字幕无码一久久区 | 婷婷五月综合缴情在线视频 | 欧美丰满老熟妇xxxxx性 | 激情亚洲一区国产精品 | 国产精品人妻一区二区三区四 | 日本护士毛茸茸高潮 | 国产va免费精品观看 | 中文字幕乱码人妻无码久久 | 国产麻豆精品一区二区三区v视界 | 免费观看的无遮挡av | 中文字幕人妻无码一夲道 | 任你躁在线精品免费 | 秋霞成人午夜鲁丝一区二区三区 | 久久亚洲中文字幕精品一区 | 欧美zoozzooz性欧美 | 夜夜影院未满十八勿进 | 学生妹亚洲一区二区 | 超碰97人人射妻 | 国产综合色产在线精品 | 国产激情艳情在线看视频 | 国产乱码精品一品二品 | 装睡被陌生人摸出水好爽 | 欧美成人家庭影院 | 免费无码av一区二区 | 欧美zoozzooz性欧美 | 亚洲精品久久久久久久久久久 | 亚洲成色在线综合网站 | 久久 国产 尿 小便 嘘嘘 | 性生交大片免费看女人按摩摩 | 色婷婷综合中文久久一本 | 国产69精品久久久久app下载 | 无码国产乱人伦偷精品视频 | 日本大香伊一区二区三区 | 国精产品一品二品国精品69xx | 国产亚洲视频中文字幕97精品 | 久久久国产一区二区三区 | 2019nv天堂香蕉在线观看 | 久久久久se色偷偷亚洲精品av | 国产人妻久久精品二区三区老狼 | 久久99精品久久久久久 | 一本一道久久综合久久 | 少妇无码一区二区二三区 | 中文字幕乱码中文乱码51精品 | 国产高清av在线播放 | 亚洲成熟女人毛毛耸耸多 | 亚洲乱亚洲乱妇50p | 性色欲情网站iwww九文堂 | 色综合久久久无码网中文 | 无码成人精品区在线观看 | 久久无码人妻影院 | 久久精品中文字幕一区 | av小次郎收藏 | 少妇无码一区二区二三区 | 在线播放亚洲第一字幕 | 无码人妻黑人中文字幕 | 最近的中文字幕在线看视频 | 久久久av男人的天堂 | 国产精品久久久 | 俄罗斯老熟妇色xxxx | 久久综合狠狠综合久久综合88 | 欧美丰满老熟妇xxxxx性 | 六十路熟妇乱子伦 | 成人av无码一区二区三区 | 久久综合狠狠综合久久综合88 | 清纯唯美经典一区二区 | 午夜性刺激在线视频免费 | 黑人粗大猛烈进出高潮视频 | 67194成是人免费无码 | 久久久久久九九精品久 | 少妇高潮喷潮久久久影院 | 六月丁香婷婷色狠狠久久 | 国产精品第一国产精品 | 天天拍夜夜添久久精品大 | 俺去俺来也www色官网 | 欧美人与善在线com | 久久综合激激的五月天 | 性欧美videos高清精品 | 精品乱码久久久久久久 | 亚洲综合无码一区二区三区 | 色欲综合久久中文字幕网 | 黑人粗大猛烈进出高潮视频 | 国产成人午夜福利在线播放 | 久久久精品成人免费观看 | 日日摸天天摸爽爽狠狠97 | 亚洲综合在线一区二区三区 | 国产精品人妻一区二区三区四 | 少妇被粗大的猛进出69影院 | 色综合久久久久综合一本到桃花网 | 中文字幕无线码 | 人妻少妇被猛烈进入中文字幕 |