日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何使用NSOperations和NSOperationQueues

發(fā)布時間:2023/12/16 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何使用NSOperations和NSOperationQueues 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
Soheil Azarpour

第一部分


學(xué)習(xí)如何在你的app中使用NSOperations!

這篇博客是由iOS個人開發(fā)者Soheil Moayedi Azarpour發(fā)布的。

每個人都會在使用iOS或者M(jìn)ac app,點(diǎn)擊按鈕或者輸入文本時,有過讓人沮喪的經(jīng)歷,突然間,用戶交互界面停止了響應(yīng)。

你真幸運(yùn) – 你只能盯著沙漏或者旋轉(zhuǎn)的風(fēng)火輪一段時間直到能夠再次和UI界面交互為止!挺討厭的,不是嗎?

在一款移動端iOS程序中,用戶期望你的app可以即時地響應(yīng)他們的觸摸操作,然而當(dāng)它不響應(yīng)時,app就會讓人覺得反應(yīng)遲鈍,通常會導(dǎo)致不好的評價(jià)。

然而說的容易做就難。一旦你的app需要執(zhí)行多個任務(wù),事情很快就會變得復(fù)雜起來。在主運(yùn)行回路中并沒有很多時間去執(zhí)行繁重的工作,并且還有一直提供可響應(yīng)的UI界面。

可憐的開發(fā)者要怎么做呢?一種方法是通過并發(fā)操作將部分任務(wù)從主線程中撤離。并發(fā)操作意味著你的程序可以在操作中同時執(zhí)行多個流(或者線程)- 這樣,當(dāng)你執(zhí)行任務(wù)時,交互界面可以保持響應(yīng)。

一種在iOS中執(zhí)行并發(fā)操作的方法,是使用NSOperation和NSOperationQueue類。在本教程中,你將學(xué)習(xí)如何使用它們!你會先創(chuàng)建一款不使用多線程的app,這樣它會變得響應(yīng)非常遲鈍。然后改進(jìn)程序,添加上并行操作 – 并且希望 – 可以提供一個交互響應(yīng)更好的界面給用戶!

在開始閱讀這篇教程之前,先閱讀我們的?Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial會很有幫助。然而,因?yàn)楸酒坛瘫容^通俗易懂,所以也可以不必閱讀這篇文章。

背景知識

在你學(xué)習(xí)這篇教程之前,有幾個技術(shù)概念需要先解決下。

也許你聽說過并發(fā)和并行操作。從技術(shù)角度來看,并發(fā)是程序的屬性,而并行運(yùn)作是機(jī)器的屬性。并行和并發(fā)是兩種分開的概念。作為程序員,你不能保證你的代碼會在能并行執(zhí)行你的代碼的機(jī)器上運(yùn)行。然而,你可以設(shè)計(jì)你的代碼,讓它使用并發(fā)操作。

首先,有必要定義幾個術(shù)語:

  • 任務(wù):一項(xiàng)需要完成的,簡單,單一的任務(wù)。
  • 線程:一種由操作系統(tǒng)提供的機(jī)制,允許多條指令在一個單獨(dú)的程序中同時執(zhí)行。
  • 進(jìn)程:一段可執(zhí)行的代碼,它可以由幾個線程組成。

注意:在iPhone和Mac中,線程功能是由POSIX Threads API(或者pthreads)提供的,它是操作系統(tǒng)的一部分。這是相當(dāng)?shù)讓拥臇|西,你會發(fā)現(xiàn)很容易犯錯;也許線程最壞的地方就是那些極難被發(fā)現(xiàn)的錯誤吧!

Foundation 框架包含了一個叫做NSThread的類,他更容易處理,但是使用NSThread管理多個線程仍然是件令人頭疼的事情。NSOperation和NSOperationQueue是更高級別的類,他們大大簡化了處理多個線程的過程。

在這張圖中,你可以看到進(jìn)程,線程和任務(wù)之間的關(guān)系:

進(jìn)程,線程和任務(wù)

正如你看到的,一個進(jìn)程包含多個可執(zhí)行的線程,而且每個線程可以同時執(zhí)行多項(xiàng)任務(wù)。

在這張圖中,線程2執(zhí)行了讀文件的操作,而線程1執(zhí)行了用戶界面相關(guān)的代碼。這跟你在iOS中構(gòu)建你的代碼很相似 – 主線程應(yīng)該執(zhí)行任何與用戶界面有關(guān)的任務(wù),然后二級線程應(yīng)該執(zhí)行緩慢的或者長時間的操作(例如讀文件,訪問網(wǎng)絡(luò),等等。)

NSOperation vs. Grand Central Dispatch (GCD)

你也許聽說過?Grand Central Dispatch (GCD)。簡而言之,GCD包含語言特性,運(yùn)行時刻庫和系統(tǒng)增強(qiáng)(提供系統(tǒng)性和綜合性的提升,從而在iOS和OS X的多核硬件上支持并發(fā)操作)。如果你希望更多的了解GCD,你可以閱讀我們的Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial教程。

在Mac OS X v10.6和iOS4之前,NSOperation 與 NSOperationQueue 不同于GCD,他們使用了完全不同的機(jī)制。從Mac OS X v10.6和iOS4開始,NSOperation 和 NSOperationQueue是建立在GCD上的。作為一種通例,蘋果推薦使用最高級別的抽象,然而當(dāng)評估顯示有需要時,會突然降到更低級別。

以下是對兩者的快速比較,它會幫助你決定何時何地去使用GCD或者NSOperation和NSOperationQueue;

  • GCD是一種輕量級的方法來代表將要被并發(fā)執(zhí)行的任務(wù)單位。你并不需要去計(jì)劃這些任務(wù)單位;系統(tǒng)會為你做計(jì)劃。在塊(block)中添加依賴會是一件令人頭疼的事情。取消或者暫停一個塊會給一個開發(fā)者產(chǎn)生額外的工作!:]
  • NSOperation和NSOperationQueue 對比GCD會帶來一點(diǎn)額外的系統(tǒng)開銷,但是你可以在多個操作(operation)中添加附屬。你可以重用操作,取消或者暫停他們。NSOperation和?Key-Value Observation (KVO)是兼容的;例如,你可以通過監(jiān)聽NSNotificationCenter去讓一個操作開始執(zhí)行。

初步的工程模型

在工程的初步模型中,你有一個由字典作為其數(shù)據(jù)來源的table view。字典的關(guān)鍵字是圖片的名字,每個關(guān)鍵字的值是圖片所在的URL地址。本工程的目標(biāo)是讀取字典的內(nèi)容,下載圖片,應(yīng)用圖片濾鏡操作,最后在table view中顯示圖片。

以下是該模型的示意圖:

初步模型

實(shí)現(xiàn) – 你可能會首先想到的方法…

注意:
如果你不想先創(chuàng)建一個非線程版本的工程,而是想直接進(jìn)入多線程方向,你可以跳過這一節(jié),下載我們在本節(jié)中創(chuàng)建的第一版本工程。

所有的圖片來自stock.xchng。在數(shù)據(jù)源中的某些圖片是有意命名錯誤,這樣就有例子去測試下載圖片失敗的情況。

啟動Xcode并使用iOSApplicationEmpty Application模版創(chuàng)建一個新工程,然后點(diǎn)擊下一步。將它命名為ClassicPhotos。選擇Universal, 勾選上Use Automatic Reference Counting(其他都不要選),然后點(diǎn)擊下一步。將工程保存到任意位置。

從Project Navigator中選擇ClassicPhoto工程。選擇Targets ClassicPhotosBuild Phases 然后展開Link Binary with Libraries。使用+按鈕添加Core Image framework(你將需要Core Image來做圖像濾鏡處理)。

在Project Navigator中切換到AppDelegate.h 文件,然后導(dǎo)入ListViewController文件 — 它將會作為root view controller,接下來你會定義它。ListViewController是UITableViewController的子類。

#import "ListViewController.h"

切換到AppDelegate.m文件,找到application:didFinishLaunchingWithOptions:方法。Init和alloc一個ListViewController的實(shí)例變量。將它包在UINavigationController中,然后設(shè)置它為UIWindow的root view controller.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];self.window.backgroundColor = [UIColor whiteColor];/*ListViewController is a subclass of UITableViewController.We will display images in ListViewController.Here, we wrap our ListViewController in a UINavigationController, and set it as the root view controller.*/ListViewController *listViewController = [[ListViewController alloc] initWithStyle:UITableViewStylePlain];UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:listViewController];self.window.rootViewController = navController;[self.window makeKeyAndVisible];return YES; }

注意: 如果你之前還沒有這樣創(chuàng)建過一個用戶界面,那么這就是在不需要使用Storyboards或者Interface Builder的情況下,用純代碼形式去創(chuàng)建一個用戶界面的方法。

接下來創(chuàng)建一個UITableViewController的子類,然后命名它為ListViewController. 切換到ListViewController.h文件,并對它做以下修改:

//1 #import UIKit/UIKit.h #import CoreImage/CoreImage.h// 2 #define kDatasourceURLString @"http://www.raywenderlich.com/downloads/ClassicPhotosDictionary.plist"// 3 @interface ListViewController : UITableViewController// 4 @property (nonatomic, strong)NSDictionary *photos; // main data source of controller @end

讓我們一段一段地過一遍上面的代碼:

  • 導(dǎo)入U(xiǎn)IKit和Core Image。
  • 方便起見,定義kDatasourceURLString為數(shù)據(jù)源文件所在位置的URL字符串。
  • 通過替換NSObject為UITableViewController,讓ListViewController繼承UITableViewController。
  • 定義NSDictionary的一個實(shí)例。這會是數(shù)據(jù)源。
  • 現(xiàn)在,切換到ListViewController.m文件,添加以下代碼:

    @implementation ListViewController //1 @synthesize photos = _photos;#pragma mark - #pragma mark - Lazy instantiation// 2 - (NSDictionary *)photos {if (!_photos) {NSURL *dataSourceURL = [NSURL URLWithString:kDatasourceURLString];_photos = [[NSDictionary alloc] initWithContentsOfURL:dataSourceURL];}return _photos; }#pragma mark - #pragma mark - Life cycle- (void)viewDidLoad {// 3self.title = @"Classic Photos";// 4self.tableView.rowHeight = 80.0;[super viewDidLoad]; }- (void)viewDidUnload {// 5[self setPhotos:nil];[super viewDidUnload]; }#pragma mark - #pragma mark - UITableView data source and delegate methods// 6 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { NSInteger count = self.photos.count;return count; }// 7 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {return 80.0; }- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *kCellIdentifier = @"Cell Identifier";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];if (!cell) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier];cell.selectionStyle = UITableViewCellSelectionStyleNone;}// 8NSString *rowKey = [[self.photos allKeys] objectAtIndex:indexPath.row];NSURL *imageURL = [NSURL URLWithString:[self.photos objectForKey:rowKey]];NSData *imageData = [NSData dataWithContentsOfURL:imageURL];UIImage *image = nil;// 9if (imageData) {UIImage *unfiltered_image = [UIImage imageWithData:imageData];image = [self applySepiaFilterToImage:unfiltered_image];}cell.textLabel.text = rowKey;cell.imageView.image = image;return cell; }#pragma mark - #pragma mark - Image filtration// 10 - (UIImage *)applySepiaFilterToImage:(UIImage *)image {CIImage *inputImage = [CIImage imageWithData:UIImagePNGRepresentation(image)];UIImage *sepiaImage = nil;CIContext *context = [CIContext contextWithOptions:nil];CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues: kCIInputImageKey, inputImage, @"inputIntensity", [NSNumber numberWithFloat:0.8], nil];CIImage *outputImage = [filter outputImage];CGImageRef outputImageRef = [context createCGImage:outputImage fromRect:[outputImage extent]];sepiaImage = [UIImage imageWithCGImage:outputImageRef];CGImageRelease(outputImageRef);return sepiaImage; }@end

    好的!這里做了很多事情。別害怕 – 以下是對代碼原理的解釋:

  • 對 photos 變量做了Synthesize操作.
  • 使用了惰性實(shí)例化去加載數(shù)據(jù)源,比如photos 字典。
  • 為view設(shè)置了title屬性。
  • 將table view中的row高度設(shè)定為80.0個像素點(diǎn)。
  • 當(dāng)ListViewController被卸載時,將photos變量設(shè)置為nil。
  • 返回被顯示的row數(shù)量。
  • 這是UITableViewDelegate的可選方法。為了達(dá)到更好的視覺效果,將每個row的高度設(shè)定為80.0。默認(rèn)值為44.0。
  • 從字典中獲取key值,根據(jù)key的value值創(chuàng)建NSURL,然后以NSData類型下載圖片數(shù)據(jù)。
  • 如果你成功下載完數(shù)據(jù),創(chuàng)建image對象,并且使用褐色濾光鏡。
  • 這個方法對image對象使用了褐色濾光鏡。如果你想了解更多關(guān)于Core Image filters的內(nèi)容,你可以閱讀Beginning Core Image in iOS 5 Tutorial。
  • 就是這樣!試試吧!編譯運(yùn)行工程。很美,褐色的照片 – 但是…他們..看起來…很…慢! 雖然很好看,但是你只能在等待圖片加載的時候去吃點(diǎn)零食打發(fā)時間了. :]

    ClassicPhotos (緩慢的版本)

    是時候想想如何提升用戶體驗(yàn)了!

    線程

    每一個應(yīng)用程序至少有一個主線程。線程的工作就是去執(zhí)行一系列的指令。在Cocoa Touch中,主線程包含應(yīng)用程序的主運(yùn)行回路。幾乎所有你寫的代碼都會在主線程中執(zhí)行,除非你特別創(chuàng)建了一個單獨(dú)的線程,并在這個新線程中執(zhí)行代碼。

    線程有兩個顯著的特征:

  • 每個線程都有訪問你的應(yīng)用程序資源的同等權(quán)限;它包括訪問除了局部變量之外的所有的對象。所以,任何對象都可能被任意線程修改,使用并且改變。
  • 沒有辦法可以去預(yù)測一個線程會運(yùn)行多久 — 或者哪個線程會首先完成!
  • 所以,知道這些技術(shù)很重要,它們可以去攻克難點(diǎn),防止意外的錯誤!:] 以下是對多線程應(yīng)用時面臨的挑戰(zhàn)介紹 – 以及一些如何有效解決它們的提示。

    • 資源競爭:當(dāng)每個線程都去訪問同一段內(nèi)存時,會導(dǎo)致所謂的資源競爭問題。當(dāng)有多個并發(fā)線程訪問共享數(shù)據(jù)時,首先訪問內(nèi)存數(shù)據(jù)的線程會改變共享數(shù)據(jù) – 而且并不能保證哪個線程會首先訪問到內(nèi)存數(shù)據(jù)。你也許會假設(shè)有一個局部變量擁有你的線程最后一次寫到共享內(nèi)存的值,但是另一個線程也許會同時改變了共享內(nèi)存的數(shù)據(jù),然后你的局部變量就過時了!如果你知道這種情況會存在你的代碼中(例如你會從多個線程同時讀/寫數(shù)據(jù)),就應(yīng)該使用互斥鎖。互斥代表互相排斥。你可以通過使用 “@synchronized block”將實(shí)例變量包圍起來,創(chuàng)建一個互斥鎖。這樣你就可以確保在互斥鎖中的代碼一次只能被一個線程訪問:
      @synchronized (self) { myClass.object = value; }

      在以上代碼中“Self”被稱為一個“信號量”。當(dāng)一個線程要范圍這段代碼時,它會檢查其他的線程是否也在訪問“self”。如果沒有線程在訪問“self”,這塊代碼會被執(zhí)行;否則這段線程會被限制訪問直到這個互斥鎖解除為止。

    • 原子性:你也許在property聲明中見過很多次“nonatomic”。當(dāng)你將一個property聲明為atomic時,通常會把它包裹在一個@synchronized塊中,確保它是線程安全的。當(dāng)然,這種方法會添加一些額外的系統(tǒng)開銷。為了更清楚的解釋它,以下是一個關(guān)于atomic property的初步實(shí)現(xiàn):
      // If you declare a property as atomic ... @property (atomic, retain) NSString *myString;// ... a rough implementation that the system generates automatically, // looks like this: - (NSString *)myString {@synchronized (self) {?return [[myString retain] autorelease];?}}

      在上面的代碼中,“retain”和“autorelease”被當(dāng)做返回值來使用,它們被多個線程訪問了,而且你不希望這個對象在多個調(diào)用之間被釋放了。

      所以,你先把它的值retain一下,然后把它放在自動釋放池中。你可以在蘋果的技術(shù)文檔里面了解到更多關(guān)于?線程安全的內(nèi)容。只要是大部分iOS程序員不想費(fèi)心去發(fā)掘它的話,都值得去了解下。重要提示:這是一個很好的面試問題!:]

      大部分的UIKit properties都不是線程安全的。想看下一個類是否是線程安全的,可以看看API文檔。如果API文檔沒有提到任何關(guān)于線程安全的內(nèi)容,你可以假設(shè)這個類是非線程安全的。

      按常規(guī),如果你正在執(zhí)行一個二級的線程,而且你要對UIKit對象做操作,可以使用performSelectorOnMainThread。

    • 死鎖:一個線程被停滯,無限期地等待永遠(yuǎn)不會發(fā)生的條件。例如,如果兩個線程在互相執(zhí)行synchronized代碼,每一個線程就會等待另一個線程完成并且打開鎖。但是這種情況永遠(yuǎn)不會發(fā)生,這樣兩個線程都會成為死鎖。
    • 困乏時間:這會發(fā)生在有太多的線程同時執(zhí)行,系統(tǒng)會停滯不前。NSOperationQueue有一個屬性,讓你設(shè)置并發(fā)線程的數(shù)量。

    NSOperation API

    NSOperation 類有一個相當(dāng)簡短的聲明。要定制一個操作,可以遵循以下步驟:

  • 繼承NSOperation類
  • 重寫“main”方法
  • 在“main”方法中創(chuàng)建一個“autoreleasepool”
  • 將你的代碼放在“autoreleasepool”中
  • 創(chuàng)建你自己的自動釋放池的原因是,你不能訪問主線程的自動釋放池,所以你應(yīng)該自己創(chuàng)建一個。以下是一個例子:

    #import Foundation/Foundation.h@interface MyLengthyOperation: NSOperation @end
    @implementation MyLengthyOperation- (void)main {// a lengthy operation@autoreleasepool {for (int i = 0 ; i < 10000 ; i++) {NSLog(@"%f", sqrt(i));}} }@end

    上面的例子代碼展示了ARC語法在自動釋放池中的使用。你現(xiàn)在必須使用ARC了!:]

    在線程操作中,你從來都不能明確知道,一個操作什么時候會開始,要持續(xù)多久才能結(jié)束。在大多數(shù)時候,如果用戶滑動離開了頁面,你并不想在后臺執(zhí)行一個操作 – 沒有任何的理由讓你去執(zhí)行。這里關(guān)鍵是要經(jīng)常地檢查NSOperation類的isCancelled屬性。例如,在上面的例子程序中,你會這樣做:

    @interface MyLengthyOperation: NSOperation @end@implementation MyLengthyOperation - (void)main {// a lengthy operation@autoreleasepool {for (int i = 0 ; i < 10000 ; i++) {// is this operation cancelled?if (self.isCancelled)break;NSLog(@"%f", sqrt(i));}} } @end

    要取消一個操作,你可以調(diào)用NSOperation的cancel方法,展示如下:

    // In your controller class, you create the NSOperation // Create the operation MyLengthyOperation *my_lengthy_operation = [[MyLengthyOperation alloc] init]; . . . // Cancel it [my_lengthy_operation cancel];

    NSOperation類還有其他的方法和屬性:

    • 開始(start):通常,你不會重寫這個方法。重寫“start”方法需要相對復(fù)雜的實(shí)現(xiàn),你還需要注意像isExecuting,isFinished,isConcurrent和isReady這些屬性。當(dāng)你將一個操作添加到一個隊(duì)列當(dāng)中時(一個NSOperationQueue的實(shí)例,接下來會討論的),這個隊(duì)列會在操作中調(diào)用“start”方法,然后它會做一些準(zhǔn)備和“main”方法的后續(xù)操作。假如你在一個NSOperation實(shí)例中調(diào)用了“start”方法,如果沒有把它添加到一個隊(duì)列中,這個操作會在main loop中執(zhí)行。
    • 從屬性(Dependency):你可以讓一個操作從屬于其他的操作。任何操作都可以從屬于任意數(shù)量的操作。當(dāng)你讓操作A從屬于操作B時,即使你調(diào)用了操作A的“start”方法,它會等待操作B結(jié)束后才開始執(zhí)行。例如:
    MyDownloadOperation *downloadOp = [[MyDownloadOperation alloc] init]; // MyDownloadOperation is a subclass of NSOperation MyFilterOperation *filterOp = [[MyFilterOperation alloc] init]; // MyFilterOperation is a subclass of NSOperation[filterOp addDependency:downloadOp];

    要刪除依賴性:

    [filterOp removeDependency:downloadOp];
    • 優(yōu)先級(Priority):有時候你希望在后臺運(yùn)行的操作并不是很重要的,它可以以較低的優(yōu)先級執(zhí)行。可以通過使用“setQueuePriority:”方法設(shè)置一個操作的優(yōu)先級。
      [filterOp setQueuePriority:NSOperationQueuePriorityVeryLow];

      其他關(guān)于設(shè)置線程優(yōu)先級的選擇有: NSOperationQueuePriorityLow, NSOperationQueuePriorityNormal, NSOperationQueuePriorityHigh和NSOperationQueuePriorityVeryHigh.
      當(dāng)你添加了操作到一個隊(duì)列時,在對操作調(diào)用“start”方法之前,NSOperationQueue會瀏覽所有的操作。那些有較高優(yōu)先級的操作會被先執(zhí)行。有同等優(yōu)先級的操作會按照添加到隊(duì)列中的順序去執(zhí)行(先進(jìn)先出)。
      (歷史注釋:在1997年,火星車中的嵌入式系統(tǒng)遭遇過優(yōu)先級反轉(zhuǎn)問題,也許這是說明正確處理優(yōu)先級和互斥鎖的最昂貴示例了。想對這一事件的背景知識有更多的了解,可以看這個網(wǎng)址:?http://research.microsoft.com/en-us/um/people/mbj/Mars_Pathfinder/Mars_Pathfinder.html?)

    • Completion block:在NSOperation 類中另一個有用的方法叫setCompletionBlock:。一旦操作完成了,如果你還有一些事情想做,你可以把它放在一個塊中,并且傳遞給這個方法。這個塊會在主線程中執(zhí)行。
      [filterOp removeDependency:downloadOp];

    其他一些關(guān)于處理線程的提示:

    • 如果你需要傳遞一些值和指針到一個線程中,創(chuàng)建你自己的指定初始化方法是一個很好的嘗試:
      #import Foundation/Foundation.h@interface MyOperation : NSOperation-(id)initWithNumber:(NSNumber *)start string:(NSString *)string;@end
    • 如果你的操作需要有一個返回值或者對象,聲明一個委托方法是不錯的選擇。記住委托方法必須在主線程中返回。然而,因?yàn)槟阋^承NSOperation類,你必須先將這個操作類強(qiáng)制轉(zhuǎn)換為NSObject對象。可以按照以下步驟去做:
      [(NSObject *)self.delegate performSelectorOnMainThread:(@selector(delegateMethod:)) withObject:object waitUntilDone:NO];
    • 要經(jīng)常檢查isCancelled屬性。如果操作不需要被執(zhí)行了,你就不想在后臺去運(yùn)行它了!
    • 你并不需要重寫“start”方法。然而,如果你決定去重寫“start”方法,就必須處理好像isExecuting, isFinished, isConcurrent 和 isReady這些屬性。否則你的操作類不會正確的運(yùn)作。
    • 你一旦添加了一個操作到一個隊(duì)列(NSOperationQueue的一個實(shí)例)中,就要負(fù)責(zé)釋放它(如果你不使用ARC的話)。NSOperationQueue 獲得操作對象的所有權(quán),調(diào)用“start”方法,然后結(jié)束時負(fù)責(zé)釋放它。
    • 你不能重用一個操作對象。一旦它被添加到一個隊(duì)列中,你就喪失了對它的所有權(quán)。如果你想再使用同一個操作類,就必須創(chuàng)建一個新的實(shí)例變量。
    • 一個結(jié)束的操作不能被重啟。
    • 如果你取消了一個操作,它不會馬上就發(fā)生。它會在未來的某個時候某人在“main”函數(shù)中明確地檢查isCancelled == YES 時被取消掉;否則,操作會一直執(zhí)行到完成為止。
    • 一個操作是否成功地完成,失敗了,或者是被取消了,isFinished的值總會被設(shè)置為YES。所以千萬不要覺得isFinished == YES就表示所有的事情都順利完成了 — 特別的,如果你在代碼里面有從屬性(dependencies),就要更加注意!

    NSOperationQueue API

    NSOperationQueue 也有一個相當(dāng)簡單的界面。它甚至比NSOperation還要簡單,因?yàn)槟悴恍枰ダ^承它,或者重寫任何的方法 — 你可以簡單創(chuàng)建一個。給你的隊(duì)列起一個名字會是一個不錯的做法;這樣你可以在運(yùn)行時識別出你的操作隊(duì)列,并且讓調(diào)試變得更簡單:

    NSOperationQueue *myQueue = [[NSOperationQueue alloc] init]; myQueue.name = @"Download Queue";
    • ?并發(fā)操作:隊(duì)列和線程是兩個不同的概念。一個隊(duì)列可以有多個線程。每個隊(duì)列中的操作會在所屬的線程中運(yùn)行。舉個例子你創(chuàng)建一個隊(duì)列,然后添加三個操作到里面。隊(duì)列會發(fā)起三個單獨(dú)的線程,然后讓所有操作在各自的線程中并發(fā)運(yùn)行。
      到底有多少個線程會被創(chuàng)建?這是個很好的問題!:] 這取決與硬件。默認(rèn)情況下,NSOperationQueue類會在場景背后施展一些魔法,決定如何在特定的平臺下運(yùn)行代碼是最好的,并且會盡量啟用最大的線程數(shù)量。考慮以下的例子。假設(shè)系統(tǒng)是空閑的,并且有很多的可用資源,這樣NSOperationQueue會啟用比如8個同步線程。下次你運(yùn)行程序,系統(tǒng)會忙于處理其他不相關(guān)的操作,它們消耗著資源,然后NSOperationQueue只會啟用兩個同步線程了。
    • 并發(fā)操作的最大值:你可以設(shè)定NSOperationQueue可以并發(fā)運(yùn)行的最大操作數(shù)。NSOperationQueue會選擇去運(yùn)行任何數(shù)量的并發(fā)操作,但是不會超過最大值。
      myQueue.MaxConcurrentOperationCount = 3;

      如果你改變了主意,想將MaxConcurrentOperationCount設(shè)置回默認(rèn)值,你可以執(zhí)行下列操作:

      myQueue.MaxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
    • 添加操作:一個操作一旦被添加到一個隊(duì)列中,你就應(yīng)該通過傳送一個release消息給操作對象(如果使用了手動引用計(jì)數(shù),非ARC的話),然后隊(duì)列會負(fù)責(zé)開始這個操作。從這點(diǎn)上看,什么時候調(diào)用“start”方法由這個隊(duì)列說了算。
      [myQueue addOperation:downloadOp]; [downloadOp release]; // manual reference counting
    • 待處理的操作:任何時候你可以詢問一個隊(duì)列哪個操作在里面,并且總共有多少個操作在里面。記住只有那些等待被執(zhí)行的操作,還有那些正在運(yùn)行的操作,會被保留在隊(duì)列中。操作一完成,就會退出隊(duì)列。
      NSArray *active_and_pending_operations = myQueue.operations; NSInteger count_of_operations = myQueue.operationCount;
    • 暫停隊(duì)列:你可以通過設(shè)定setSuspended:YES來暫停一個隊(duì)列。這樣會暫停所有在隊(duì)列中的操作 — 你不能單獨(dú)的暫停操作。要重新開始隊(duì)列,只要簡單的setSuspended:NO。
      // Suspend a queue [myQueue setSuspended:YES]; . . . // Resume a queue [myQueue setSuspended: NO];
    • 取消操作:要取消一個隊(duì)列中的所有操作,你只要簡單的調(diào)用“cancelAllOperations”方法即可。還記得之前提醒過經(jīng)常檢查NSOperation中的isCancelled屬性嗎?
      原因是“cancelAllOperations”并沒有做太多的工作,他只是對隊(duì)列中的每一個操作調(diào)用“cancel”方法 — 這并沒有起很大作用!:] 如果一個操作并沒有開始,然后你對它調(diào)用“cancel”方法,操作會被取消,并從隊(duì)列中移除。然而,如果一個操作已經(jīng)在執(zhí)行了,這就要由單獨(dú)的操作去識別撤銷(通過檢查isCancelled屬性)然后停止它所做的工作。
    [myQueue cancelAllOperations];
    • addOperationWithBlock: 如果你有一個簡單的操作不需要被繼承,你可以將它當(dāng)做一個塊(block)傳遞給隊(duì)列。如果你需要從塊那里傳遞回任何數(shù)據(jù),記得你不應(yīng)該傳遞任何強(qiáng)引用的指針給塊;相反,你必須使用弱引用。而且,如果你想要在塊中做一些跟UI有關(guān)的事情,你必須在主線程中做。
      UIImage *myImage = nil;// Create a weak reference __weak UIImage *myImage_weak = myImage;// Add an operation as a block to a queue [myQueue addOperationWithBlock: ^ {// a block of operationNSURL *aURL = [NSURL URLWithString:@"http://www.somewhere.com/image.png"];NSError *error = nil;NSData *data = [NSData dataWithContentsOfURL:aURL options:nil error:&error];If (!error)[myImage_weak imageWithData:data];// Get hold of main queue (main thread)[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {myImageView.image = myImage_weak; // updating UI}];}];

    ?

    重新定義模型

    是時候重新定義初步的非線程模型了!如果你仔細(xì)看下初步的模型,你會看到有三個線程區(qū)域可以改進(jìn)。通過把這三個區(qū)域區(qū)分開來,然后把它們各自放在一個單獨(dú)的線程中,主線程會獲得解脫,并且可以保持對用戶交互的迅速響應(yīng)。

    注意:如果你不能馬上理解為什么你的app運(yùn)作得這么慢 — 而且有時候這并不明顯 — 你應(yīng)該使用Instruments工具。然而,這需要另一篇教程去講解它了!:]

    改進(jìn)的模型

    為了擺脫你的程序的瓶頸限制,你需要一個特定的線程去響應(yīng)用戶交互事件,一個線程專門用于下載數(shù)據(jù)源和圖片,還有一個線程用于執(zhí)行圖片濾鏡處理。在新的模型中,app在主線程中開始,并且加載一個空白的table view。同時,app會開始另一個線程去下載數(shù)據(jù)源。

    一旦數(shù)據(jù)源下載完畢,你會告訴table view重新加載自己。這會在主線程中完成。這個時候,table view知道有多少行,而且知道需要顯示的圖片的URL地址,但是它還沒有實(shí)際的圖片!如果你在這個時候馬上開始下載所有的圖片,這會非常沒有效率,因?yàn)槟阋幌伦硬恍枰械膱D片!

    怎樣可以把它弄得更好?

    一個更好的模型就是去下載在當(dāng)前屏幕可見的row的圖片。所以你的代碼首先會問table view哪些row是可見的,然后才會開始下載過程。還有,圖片濾鏡處理會在圖片下載完成后才開始。因此,代碼應(yīng)該等待出現(xiàn)有一個待濾鏡處理的圖片時才開始進(jìn)行圖片濾鏡處理。

    為了讓app的反應(yīng)變得更加靈敏,代碼會在圖片下載完畢后馬上顯示,而不會等待進(jìn)行濾鏡處理。一旦圖片的濾鏡處理完成,就會更新UI以顯示濾鏡處理過的圖片。以下是整個處理過程的控制流示意圖:

    控制流程

    為了達(dá)到這些目標(biāo),你需要去監(jiān)測圖片是否正在下載,或者已經(jīng)完成了下載,還是圖片的濾鏡處理已經(jīng)完成了。你還需要去監(jiān)測每個操作的狀態(tài),以及判斷它是一個下載操作還是一個濾鏡處理操作,這樣你才能在用戶滾動table view的時候去做取消,中止或者恢復(fù)操作。

    好的!現(xiàn)在你準(zhǔn)備好開始寫代碼了!:]

    打開之前的工程,添加一個命名為?PhotoRecord的NSObject新子類到工程中。打開PhotoRecord.h文件,然后添加以下代碼到頭文件中:

    #import UIKit/UIKit.h // because we need UIImage@interface PhotoRecord : NSObject@property (nonatomic, strong) NSString *name; // To store the name of image @property (nonatomic, strong) UIImage *image; // To store the actual image @property (nonatomic, strong) NSURL *URL; // To store the URL of the image @property (nonatomic, readonly) BOOL hasImage; // Return YES if image is downloaded. @property (nonatomic, getter = isFiltered) BOOL filtered; // Return YES if image is sepia-filtered @property (nonatomic, getter = isFailed) BOOL failed; // Return Yes if image failed to be downloaded@end

    是不是覺得上面的語法挺熟悉的?每一個property都有一個getter和setter方法。像這樣去指定getter方法僅僅是讓它的命名更加明確。

    切換到PhotoRecord.m文件,然后添加以下代碼:

    @implementation PhotoRecord@synthesize name = _name; @synthesize image = _image; @synthesize URL = _URL; @synthesize hasImage = _hasImage; @synthesize filtered = _filtered; @synthesize failed = _failed;- (BOOL)hasImage {return _image != nil; }- (BOOL)isFailed {return _failed; }- (BOOL)isFiltered {return _filtered; }@end

    要監(jiān)測每一個操作的狀態(tài),你需要一個單獨(dú)的類。創(chuàng)建另一個命名為PendingOperations的NSObject新類。切換到PendingOperations.h文件,然后添加以下代碼:

    #import Foundation/Foundation.h@interface PendingOperations : NSObject@property (nonatomic, strong) NSMutableDictionary *downloadsInProgress; @property (nonatomic, strong) NSOperationQueue *downloadQueue;@property (nonatomic, strong) NSMutableDictionary *filtrationsInProgress; @property (nonatomic, strong) NSOperationQueue *filtrationQueue;@end

    這個頭文件也挺簡單。你申明了兩個字典去監(jiān)測活躍和等待的下載與濾鏡操作。字典的key代表table view row的indexPath,然后字典的value會是兩個單獨(dú)的ImageDownloader和ImageFiltration實(shí)例。

    注意:你可能會對為什么要監(jiān)測所有的活躍和等待操作感到好奇。難道不能通過對 [NSOperationQueue operations]的查詢來訪問它們嗎?是的,但是在本工程中,這樣做的話效率不是很高。

    每次你需要去用有等待操作的行(row)的indexPath去和可見行的indexPath作對比時,你需要使用幾個迭代循環(huán),這樣的話會是一個很耗資源的操作。通過申明一個額外的NSDictionary實(shí)例,你可以方便的了解等待操作(operations),而不需要執(zhí)行沒有效率的循環(huán)操作(operations)。

    切換到PendingOperations.m文件,然后添加以下代碼:

    @implementation PendingOperations @synthesize downloadsInProgress = _downloadsInProgress; @synthesize downloadQueue = _downloadQueue;@synthesize filtrationsInProgress = _filtrationsInProgress; @synthesize filtrationQueue = _filtrationQueue;- (NSMutableDictionary *)downloadsInProgress {if (!_downloadsInProgress) {_downloadsInProgress = [[NSMutableDictionary alloc] init];}return _downloadsInProgress; }- (NSOperationQueue *)downloadQueue {if (!_downloadQueue) {_downloadQueue = [[NSOperationQueue alloc] init];_downloadQueue.name = @"Download Queue";_downloadQueue.maxConcurrentOperationCount = 1;}return _downloadQueue; }- (NSMutableDictionary *)filtrationsInProgress {if (!_filtrationsInProgress) {_filtrationsInProgress = [[NSMutableDictionary alloc] init];}return _filtrationsInProgress; }- (NSOperationQueue *)filtrationQueue {if (!_filtrationQueue) {_filtrationQueue = [[NSOperationQueue alloc] init];_filtrationQueue.name = @"Image Filtration Queue";_filtrationQueue.maxConcurrentOperationCount = 1;}return _filtrationQueue; }@end

    這里,你重寫了一些getter方法去利用惰性實(shí)例化,所以你并不需要真的去給實(shí)例變量分配內(nèi)存空間,直到他們被訪問為止。你還要給兩個隊(duì)列初始化和分配內(nèi)存空間 — 一個用于下載操作,一個用于濾鏡處理 — 然后設(shè)定他們的屬性(properties),所以當(dāng)你在另外的類中訪問他們時,你不需要擔(dān)心他們的初始化操作。 maxConcurrentOperationCount變量在本教程中設(shè)定為1。

    現(xiàn)在,是時候處理下載和濾鏡處理操作了。創(chuàng)建一個命名為ImageDownloader的NSOperatoin子類。切換到ImageDownloader.h文件,然后添加以下代碼:

    #import Foundation/Foundation.h// 1 #import "PhotoRecord.h"// 2 @protocol ImageDownloaderDelegate;@interface ImageDownloader : NSOperation@property (nonatomic, assign) id delegate;// 3 @property (nonatomic, readonly, strong) NSIndexPath *indexPathInTableView; @property (nonatomic, readonly, strong) PhotoRecord *photoRecord;// 4 - (id)initWithPhotoRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath delegate:(id) theDelegate;@end@protocol ImageDownloaderDelegate // 5 - (void)imageDownloaderDidFinish:(ImageDownloader *)downloader; @end


    第二部分

    以下是對上面代碼的注解:

  • 導(dǎo)入PhotoRecord.h文件,這樣你就可以在下載成功后,單獨(dú)地設(shè)置PhotoRecord變量的圖片屬性(image property)。如果下載失敗,設(shè)定它的failed值為YES。
  • 申明一個delegate,這樣一旦操作完成了,你可以通知調(diào)用者(caller)。
  • 為了方便起見,申明了indexPathInTableView變量,這樣一旦操作結(jié)束了,調(diào)用者就會有一個屬于操作的引用。
  • 申明一個初始化方法。
  • 在你的delegate方法中,將整個類作為對象傳遞回給調(diào)用者,這樣調(diào)用者就可以訪問indexPathInTableView和photoRecord變量了。因?yàn)槟阈枰⒉僮鲗ο?#xff08;operation)強(qiáng)制轉(zhuǎn)換為NSObject類型,然后在主線程中返回,delegate方法的變量不能超過一個。
  • 切換到ImageDownloader.m文件,然后做以下修改:

    // 1 @interface ImageDownloader () @property (nonatomic, readwrite, strong) NSIndexPath *indexPathInTableView; @property (nonatomic, readwrite, strong) PhotoRecord *photoRecord; @end@implementation ImageDownloader @synthesize delegate = _delegate; @synthesize indexPathInTableView = _indexPathInTableView; @synthesize photoRecord = _photoRecord;#pragma mark - #pragma mark - Life Cycle- (id)initWithPhotoRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath delegate:(id<ImageDownloaderDelegate>)theDelegate {if (self = [super init]) {// 2self.delegate = theDelegate;self.indexPathInTableView = indexPath;self.photoRecord = record;}return self; }#pragma mark - #pragma mark - Downloading image// 3 - (void)main {// 4@autoreleasepool {if (self.isCancelled)return;NSData *imageData = [[NSData alloc] initWithContentsOfURL:self.photoRecord.URL];if (self.isCancelled) {imageData = nil;return;}if (imageData) {UIImage *downloadedImage = [UIImage imageWithData:imageData];self.photoRecord.image = downloadedImage;}else {self.photoRecord.failed = YES;}imageData = nil;if (self.isCancelled)return;// 5[(NSObject *)self.delegate performSelectorOnMainThread:@selector(imageDownloaderDidFinish:) withObject:self waitUntilDone:NO];} }@end

    通過代碼注釋,你將看到上面的代碼在做下面的操作:

  • 申明一個私有的接口,你可以改變實(shí)例變量的屬性為讀寫(read-write)。
  • 設(shè)定properties屬性。
  • 經(jīng)常檢查isCancelled變量,確保操作即時結(jié)束。
  • 蘋果推薦使用@autoreleasepool塊,而不是alloc和init NSAutoreleasePool變量,因?yàn)閎locks更加有效率。你也許會使用NSAutoreleasePool,這樣也行。
  • 將operation對象強(qiáng)制轉(zhuǎn)換為NSobject類型,然后在主線程中通知調(diào)用者(caller)。
  • 現(xiàn)在,繼續(xù)創(chuàng)建一個NSOperation的子類,用來處理圖片濾鏡操作吧!

    創(chuàng)建另一個命名為 ImageFiltration的NSOperation新子類。打開 ImageFiltration.h文件,添加以下代碼:

    ? // 1 #import <UIKit/UIKit.h> #import <CoreImage/CoreImage.h> #import "PhotoRecord.h"// 2 @protocol ImageFiltrationDelegate;@interface ImageFiltration : NSOperation@property (nonatomic, weak) id <ImageFiltrationDelegate> delegate; @property (nonatomic, readonly, strong) NSIndexPath *indexPathInTableView; @property (nonatomic, readonly, strong) PhotoRecord *photoRecord;- (id)initWithPhotoRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath delegate:(id<ImageFiltrationDelegate>)theDelegate;@end@protocol ImageFiltrationDelegate <NSObject> - (void)imageFiltrationDidFinish:(ImageFiltration *)filtration; @end

    再次的,以下是對上面代碼的注釋:

  • 因?yàn)槟阋IImage實(shí)例執(zhí)行濾鏡處理操作,就需要導(dǎo)入U(xiǎn)IKit和CoreImage框架。還需要導(dǎo)入PhotoRecord頭文件。與ImageDownloader類似,你想讓調(diào)用者(caller)使用初始化函數(shù)去進(jìn)行alloc和init操作。申明一個delegate,一旦操作完成了,通知調(diào)用者。
  • 申明一個delegate,一旦操作完成了,通知調(diào)用者。
  • 切換到ImageFiltration.m文件,添加以下代碼:

    ? @interface ImageFiltration () @property (nonatomic, readwrite, strong) NSIndexPath *indexPathInTableView; @property (nonatomic, readwrite, strong) PhotoRecord *photoRecord; @end@implementation ImageFiltration @synthesize indexPathInTableView = _indexPathInTableView; @synthesize photoRecord = _photoRecord; @synthesize delegate = _delegate;#pragma mark - #pragma mark - Life cycle- (id)initWithPhotoRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath delegate:(id<ImageFiltrationDelegate>)theDelegate {if (self = [super init]) {self.photoRecord = record;self.indexPathInTableView = indexPath;self.delegate = theDelegate;}return self; }#pragma mark - #pragma mark - Main operation- (void)main {@autoreleasepool {if (self.isCancelled)return;if (!self.photoRecord.hasImage)return;UIImage *rawImage = self.photoRecord.image;UIImage *processedImage = [self applySepiaFilterToImage:rawImage];if (self.isCancelled)return;if (processedImage) {self.photoRecord.image = processedImage;self.photoRecord.filtered = YES;[(NSObject *)self.delegate performSelectorOnMainThread:@selector(imageFiltrationDidFinish:) withObject:self waitUntilDone:NO];}}}#pragma mark - #pragma mark - Filtering image- (UIImage *)applySepiaFilterToImage:(UIImage *)image {// This is expensive + time consumingCIImage *inputImage = [CIImage imageWithData:UIImagePNGRepresentation(image)];if (self.isCancelled)return nil;UIImage *sepiaImage = nil;CIContext *context = [CIContext contextWithOptions:nil];CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues: kCIInputImageKey, inputImage, @"inputIntensity", [NSNumber numberWithFloat:0.8], nil];CIImage *outputImage = [filter outputImage];if (self.isCancelled)return nil;// Create a CGImageRef from the context// This is an expensive + time consumingCGImageRef outputImageRef = [context createCGImage:outputImage fromRect:[outputImage extent]];if (self.isCancelled) {CGImageRelease(outputImageRef);return nil;}sepiaImage = [UIImage imageWithCGImage:outputImageRef];CGImageRelease(outputImageRef);return sepiaImage; }@end

    上面的實(shí)現(xiàn)方法和ImageDownloader類似。圖片的濾鏡處理的實(shí)現(xiàn)方法和你之前在ListViewController.m文件中的一樣。它被移動到這里以便可以在后臺作為一個單獨(dú)的操作完成。你應(yīng)該經(jīng)常檢查isCancelled參數(shù);在任何系統(tǒng)資源消耗較大的函數(shù)調(diào)用前后去調(diào)用這個濾鏡處理函數(shù),是不錯的做法。一旦濾鏡處理結(jié)束了,PhotoRecord實(shí)例的值會被恰當(dāng)?shù)脑O(shè)置好,然后主線程的delegate被通知了。

    很好!現(xiàn)在你已經(jīng)有了在后臺線程中執(zhí)行操作(operations)的所有工具和基礎(chǔ)了。是時候回到view controller然后恰當(dāng)?shù)男薷乃?#xff0c;以便它可以利用好這些新優(yōu)勢。

    注意:在動手之前,你要下載AFNetworking library from GitHub.

    AFNetworking庫是建立在NSOperation 和 NSOperatinQueue之上的。它提供給你很多便捷的方法,以便你不需要為普通的任務(wù),比如在后臺下載一個文件,創(chuàng)建你自己的操作。

    當(dāng)需要從互聯(lián)網(wǎng)下載一個文件的時候,在適當(dāng)?shù)奈恢脤懸恍┐a來檢查錯誤是個不錯的做法。下載數(shù)據(jù)源,一個只有4kBytes 的property list,不是什么大問題,你并不需要操心去為它創(chuàng)建一個子類。然而,你不能假設(shè)會有一個可靠持續(xù)的網(wǎng)絡(luò)連接。

    蘋果為此提供了NSURLConnection類。使用它會是一項(xiàng)額外的工作,特別是當(dāng)你只是想下載一個小的property list時。AFNetworking是一個開源代碼庫,提供了一種非常方便的方式去實(shí)施這類任務(wù)。你要傳入兩個塊(blocks),一個在操作成功時傳入,另一個在操作失敗時傳入。接下來你會看到相關(guān)的實(shí)踐例子。

    要添加這個庫到工程中,選擇File > Add Files To …,然后瀏覽選擇你下載好的AFNetworking文件夾,最后點(diǎn)擊“Add”。確保選中了“Copy items into destination group’s folder”選項(xiàng)!是的,你正在使用ARC,但是AFNetworking還沒有從陳舊的手動管理內(nèi)存的泥潭中爬出來。

    如果你遵循著安裝指南,就可以避免編譯錯誤,如果你不遵循的話,你會在編譯時去處理非常多的錯誤。每一個AFNetworking模塊需要在你的Target’s Build Phases標(biāo)簽包含 “-fno-objc-arc”字段,它在 Compiler Flags部分下面。

    要實(shí)現(xiàn)它,在導(dǎo)航欄(在左手邊)點(diǎn)擊“PhotoRecords”。在右手邊,選擇“Targets”下面的“ClassicPhotos”。從標(biāo)簽欄選擇“Build Phases”。在它下面,選擇三角形展開“Compile Sources”項(xiàng)。選上屬于AFNetworking的所有文件。敲擊Enter鍵,一個對話框就會彈出來。在對話框中,輸入 “fno-objc-arc”,然后點(diǎn)擊“Done”。

    切換到 ListViewController.h文件,然后根據(jù)以下內(nèi)容更新頭文件:

    ? // 1 #import <UIKit/UIKit.h> // #import <CoreImage/CoreImage.h> ... you don't need CoreImage here anymore. #import "PhotoRecord.h" #import "PendingOperations.h" #import "ImageDownloader.h" #import "ImageFiltration.h" // 2 #import "AFNetworking/AFNetworking.h"#define kDatasourceURLString @"https://sites.google.com/site/soheilsstudio/tutorials/nsoperationsampleproject/ClassicPhotosDictionary.plist"// 3 @interface ListViewController : UITableViewController <ImageDownloaderDelegate, ImageFiltrationDelegate>// 4 @property (nonatomic, strong) NSMutableArray *photos; // main data source of controller// 5 @property (nonatomic, strong) PendingOperations *pendingOperations; @end

    這里發(fā)生了什么事?以下要點(diǎn)對上面的代碼做了解釋:

  • 你可以從ListViewController頭文件中刪除CoreImage,因?yàn)槟悴辉傩枰恕H欢?#xff0c;你需要導(dǎo)入PhotoRecord.h文件,PendingOperations.h,ImageDownloader.h和ImageFiltration.h文件。
  • 這里是對AFNetworking庫的引用。
  • 確保讓ListViewController遵從 ImageDownloader和ImageFiltration的delegate方法。
  • 你不再需要這樣的數(shù)據(jù)源。你將要使用property list來創(chuàng)建PhotoRecord的實(shí)例。所以,將“photos”類從NSDictionary修改為NSMutableArray,這樣你就可以更新圖片數(shù)組了。
  • 這個property被用來監(jiān)測等待操作(operations)。
  • 切換到ListViewController.m文件,然后根據(jù)以下內(nèi)容進(jìn)行更新:

    // Add this to the beginning of ListViewController.m @synthesize pendingOperations = _pendingOperations; . . . // Add this to viewDidUnload [self setPendingOperations:nil];

    在“photos”的惰性初始化之前,添加“pendingOperations”的惰性初始化:

    - (PendingOperations *)pendingOperations {if (!_pendingOperations) {_pendingOperations = [[PendingOperations alloc] init];}return _pendingOperations; }

    現(xiàn)在來到“photos”的惰性初始化,并做以下修改:

    - (NSMutableArray *)photos {if (!_photos) {// 1NSURL *datasourceURL = [NSURL URLWithString:kDatasourceURLString];NSURLRequest *request = [NSURLRequest requestWithURL:datasourceURL];// 2AFHTTPRequestOperation *datasource_download_operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];// 3[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];// 4[datasource_download_operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {// 5NSData *datasource_data = (NSData *)responseObject;CFPropertyListRef plist = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, (__bridge CFDataRef)datasource_data, kCFPropertyListImmutable, NULL);NSDictionary *datasource_dictionary = (__bridge NSDictionary *)plist;// 6NSMutableArray *records = [NSMutableArray array];for (NSString *key in datasource_dictionary) {PhotoRecord *record = [[PhotoRecord alloc] init];record.URL = [NSURL URLWithString:[datasource_dictionary objectForKey:key]];record.name = key;[records addObject:record];record = nil;}// 7self.photos = records;CFRelease(plist);[self.tableView reloadData];[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];} failure:^(AFHTTPRequestOperation *operation, NSError *error){// 8// Connection error messageUIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Oops!"message:error.localizedDescriptiondelegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil];[alert show];alert = nil;[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];}];// 9[self.pendingOperations.downloadQueue addOperation:datasource_download_operation];}return _photos; }

    以上代碼做了一些操作。下面的內(nèi)容是對代碼完成內(nèi)容的一步步解析:

  • 創(chuàng)建NSURL和NSURLRequest對象,指向數(shù)據(jù)源的位置。
  • 使用AFHTTPRequestOperation類,用request對象來alloc和init它。
  • 在下載數(shù)據(jù)時,通過啟動網(wǎng)絡(luò)活動指示器(network activity indicator)來提供用戶反饋。
  • 通過使用setCompletionBlockWithSuccess:failure:,你可以添加兩個塊(blocks):一個給操作成功的情況,另一個給操作失敗的情況。
  • 在成功的塊中,以NSData的數(shù)據(jù)格式下載property list, 然后通過使用toll-free briding橋,將參數(shù)強(qiáng)制轉(zhuǎn)換成CFDataRef和CFPropertyList, 再將property list文件轉(zhuǎn)換成NSDictionary。
  • 創(chuàng)建一個NSMutableArray,然后在字典中循環(huán)申明所有的objects和key,創(chuàng)建一個PhotoRecord實(shí)例,然后保存它到數(shù)組中。
  • 一旦完成了,將_photo對象指向records數(shù)組,重新加載table view然后停止網(wǎng)絡(luò)活動指示器。你還要釋放”plist”實(shí)例變量。
  • 也許你的操作會不成功,這時要顯示一條消息給用戶看。
  • 最后,添加 “datasource_download_operation”到PendingOperations的“downloadQueue”中。
  • 來到 tableView:cellForRowAtIndexPath:方法,根據(jù)以下內(nèi)容做修改:

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *kCellIdentifier = @"Cell Identifier";UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];if (!cell) {cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellIdentifier];cell.selectionStyle = UITableViewCellSelectionStyleNone;// 1UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];cell.accessoryView = activityIndicatorView;}// 2PhotoRecord *aRecord = [self.photos objectAtIndex:indexPath.row];// 3if (aRecord.hasImage) {[((UIActivityIndicatorView *)cell.accessoryView) stopAnimating];cell.imageView.image = aRecord.image;cell.textLabel.text = aRecord.name;}// 4else if (aRecord.isFailed) {[((UIActivityIndicatorView *)cell.accessoryView) stopAnimating];cell.imageView.image = [UIImage imageNamed:@"Failed.png"];cell.textLabel.text = @"Failed to load";}// 5else {[((UIActivityIndicatorView *)cell.accessoryView) startAnimating];cell.imageView.image = [UIImage imageNamed:@"Placeholder.png"];cell.textLabel.text = @"";[self startOperationsForPhotoRecord:aRecord atIndexPath:indexPath];}return cell; }

    同樣的,花點(diǎn)時間看下下面的評論解析:

  • 要提供反饋給用戶,創(chuàng)建一個UIActivityIndicatorView然后把它設(shè)置為cell的accessory view。
  • 數(shù)據(jù)源包含PhotoRecord的所有實(shí)例。根據(jù)行(row)的indexPath參數(shù),從photos數(shù)組中獲取并創(chuàng)建相應(yīng)的PhotoRecord實(shí)例。
  • 檢查PhotoRecord。看它的圖片是否已經(jīng)下載完了,顯示了圖片,圖片的名字,然后停止了活動指示器 (activity indicator)。
  • 如果下載圖片失敗,顯示一個預(yù)留圖片來提示失敗情況,然后停止活動指示器(activity indicator)。
  • 否則,圖片還沒有被下載下來。開始下載和圖片濾鏡處理操作(它們現(xiàn)在還沒有被實(shí)現(xiàn)),然后顯示一個預(yù)留圖片表示你正在對它進(jìn)行處理。啟動活動指示器(activity indicator)來提醒用戶有操作正在進(jìn)行。
  • 現(xiàn)在是時候來實(shí)現(xiàn)負(fù)責(zé)啟動操作的方法了。如果你還沒有實(shí)現(xiàn)它,可以在ListViewController.m文件中刪除舊的“applySepiaFilterToImage:”實(shí)現(xiàn)方法。

    來到代碼的結(jié)尾,實(shí)現(xiàn)下列方法:

    ? // 1 - (void)startOperationsForPhotoRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath {// 2if (!record.hasImage) {// 3[self startImageDownloadingForRecord:record atIndexPath:indexPath];}if (!record.isFiltered) {[self startImageFiltrationForRecord:record atIndexPath:indexPath];} }

    以上的代碼相當(dāng)直接,但是有些東西要解釋下:

  • 為了保持簡潔,你要根據(jù)它的indexPath值,傳入一個需要操作(operations)的PhotoRecord實(shí)例。
  • 檢查一下看看它是否有一張圖片;如果是,就不管它。
  • 如果它沒有一張圖片,通過調(diào)用 startImageDownloadingForRecord:atIndexPath:(它會被簡短的實(shí)現(xiàn)出來)方法,開始下載圖片。你也可以對濾鏡操作做同樣的處理:如果圖片還沒有被濾鏡處理過,可以調(diào)用startImageFiltrationForRecord:atIndexPath:(他也會被簡短的實(shí)現(xiàn)出來)方法。
  • 注意:?下載圖片和濾鏡處理圖片的方法是單獨(dú)實(shí)現(xiàn)的,因?yàn)橛锌赡墚?dāng)圖片正在下載時,用戶會將圖片滾動掉,然后你還沒有對圖片做濾鏡處理。這樣下次用戶回到同一行時,你就不需要重新下載圖片;只需要去實(shí)現(xiàn)圖片的濾鏡處理了!很有效的一招!:]

    現(xiàn)在你需要去實(shí)現(xiàn)以上代碼段的startImageDownloadingForRecord:atIndexPath:方法。記住你創(chuàng)建了一個自定義的類,PendingOperations,用于檢測操作(operations)。在這里你開始使用它了。

    - (void)startImageDownloadingForRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath {// 1if (![self.pendingOperations.downloadsInProgress.allKeys containsObject:indexPath]) {// 2 // Start downloadingImageDownloader *imageDownloader = [[ImageDownloader alloc] initWithPhotoRecord:record atIndexPath:indexPath delegate:self];[self.pendingOperations.downloadsInProgress setObject:imageDownloader forKey:indexPath];[self.pendingOperations.downloadQueue addOperation:imageDownloader];} }- (void)startImageFiltrationForRecord:(PhotoRecord *)record atIndexPath:(NSIndexPath *)indexPath {// 3if (![self.pendingOperations.filtrationsInProgress.allKeys containsObject:indexPath]) {// 4// Start filtrationImageFiltration *imageFiltration = [[ImageFiltration alloc] initWithPhotoRecord:record atIndexPath:indexPath delegate:self];// 5ImageDownloader *dependency = [self.pendingOperations.downloadsInProgress objectForKey:indexPath];if (dependency)[imageFiltration addDependency:dependency];[self.pendingOperations.filtrationsInProgress setObject:imageFiltration forKey:indexPath];[self.pendingOperations.filtrationQueue addOperation:imageFiltration];} }

    好的!以下是簡短的解析,以確保你理解了以上代碼的工作原理。

  • 首先,檢查特定的indexPath看是否已經(jīng)有一個操作在downloadsInProgress中了。如果有,就不管它。
  • 如果沒有,使用指定的初始化函數(shù)創(chuàng)建一個ImageDownloader的實(shí)例,然后設(shè)置ListViewController作為它的delegate。傳入恰當(dāng)?shù)膇ndexPath和一個指針給PhotoRecord的實(shí)例,然后把它添加到下載隊(duì)列中。你還要把它添加到downloadsInProgress中,來幫助監(jiān)測事情。
  • 同樣的,檢查看是否有任何的濾鏡處理操作在特定的indexPath項(xiàng)中進(jìn)行。
  • 如果沒有,使用指定的初始化函數(shù)開始一個。
  • 這里的代碼有點(diǎn)巧妙。你首先必須檢查看這個特定的indexPath項(xiàng)是否有一個等待的下載任務(wù);如果是,你可以基于該特定項(xiàng)創(chuàng)建這個濾鏡操作。
  • 很好!你現(xiàn)在需要去實(shí)現(xiàn)ImageDownloader和ImageFiltration的delegate方法了。將下列代碼添加到ListViewController.m文件的末尾:

    - (void)imageDownloaderDidFinish:(ImageDownloader *)downloader {// 1NSIndexPath *indexPath = downloader.indexPathInTableView;// 2PhotoRecord *theRecord = downloader.photoRecord;// 3[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];// 4[self.pendingOperations.downloadsInProgress removeObjectForKey:indexPath]; }- (void)imageFiltrationDidFinish:(ImageFiltration *)filtration {NSIndexPath *indexPath = filtration.indexPathInTableView;PhotoRecord *theRecord = filtration.photoRecord;[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];[self.pendingOperations.filtrationsInProgress removeObjectForKey:indexPath]; }

    所有的delegate方法都有非常相似的實(shí)現(xiàn),所以這里只需要拿其中一個做講解:

  • 檢查操作(operation)的indexPath值,看看它是一個下載操作,還是一個濾鏡處理操作。
  • 創(chuàng)建PhotoRecord的實(shí)例對象。
  • 更新UI。
  • 從downloadsInProgress(或者filtrationsInProgress)中移除操作。
  • 更新:關(guān)于處理PhotoRecord的實(shí)例,來自論壇的“xlledo”提了一個不錯的意見。因?yàn)槟阏趥饕粋€指針給PhotoRecord,再給NSOperation的子類(ImageDownloader和ImageFiltration),你可以直接修改它們。所以replaceObjectAtIndex:withObject:方法在這里是多余的。

    贊!

    Wow! 你做到了!你的工程完成了。編譯運(yùn)行看看實(shí)際的提升效果!當(dāng)你滾動table view的時候,app不再卡死,當(dāng)cell可見時,就開始下載和濾鏡處理圖片了。

    難道這不是很cool嗎?你可以看到一點(diǎn)小小的努力就可以讓你的應(yīng)用程序的響應(yīng)變得更加靈敏 — 并且讓用戶覺得更加有趣!

    進(jìn)一步地調(diào)整

    你已經(jīng)在本篇教程中進(jìn)展很久了!你的小工程比起原來的版本變得更加反應(yīng)靈敏,有了很大的提升。然而,仍然有一些細(xì)節(jié)需要去處理。你想成為一個優(yōu)秀的程序員,而不僅僅是好的程序員!

    你也許已經(jīng)注意到當(dāng)你在table view中滾動時,那些屏幕以外的cell仍然處于下載和濾鏡處理的進(jìn)程中。難道你沒有在代碼里面設(shè)置取消操作?是的,你有 — 你應(yīng)該好好的利用它們!:]

    回到Xcode,切換到ListViewController.m文件中。來到tableView:cellForRowAtIndexPath:的方法實(shí)現(xiàn),如下所示,將[self startOperationsForPhotoRecord:aRecord atIndexPath:indexPath];放在if判斷分支中:

    ? // in implementation of tableView:cellForRowAtIndexPath: if (!tableView.dragging && !tableView.decelerating) {[self startOperationsForPhotoRecord:aRecord atIndexPath:indexPath]; }

    你告訴table view只有在它沒有滾動時才開始操作(operations)。判斷項(xiàng)是UIScrollView的properties屬性,然后因?yàn)閁ITableView是UIScrollView的子類,它就自動地繼承了這些properties屬性。

    現(xiàn)在,來到ListViewController.m文件的結(jié)尾,實(shí)現(xiàn)下面的UIScrollView委托方法:

    #pragma mark - #pragma mark - UIScrollView delegate- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {// 1[self suspendAllOperations]; }- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {// 2if (!decelerate) {[self loadImagesForOnscreenCells];[self resumeAllOperations];} }- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {// 3[self loadImagesForOnscreenCells];[self resumeAllOperations]; }

    以下是對上面代碼的解析:

  • 一旦用戶開始了滾動操作,你會想中止所有的操作(operations),然后看下用戶想看什么。接下來會實(shí)現(xiàn)suspendAllOperations方法。
  • 如果decelerate的值為NO,表示用戶停止了拖動table view的操作。所以你想恢復(fù)中止了的操作(operations),取消屏幕以外的cell的操作,開始屏幕內(nèi)cell的操作。接下來我們會實(shí)現(xiàn)loadImagesForOnscreenCells和resumeAllOperations方法。
  • 這個delegate方法告訴你table view停止了滾動,所以你會做跟第2步一樣的操作。
  • 好的!現(xiàn)在,在ListViewController.m文件的結(jié)尾添加上suspendAllOperations,resumeAllOperations和loadImagesForOnscreenCells方法的實(shí)現(xiàn):

    ? #pragma mark - #pragma mark - Cancelling, suspending, resuming queues / operations- (void)suspendAllOperations {[self.pendingOperations.downloadQueue setSuspended:YES];[self.pendingOperations.filtrationQueue setSuspended:YES]; }- (void)resumeAllOperations {[self.pendingOperations.downloadQueue setSuspended:NO];[self.pendingOperations.filtrationQueue setSuspended:NO]; }- (void)cancelAllOperations {[self.pendingOperations.downloadQueue cancelAllOperations];[self.pendingOperations.filtrationQueue cancelAllOperations]; }- (void)loadImagesForOnscreenCells {// 1NSSet *visibleRows = [NSSet setWithArray:[self.tableView indexPathsForVisibleRows]];// 2NSMutableSet *pendingOperations = [NSMutableSet setWithArray:[self.pendingOperations.downloadsInProgress allKeys]];[pendingOperations addObjectsFromArray:[self.pendingOperations.filtrationsInProgress allKeys]];NSMutableSet *toBeCancelled = [pendingOperations mutableCopy];NSMutableSet *toBeStarted = [visibleRows mutableCopy];// 3[toBeStarted minusSet:pendingOperations];// 4[toBeCancelled minusSet:visibleRows];// 5for (NSIndexPath *anIndexPath in toBeCancelled) {ImageDownloader *pendingDownload = [self.pendingOperations.downloadsInProgress objectForKey:anIndexPath];[pendingDownload cancel];[self.pendingOperations.downloadsInProgress removeObjectForKey:anIndexPath];ImageFiltration *pendingFiltration = [self.pendingOperations.filtrationsInProgress objectForKey:anIndexPath];[pendingFiltration cancel];[self.pendingOperations.filtrationsInProgress removeObjectForKey:anIndexPath];}toBeCancelled = nil;// 6for (NSIndexPath *anIndexPath in toBeStarted) {PhotoRecord *recordToProcess = [self.photos objectAtIndex:anIndexPath.row];[self startOperationsForPhotoRecord:recordToProcess atIndexPath:anIndexPath];}toBeStarted = nil;}

    suspendAllOperations, resumeAllOperations 和 cancelAllOperations 方法都有直接的實(shí)現(xiàn)方式。你基本上會使用工廠方法去中止,恢復(fù)或者取消操作和隊(duì)列。為了方便起見,你將它們放在單獨(dú)的方法中。

    LoadImagesForOnscreenCells方法有點(diǎn)復(fù)雜。以下是對它的解釋:

  • 獲取一NSSet可見行(rows)。
  • 獲取一NSMutableSet所有的等待操作(下載和濾鏡處理)。
  • Rows(或者indexPaths)對應(yīng)的開始操作(operation),等于visible rows — pendings的數(shù)量。
  • Rows(或者indexPaths)對應(yīng)的需要取消的操作,等于pendings — visible rows的數(shù)量。
  • 循環(huán)查看需要被取消的操作,取消它們,然后從PendingOperations中移除它們的引用。
  • 循環(huán)查看需要開始的操作,為它們中的每一個調(diào)用startOperationsForPhotoRecord:atIndexPath:方法。
  • 最后,這個難題的最后項(xiàng)由ListViewController.m文件中的didReceiveMemoryWarning方法解決。

    ? // If app receive memory warning, cancel all operations - (void)didReceiveMemoryWarning {[self cancelAllOperations];[super didReceiveMemoryWarning]; }

    編譯運(yùn)行工程,你會看到一個響應(yīng)更加靈敏,有更好的資源管理的應(yīng)用程序!給自己一點(diǎn)掌聲吧!

    ClassicPhotos (改進(jìn)版本)

    現(xiàn)在還可以做什么?

    這里是工程改進(jìn)后的完整代碼。

    如果你完成了這個工程,并且花時間真正理解了它,恭喜!相比剛閱讀本教程時,你可以把自己看待成一個更有價(jià)值的iOS開發(fā)者了!大部分的開發(fā)工作室都會幸運(yùn)的擁有一兩個能真正理解這些原理的人。

    但是注意 — 像deeply-nested blocks(塊),無理由地使用線程會讓維護(hù)你的代碼的人難以理解。線程會引來不易察覺的bugs,只有當(dāng)網(wǎng)絡(luò)緩慢時才會出現(xiàn),或者當(dāng)代碼運(yùn)行在一個更快(或者更慢)的設(shè)備中,或者有不同內(nèi)核數(shù)目的設(shè)備中。仔細(xì)認(rèn)真的測試,經(jīng)常使用Instruments(或者是你自己的觀察)來核實(shí)引入的線程是否真的取得了性能提升。

    如果你對本教程或者NSOperations有任何的意見或者問題,請加入下面的論壇討論!


    總結(jié)

    以上是生活随笔為你收集整理的如何使用NSOperations和NSOperationQueues的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    婷婷九月激情 | 亚洲精品国产精品国 | 久草新在线 | 99这里有精品 | 久久国产精品电影 | 一本一本久久a久久精品牛牛影视 | 亚洲狠狠操 | av成人资源 | 激情五月五月婷婷 | 色综合久久久久久中文网 | 国产成人精品av久久 | 精品中文字幕在线观看 | 草久久影院 | 国产va饥渴难耐女保洁员在线观看 | 国产精品免费视频久久久 | 久久精品一区二区国产 | 国产精品久久久久久影院 | 久久免费视频一区 | 在线免费高清一区二区三区 | 激情综合色综合久久 | 午夜久久久久久久久久影院 | 久草线| 狠狠操91| 91在线影视 | 欧美在线观看视频一区二区三区 | 亚洲国产精品久久 | 中文字幕资源在线 | 麻豆久久| 黄色软件在线观看 | 欧美日韩精品在线免费观看 | 狠狠操狠狠插 | 国产视频综合在线 | 日韩欧美精品免费 | 久久露脸国产精品 | 成人国产精品一区 | 91传媒免费观看 | www.夜色.com | 在线观看av中文字幕 | 日韩中字在线 | 黄网站色欧美视频 | 亚洲黄a | 国内一区二区视频 | 四虎国产视频 | 又黄又爽又无遮挡免费的网站 | 尤物97国产精品久久精品国产 | 国产亚洲字幕 | 黄色av成人在线 | 亚洲精品乱码久久久久久按摩 | 中文字幕精品一区久久久久 | 国产精品视频资源 | 91福利社区在线观看 | 欧美久久久久久久久中文字幕 | 国产亚洲一区二区三区 | 久久成年人网站 | 92国产精品久久久久首页 | 欧美福利精品 | 九九免费精品视频在线观看 | 亚洲欧美日本国产 | 国产在线观看高清视频 | 国产精品白丝jk白祙 | 国产91电影在线观看 | 亚洲日本精品视频 | 日韩欧美在线免费观看 | 中文字幕精品在线 | 日韩国产欧美视频 | 久久综合狠狠综合 | 国产性xxxx | 欧美韩国在线 | 精品久久91 | 国产精品免费观看久久 | 久久av在线 | 探花视频免费在线观看 | 国产精品免费av | 91高清不卡| 婷婷在线资源 | 国产永久免费高清在线观看视频 | 久久天天综合网 | 黄色一级大片免费看 | 在线日韩一区 | 国产视频99 | 麻豆久久一区二区 | 349k.cc看片app | 久久爱综合 | 九九99| 国产精品视频线看 | 国产流白浆高潮在线观看 | 日韩高清无线码2023 | 免费高清看电视网站 | 日韩在线观看你懂得 | 草久在线| 中文乱码视频在线观看 | 日韩免费在线看 | 成人av电影免费在线播放 | 尤物九九久久国产精品的分类 | 久久欧美视频 | 亚洲一本视频 | 中文久久精品 | 在线亚洲欧美视频 | 免费在线观看av | 久久69av| 在线观看免费版高清版 | 国产精品第2页 | 一区国产精品 | 97超碰人人澡人人爱学生 | a天堂免费| 国产精品久久久久av福利动漫 | 国产 亚洲 欧美 在线 | 91精品一区二区三区蜜桃 | 国产另类xxxxhd高清 | 天天色天天操天天爽 | 韩国在线一区 | 2023天天干 | 国产视频精品久久 | 91麻豆操 | 欧美亚洲xxx| 日本女人在线观看 | 成人av电影免费在线观看 | 国产精品资源在线 | 午夜视频99 | 亚洲欧美色婷婷 | 91精品国产成人 | 国产一区二区不卡视频 | 中文字幕资源网在线观看 | 亚洲欧洲国产日韩精品 | 一区二区三区国产欧美 | 午夜电影中文字幕 | 亚洲天堂色婷婷 | 精品黄色在线观看 | 最新成人av | 国产高清在线精品 | 日韩三级视频在线观看 | 久久欧美精品 | 久久精品欧美一 | 久久亚洲私人国产精品va | 久99久在线视频 | 色在线亚洲 | 亚洲一区二区三区精品在线观看 | 久久视频这里有精品 | 久久久久久久久久久精 | 九九免费在线观看视频 | 狠狠色噜噜狠狠狠合久 | 一级片免费观看视频 | 精品国产乱码一区二区三区在线 | 一区二区视频在线免费观看 | 婷婷六月综合亚洲 | 国产午夜精品一区二区三区欧美 | 成人一区二区在线观看 | 欧美日韩在线免费观看 | 天天婷婷 | 久久天天躁夜夜躁狠狠85麻豆 | 24小时日本在线www免费的 | 夜夜爱av | 中文字幕亚洲综合久久五月天色无吗'' | 国产精品美女免费 | 亚洲精品白浆高清久久久久久 | 亚洲精品va| 日本黄色片一区二区 | 免费色视频 | 国产黄a三级三级三级三级三级 | 亚洲人成在线电影 | 偷拍精偷拍精品欧洲亚洲网站 | 精品1区2区3区 | 日韩毛片精品 | 99热最新在线 | av免费看在线 | 在线免费观看麻豆视频 | 欧美国产日韩一区二区三区 | 亚洲精品中文在线观看 | 中文字幕久久亚洲 | 色在线免费视频 | 狠狠操电影网 | 国产麻豆剧传媒免费观看 | 国产小视频福利在线 | 九九久久视频 | 午夜视频一区二区三区 | 色婷婷88av视频一二三区 | 婷婷资源站 | 成人福利在线播放 | 天天婷婷| 国内丰满少妇猛烈精品播放 | 91黄色在线观看 | www.com.日本一级 | 亚洲激情 欧美激情 | 免费网址你懂的 | 亚洲国产视频直播 | 91亚洲在线| 久久免费看视频 | 国产午夜在线观看 | 久草免费在线视频观看 | 久草在线最新 | 国产成人一区二区三区 | 经典三级一区 | 日韩av中文字幕在线免费观看 | 日本精品视频网站 | 黄色影院在线播放 | 精品久久久国产 | 干综合网| 精品成人在线 | 亚洲国产视频网站 | 91麻豆精品国产午夜天堂 | 国产精品久久婷婷六月丁香 | 91在线一区二区 | 成人激情开心网 | 久久女同性恋中文字幕 | 999久久久国产精品 高清av免费观看 | 国产一级黄色片免费看 | 在线欧美国产 | 91观看视频| 成人免费在线观看电影 | 国产小视频在线 | 色天天| 99在线精品免费视频九九视 | 久久免费在线视频 | 91精品国产高清 | 婷婷免费在线视频 | 精品国产视频一区 | 国产又粗又猛又色又黄视频 | 国产黑丝一区二区 | 免费影视大全推荐 | 三级在线视频观看 | 午夜视频在线网站 | 一区二区三区在线看 | 成人网页在线免费观看 | 最近中文国产在线视频 | 久久久久激情 | 91欧美视频网站 | 中文字幕第一页在线视频 | 日本婷婷色 | 国产亚洲高清视频 | 黄色www在线观看 | 九九导航 | 婷婷伊人五月 | 青青草华人在线视频 | 天天操天天添 | 国产精品18久久久久久首页狼 | 天天舔夜夜操 | 国产色久 | 亚洲一区二区三区四区在线视频 | 91视频久久久久久 | 国产破处视频在线播放 | 午夜电影av | 亚洲精品在线免费看 | 精品国产乱码久久久久久久 | 亚洲综合成人专区片 | 很黄很污的视频网站 | 欧美日韩国产一区 | 韩日精品在线 | 日韩在线看片 | 免费a视频| 成人一区二区在线 | 国产91成人 | 国产视 | 中文字幕国产 | 国产精品ⅴa有声小说 | 婷婷夜夜 | 国产精品video | 精品久久久久久亚洲 | 免费亚洲电影 | 亚洲国产三级在线观看 | 国产精品高 | 日韩精品免费一区二区三区 | 国产中出在线观看 | 亚洲精品网站 | 最近免费中文字幕mv在线视频3 | 日韩在线观看小视频 | 国产精选在线 | 97在线精品 | 国内少妇自拍视频一区 | 色窝资源 | 亚洲欧洲精品视频 | 色综合久久综合中文综合网 | 久久精品久久精品 | 特级西西www44高清大胆图片 | 激情五月av | 91激情在线视频 | 日日干夜夜爱 | av一二三区| 97人人精品 | 91网页版免费观看 | 亚洲精品乱码久久久久久9色 | 国产中文欧美日韩在线 | 久久精品人人做人人综合老师 | 五月天综合网站 | 黄色影院在线观看 | 久久久久久久久久久久久国产精品 | 福利网址在线观看 | 国产v亚洲v| 久久久99精品免费观看 | 欧美色婷婷 | 天天色天天操天天爽 | 国产不卡视频 | 偷拍区另类综合在线 | 久久a久久 | 日韩高清一二三区 | 欧美精品在线观看 | 国产成人精品国内自产拍免费看 | 99久久精品日本一区二区免费 | 国产不卡免费av | 亚洲在线高清 | 亚洲欧美国产精品 | 天天摸天天干天天操天天射 | 日韩小视频 | 亚洲精品小区久久久久久 | 日本精品中文字幕在线观看 | 午夜精品久久久 | 亚洲第一中文网 | 天天干,天天草 | 亚洲精品玖玖玖av在线看 | 日本丶国产丶欧美色综合 | 亚洲国产日韩一区 | 夜夜干夜夜 | 成人在线中文字幕 | 成人网中文字幕 | 黄色成人免费电影 | 精品国产免费人成在线观看 | 国产精品免费观看在线 | 99精品久久99久久久久 | 婷婷网在线 | 色综合久久中文综合久久牛 | 一本一本久久a久久精品综合 | 91免费视频网站在线观看 | 天天艹天天操 | 国产精品一区二区三区在线 | 国产黄色精品在线观看 | 国产99久久精品一区二区300 | 久久撸在线视频 | 日本九九视频 | 国产精品久久影院 | 一区二区 精品 | 久久婷婷久久 | 色av婷婷 | av一区二区在线观看中文字幕 | 久草在线免费新视频 | 色瓜 | 精品国产免费看 | 超级碰99 | 亚洲国产精品99久久久久久久久 | 天天综合入口 | 亚洲专区免费观看 | 国产精品久久久久久久久久久不卡 | 日批视频 | 综合久久五月天 | 懂色av懂色av粉嫩av分享吧 | 国产原厂视频在线观看 | 国产99视频在线观看 | 人人插人人玩 | 成人亚洲精品国产www | 美女免费视频观看网站 | 一级黄色片在线免费观看 | 欧美日韩a视频 | 九九久久成人 | 国产精品白浆视频 | 91麻豆精品国产自产在线 | 五月婷婷在线视频观看 | 亚洲男女精品 | 日韩三级视频在线观看 | 中国黄色一级大片 | 欧美国产精品久久久久久免费 | 天天射天天操天天干 | 天天射天天色天天干 | 国产精品久久久久一区二区三区 | 在线视频 国产 日韩 | 黄色三级网站在线观看 | 日日爽天天| 欧美精品少妇xxxxx喷水 | 国产视频在线观看免费 | 91丨九色丨国产在线观看 | 在线看av网址 | 狠狠的日 | 国产精品白浆视频 | 日韩黄色在线观看 | 亚洲va欧美va人人爽春色影视 | 免费看的毛片 | 久久手机精品视频 | 在线欧美最极品的av | 国产精品视频久久久 | 丁香资源影视免费观看 | 免费看的黄色 | 成人禁用看黄a在线 | 高潮毛片无遮挡高清免费 | 欧美日本三级 | 色国产在线 | 国产在线精品二区 | 免费观看国产成人 | 日韩欧美一区二区三区黑寡妇 | 国产精品一区二区在线看 | 日韩精品免费在线视频 | 综合久久久久久久 | 色久综合| 婷婷色中文 | 久草免费福利在线观看 | 18女毛片 | av免费看在线 | 国产亚洲精品久久久久动 | 日韩精品 在线视频 | 免费日韩 精品中文字幕视频在线 | av大全免费在线观看 | 久草在线看片 | 91在线看视频免费 | sm免费xx网站 | 久久男人中文字幕资源站 | 日韩啪视频| 激情 一区二区 | 国产r级在线观看 | 91精品久久久久久久91蜜桃 | 午夜精品久久久久久久久久久久 | av在线播放中文字幕 | 亚州精品在线视频 | 国产99久久精品一区二区300 | 国产v在线观看 | 天天色天天操综合网 | 高清精品视频 | 国产一级免费观看 | 久久精品一区二区国产 | 日韩高清在线一区 | 欧美成人一二区 | 日韩一区二区三区观看 | 黄p在线播放 | 日韩综合一区二区 | 国产一级二级视频 | av免费网页 | 国产高清在线a视频大全 | 99久久99久久综合 | 在线亚洲午夜片av大片 | 在线观看视频一区二区三区 | 欧美一级激情 | 亚洲国产精品va在线 | 精品久久久久久久久中文字幕 | 91精品毛片| 国产午夜精品一区二区三区嫩草 | 久久久久久草 | 一区二区精品在线视频 | 午夜在线观看一区 | 久草99| 999热线在线观看 | 国产精品毛片完整版 | 中文字幕乱码亚洲精品一区 | 久草在线一免费新视频 | 一级a毛片高清视频 | 婷婷丁香自拍 | 玖玖爱在线观看 | 99视频免费观看 | 久久久视频在线 | 91香蕉久久 | 日韩精品一区二区在线 | 人人狠| 四虎4hu永久免费 | 看黄色91| 一级a性色生活片久久毛片波多野 | 国产精品久久久电影 | 日韩高清在线一区二区三区 | 久久看片网站 | 欧美日韩国产一区二区三区在线观看 | 精品美女视频 | 在线播放 日韩专区 | 18国产精品白浆在线观看免费 | 美女网站黄免费 | 久久久久97国产 | www.操.com| 免费人成网ww44kk44 | 国产视频中文字幕在线观看 | 国产精品三级视频 | 在线观看免费av网站 | 久久免费99精品久久久久久 | 国产精品免费一区二区 | 久久国产a | 成人亚洲综合 | 黄色小说免费在线观看 | 婷婷去俺也去六月色 | 欧美精品资源 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 午夜久久久久久久 | 娇妻呻吟一区二区三区 | 精品在线免费视频 | 综合色婷婷 | 国产成人精品av在线观 | 99性视频 | 久久草草热国产精品直播 | 久久视频在线 | 五月天伊人网 | www.av免费| 一区二区三区高清在线观看 | 蜜臀久久99精品久久久无需会员 | 亚洲综合成人av | 亚洲精品国产精品国自产在线 | 在线观看日韩中文字幕 | 成人在线一区二区 | 国产精品福利在线播放 | 一区二区视频在线播放 | 在线 精品 国产 | 99久热在线精品视频观看 | 久久综合狠狠综合 | 992tv又爽又黄的免费视频 | 91在线入口 | 在线免费观看国产黄色 | 免费观看完整版无人区 | 友田真希x88av | 国产精品久久久一区二区 | 国产在线不卡视频 | 91人人网 | 狠狠色综合网站久久久久久久 | 中文在线免费一区三区 | 色婷婷一 | 一区二区三区在线观看 | 92国产精品久久久久首页 | 成人91视频 | 天天操天天射天天 | 精品一二三四五区 | 91日韩精品视频 | 国产精品九色 | 少妇bbbb搡bbbb搡bbbb| 狠狠狠狠狠狠天天爱 | 国产一区二区在线免费播放 | 免费三级a | 一区二区三区四区精品 | 日韩免费在线视频观看 | 丁香婷婷综合激情 | 日韩高清片| 国产黄色免费电影 | 91亚洲精品久久久中文字幕 | 久久不射电影院 | 国产麻豆传媒 | 精品视频专区 | 久久9999久久免费精品国产 | 欧美激情精品久久久久久变态 | 成人aaa毛片 | www夜夜操com | 一区三区视频在线观看 | 亚洲综合在线一区二区三区 | 香蕉一区| 五月天电影免费在线观看一区 | 国语对白少妇爽91 | 欧美成人a在线 | 香蕉视频色 | 日本精品一区二区在线观看 | 久久精品一区二区国产 | 日韩视频 一区 | 午夜av在线播放 | 久久字幕 | 黄色的视频 | 亚洲更新最快 | 国内精品美女在线观看 | 国产精品久久久久免费 | 色 中文字幕 | 91精品办公室少妇高潮对白 | 亚洲男男gaygay无套 | 国产乱视频| 色噜噜日韩精品欧美一区二区 | 黄色资源网站 | 久久久久久久久久久成人 | 亚洲精品成人网 | 日韩成人免费观看 | 99精品国产一区二区三区不卡 | 国产在线播放不卡 | 亚洲精选在线 | 欧美 日韩 成人 | 国产成人在线精品 | 国产成人精品一二三区 | 国内精品在线看 | 日本三级不卡 | 又色又爽又激情的59视频 | 国产91亚洲精品 | 久久99久久99免费视频 | 日本老少交 | 久草在线视频在线观看 | 人人玩人人添人人澡超碰 | 亚洲欧美日韩精品久久久 | 欧美一区影院 | www久久 | 丁香五月亚洲综合在线 | 韩国视频一区二区三区 | 色狠狠狠 | 国产又黄又爽无遮挡 | 综合久久久久久久 | 日韩精品综合在线 | 激情 亚洲| 国产精品美女在线观看 | 国产又粗又长又硬免费视频 | 草久久久 | 天堂在线免费视频 | 天天干天天想 | 久久天天躁狠狠躁亚洲综合公司 | av网站在线观看免费 | 日本久久高清视频 | 午夜久久久精品 | 97在线视频免费看 | 亚洲精品国产精品久久99 | 国产五月色婷婷六月丁香视频 | 国产精品中文久久久久久久 | 精品国产乱子伦一区二区 | 天天色欧美| 午夜久久久精品 | 中文字幕文字幕一区二区 | 色综合天天视频在线观看 | 久精品视频在线 | 超碰在线人人艹 | 久久国产亚洲 | 久久精品www人人爽人人 | 97超碰人人澡 | 日韩在线观看第一页 | 久久久久久久久久久久久久电影 | 日韩在线视频免费播放 | 精品久久久久国产 | 中文字幕免费在线 | 黄色小说网站在线 | 精品国产诱惑 | 免费在线观看av的网站 | 国产手机在线精品 | 久久久久久久久久电影 | 国产视频在线看 | 国产精品扒开做爽爽的视频 | 日韩欧美精品在线观看视频 | 欧美日韩p片 | 欧美精品中文在线免费观看 | 国产精品a级 | 久久精品国亚洲 | av黄色在线观看 | 日韩一区二区三区在线看 | 西西人体www444 | 欧美狠狠操 | 国产黄网站在线观看 | 日韩在线视 | 中文字幕在线日亚洲9 | 又色又爽又黄高潮的免费视频 | 婷婷激情av| 亚洲综合网站在线观看 | 天天干天天操天天操 | 国产99久久九九精品 | 中文字幕av全部资源www中文字幕在线观看 | 91爱在线 | 在线观看免费av网 | 成人黄色av免费在线观看 | 天天色天天射天天操 | 天天av综合网 | www.国产精品| av在线播放亚洲 | 久久精品毛片 | 免费在线一区二区 | 久久成人国产精品入口 | 日韩久久久久久久久久久久 | 亚洲福利精品 | 国产精品久久久久免费a∨ 欧美一级性生活片 | 在线观看亚洲国产精品 | 日韩欧美视频二区 | 亚洲三级毛片 | 色99色| 国产一区视频在线 | 日韩二三区 | 玖玖视频在线 | av在线精品 | 啪啪资源| 色视频 在线 | 亚洲五月婷婷 | 伊人天天狠天天添日日拍 | 91在线免费视频观看 | 日韩一区二区三区免费视频 | 免费在线观看视频a | 午夜视频在线观看一区二区三区 | 中文字幕亚洲欧美日韩2019 | 91伊人| 天天躁天天躁天天躁婷 | 中文字幕精品视频 | 最近高清中文在线字幕在线观看 | 色先锋av资源中文字幕 | 欧美一区二区日韩一区二区 | 国产精品综合久久久久 | 日日干av| 成 人 黄 色 视频 免费观看 | 在线看国产一区 | 日韩激情av在线 | 狠狠狠色| 超级碰碰免费视频 | 人人澡超碰碰97碰碰碰软件 | 探花视频在线观看+在线播放 | 成人资源在线观看 | 免费能看的黄色片 | 日韩一区二区三免费高清在线观看 | 一区二区三区视频 | 香蕉久久国产 | 在线观看亚洲视频 | 3d黄动漫免费看 | 狠狠色丁香婷婷综合基地 | 五月婷婷伊人网 | 国产一级免费片 | 国产一级免费播放 | 国产精品久久一 | 黄色高清视频在线观看 | 欧美色图亚洲图片 | 天天曰天天 | 欧美成年网站 | 九九九九色 | 黄色1级大片 | 成人在线免费观看视视频 | 另类老妇性bbwbbw高清 | 国产99久久久精品 | 久草在线视频精品 | 国产精品毛片一区二区在线看 | 97在线观看免费高清完整版在线观看 | 欧美日韩综合在线 | 黄色电影小说 | 五月天色丁香 | 日日爽 | 国产在线理论片 | 国产一级片免费观看 | 成人九九视频 | 91在线免费播放 | 日韩精品久久久久久久电影99爱 | 久久精品男人的天堂 | 天天操天天干天天插 | 四虎在线观看网址 | 亚洲做受高潮欧美裸体 | av片无限看 | 97av视频 | 亚洲无毛专区 | 91插插影库 | 精品嫩模福利一区二区蜜臀 | 久久草在线精品 | 91黄视频在线观看 | 国产成人一区二区啪在线观看 | 国产精品ⅴa有声小说 | 521色香蕉网站在线观看 | 天天爽夜夜爽人人爽一区二区 | 久久免费一 | 亚洲精品99久久久久中文字幕 | 国产a级片免费观看 | 日日操夜 | 亚洲精品99| 亚洲精品456在线播放乱码 | 欧美日韩综合在线 | 最新日韩中文字幕 | 欧美精选一区二区三区 | av大片免费看 | 99久久精品免费看国产麻豆 | 久久久久久久久久久久久久av | 亚洲高清视频在线观看免费 | 97国产在线 | 正在播放国产一区 | 最近日本中文字幕 | 夜添久久精品亚洲国产精品 | 玖玖爱国产在线 | 天天干天天干天天 | 日本久久久久久久久久久 | 欧美色图亚洲图片 | 夜又临在线观看 | 午夜婷婷在线观看 | 亚洲午夜激情网 | 国产无吗一区二区三区在线欢 | 最近最新中文字幕 | av线上免费观看 | 国产视频在线观看一区二区 | 五月香视频在线观看 | 日韩精品在线看 | 九九免费在线视频 | 97电影手机| 97国产超碰在线 | 在线免费观看黄 | 欧美激情综合五月色丁香小说 | 国产精品久久久久四虎 | 2023年中文无字幕文字 | 黄网av在线 | 一区二区三区免费在线观看 | 丝袜精品视频 | 日韩最新在线 | 黄色毛片大全 | 毛片888 | 久草视频在线免费播放 | 欧美网址在线观看 | 国产午夜影院 | 91亚洲精品国偷拍 | 久久免费看毛片 | 毛片网免费 | 青青五月天 | 亚洲自拍自偷 | 一区中文字幕电影 | 毛片一区二区 | 色婷婷播放 | 久久成人精品电影 | 久久久综合九色合综国产精品 | 欧美男男tv网站 | 久草精品在线播放 | 少妇搡bbbb搡bbb搡aa | 91热这里只有精品 | 亚洲三级视频 | 亚洲最新在线视频 | 91成人在线视频 | 美女黄久久 | 97超碰人人澡 | 九九热久久久 | 国产精品一区二区三区四 | 日韩精品一二三 | 国产高清免费在线观看 | 婷婷在线五月 | 成人免费看片网址 | 国产明星视频三级a三级点| 中文字幕一区二区三区精华液 | 丁香视频免费观看 | 四虎成人精品永久免费av | 日日爱网址 | 久久综合狠狠综合久久综合88 | 久草在线视频新 | 成年人在线免费视频观看 | 成人一区二区在线 | 四虎国产免费 | 色天天综合久久久久综合片 | 国产999在线观看 | 一级免费黄视频 | 五月天久久 | 国产又粗又猛又爽 | 日韩手机在线观看 | 精品久久1 | 日韩成人欧美 | 欧美怡红院 | 精品色999| 亚洲国产一区二区精品专区 | 欧美日韩在线视频一区二区 | 99在线精品观看 | 婷婷色六月天 | 日韩欧美在线不卡 | 欧美性生交大片免网 | 久草在线视频免费资源观看 | 国产一区二区三区免费视频 | 国产精品中文字幕在线播放 | 中文字幕传媒 | 中文字幕av在线不卡 | 国产成人久久精品 | 日韩中文在线视频 | 菠萝菠萝在线精品视频 | 99热免费在线 | 在线观看国产日韩欧美 | 久久精品1区2区 | 五月黄色 | 亚洲精品国产综合99久久夜夜嗨 | 激情开心 | 免费看的黄色的网站 | 人人爽人人爽人人爽人人爽 | 久久久久免费 | 久久电影色 | 久久久电影网站 | 国产999精品久久久久久麻豆 | 国产精品高潮呻吟久久久久 | 在线之家官网 | 丁香婷婷社区 | 激情五月播播久久久精品 | 欧美a在线看 | av在线之家电影网站 | 97国产在线观看 | 精精国产xxxx视频在线播放 | av综合av | 91激情视频在线 | 国产剧情一区 | 亚洲精品高清在线观看 | 99精品视频免费看 | 久久亚洲福利视频 | 国产午夜影院 | 又爽又黄在线观看 | 日韩av电影中文字幕在线观看 | 国产精品久久久久久欧美 | 日本三级全黄少妇三2023 | 欧美aaa一级| 字幕网资源站中文字幕 | 岛国av在线不卡 | 欧美成人va | 久久免费视频这里只有精品 | 日韩在线网 | 免费看久久久 | 免费下载高清毛片 | 人人澡人人澡人人 | 久草在线资源网 | 久久久亚洲网站 | 久久九九国产精品 | 最近最新最好看中文视频 | 久久五月婷婷丁香 | 国精产品999国精产品视频 | 在线之家免费在线观看电影 | 98精品国产自产在线观看 | 天天天天天天天天操 | 在线观看你懂的网址 | 国产日韩欧美在线播放 | 91精品一区国产高清在线gif | www.天天草| 9999在线视频 | 日韩在线观看电影 | 国产成人精品电影久久久 | 日韩av一区二区在线影视 | 亚洲精品综合在线观看 | 国产亚洲精品久久久久久 | 亚洲高清91 | 久久久国产一区二区三区 | 精品免费视频. | 最新国产精品视频 | 久草在线久草在线2 | 热久久最新地址 | 亚洲精品视频免费 | 天天综合网久久 | 91网站在线视频 | 色a资源在线 | 亚洲九九精品 | 欧美 亚洲 另类 激情 另类 | 国产成人精品一区二三区 | 黄色毛片视频免费观看中文 | 99精品黄色片免费大全 | 婷婷激情五月 | 六月色播| 中文字幕在线观看免费高清完整版 | 欧美电影在线观看 | 91女人18片女毛片60分钟 | 日本精品一区二区 | 中文字幕丝袜制服 | 丝袜护士aⅴ在线白丝护士 天天综合精品 | 狠狠干2018| 久草视频在线新免费 | 亚洲综合激情网 | 国产精品a成v人在线播放 | 中文字幕在线观看1 | 精品视频成人 | 五月天婷婷在线播放 | 久久99精品波多结衣一区 | 久久永久视频 | 亚洲三级视频 | 亚洲区精品视频 | 在线观看色视频 | 美女视频一区 | 国产精品理论片在线播放 | 最新国产精品拍自在线播放 | 九九九九色 | 国产精品美女久久久网av | 欧美伦理电影一区二区 | 日日夜夜人人精品 | 欧美一区二区三区在线视频观看 | 天天干天天操av | 久久久免费国产 | 国产亚洲精品免费 | 超碰人人舔 | 亚洲精品视频观看 | 91成版人在线观看入口 | 开心激情五月婷婷 | 99精品热视频只有精品10 | 久久 亚洲视频 | 亚洲黄色在线 | 久久久国内精品 | 91在线免费观看国产 | 国产精品视屏 | 国产精品免费成人 | 天天狠狠操 | 中文字幕在线观看91 | 成人黄色av网站 | 国产精品com| 五月婷丁香 | 久久久久免费网 | 久久蜜臀一区二区三区av | 国产a高清 | 中文字幕中文中文字幕 | 中文字幕日韩有码 | 久久tv| 久久夜色精品国产欧美乱极品 | 中文字幕在线不卡国产视频 | 国产精品理论片在线观看 | 国产麻豆精品传媒av国产下载 | 久久夜色精品国产欧美乱极品 | 成人免费视频网站在线观看 | 国产亚洲成av人片在线观看桃 | 香蕉精品视频在线观看 | 狠狠干.com| 日韩在线视频二区 | 一级黄色片网站 | 久久九九免费视频 | 91麻豆精品国产自产在线 | 粉嫩高清一区二区三区 | 天天综合中文 | 成人中心免费视频 | 粉嫩aⅴ一区二区三区 | 国产精品国产亚洲精品看不卡 | 韩国av电影在线观看 | 久久久久久黄色 | 国产短视频在线播放 | 欧美色综合| 日韩一三区 | 国产麻豆剧果冻传媒视频播放量 | 国产精品久久三 | 色噜噜色噜噜 | 亚洲视频免费在线观看 | 国产成人久| 97超碰中文字幕 | 日日夜夜网站 | 三级免费黄色 | 狠狠色丁香九九婷婷综合五月 | 成人一级黄色片 | 超级碰碰免费视频 | 国产成人三级一区二区在线观看一 | av综合av | 中日韩欧美精彩视频 | 521色香蕉网站在线观看 | 久久免费电影 | 国产一区二区在线免费播放 | 五月天综合网 | 国产特级毛片aaaaaa毛片 | 久草视频中文在线 | 久久综合久久久久88 |