Ios NSOperation类运行多个操作

Ios NSOperation类运行多个操作,ios,objective-c,nsoperation,Ios,Objective C,Nsoperation,所以我问了几个关于UICollectionView的问题。了解它的工作原理,我尝试实现延迟加载,将15个图像加载到视图控制器上。我发现了很多例子,…第一个和第三个例子只处理一个操作,第二个例子我认为根本不使用操作,只使用线程。我的问题是,是否可以使用NSOperation类并使用/重用操作?我读到您不能重新运行操作,但我认为您可以在再次初始化操作后重新运行。这是我的密码: 视图控制器: UICollectionViewFlowLayout *layout = [[UICollectionView

所以我问了几个关于UICollectionView的问题。了解它的工作原理,我尝试实现延迟加载,将15个图像加载到视图控制器上。我发现了很多例子,…第一个和第三个例子只处理一个操作,第二个例子我认为根本不使用操作,只使用线程。我的问题是,是否可以使用NSOperation类并使用/重用操作?我读到您不能重新运行操作,但我认为您可以在再次初始化操作后重新运行。这是我的密码:

视图控制器:

UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
layout.sectionInset = UIEdgeInsetsMake(10, 20, 10, 20);
[layout setItemSize:CGSizeMake(75, 75)];
self.images = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 230, self.view.frame.size.width, 200) collectionViewLayout:layout];
self.images.delegate = self;
self.images.dataSource = self;
[self.images registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"cellIdentifier"];
[self.view addSubview:self.images];
self.operation = [[NSOperationQueue alloc]init];
[self.operation addOperationWithBlock:^{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"getgallery.php?user=%@", userId] relativeToURL:[NSURL URLWithString:@"http://www.mywebsite.com/"]];
    NSData *data = [NSData dataWithContentsOfURL:url];
//datasource for all images
    self.imagesGalleryPaths = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
        [self.images reloadData];
//reload collection view to place placeholders
    }];
}];

- (void)viewDidAppear:(BOOL)animated{
//get the visible cells right away
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
self.processedImages = [[NSMutableDictionary alloc]initWithCapacity:visibleCellPaths.count];
}
#pragma mark - collection view
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView{
return 1;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
int i;
if (self.imagesGalleryPaths.count == 0)
    i = 25;
else
    i = self.imagesGalleryPaths.count;
return i;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
UICollectionViewCell *cell = [UICollectionViewCell new];
cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath];
cell.layer.borderWidth = 1;
cell.layer.borderColor = [[UIColor whiteColor]CGColor];
UIImageView *image = [[UIImageView alloc]init];
if (self.imagesGalleryPaths.count != 0) {
    if ([visibleCellPaths containsObject:indexPath]) {
        [self setUpDownloads:visibleCellPaths];
    }
    image.image = [UIImage imageNamed:@"default.png"];
    image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
    [cell.contentView addSubview:image];
}
return cell;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
[self setUpDownloads:visibleCellPaths];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
visibleCellPaths = [NSArray new];
visibleCellPaths = self.images.indexPathsForVisibleItems;
[self setUpDownloads:visibleCellPaths];
}

- (void)setUpDownloads:(NSArray *)visiblePaths{
//I want to pass the visiblePaths to the NSOperation class if visible cells changed
GalleryOps *gallery = [[GalleryOps alloc]init];
//I will use a dictionary to keep track of which indexPaths are being downloaded...
//...so there are no duplicate downloads
}
厨房操作

@implementation GalleryOps

- (void)main{
    //would I initialize all the operations here and perform them?
}

显示空的GalleryOps类几乎毫无意义,因为我不知道如何使用多个操作初始化它。我知道我必须重写main方法,一旦我从URL获取了图像数据,我就需要更新UI,为此我需要一个委托方法……另一件事我还不知道如何做,但有很多例子可以说明这一点。我最大的问题是如何将可见单元格传递给这个类并运行多个操作?当新的可见单元格出现时,我将运行一个检查,查看哪些要取消,哪些要保留。有什么建议吗?提前谢谢

您可以使用队列和操作本身来管理多个操作。如果我没有看错您的问题,那么您有一个操作(从JSON获取图像URL列表)需要生成子操作。您可以通过让父操作将子操作添加到队列中,或者通过使用依赖操作(子操作将父操作作为依赖项)来实现这一点

对于您试图做的事情,您不需要将NSOperation子类化,NSBlockOperation应该满足您的需要。子类化NSOperation比看起来更复杂,因为它依赖于KVO(很容易出错)

但对于你问题的细节:

我的问题是,是否可以使用NSOperation类并使用/重用操作?我读到您不能重新运行操作,但我认为您可以在再次初始化操作后重新运行

如果您再次初始化它们,它们就是新对象(或者至少应该是)。NSO操作无法重新运行,因为它们具有内部状态——我上面提到的棘手的KVO位。一旦它们转到“finished”,该实例就不能返回到干净状态

操作应该是相当轻量级的对象,重用它们不应该有任何显著的价值,并且存在大量的潜在问题。创建新的操作应该是一条路


Apple示例代码“”可能会为您提供一些提示,说明如何完成您正在尝试的操作。

基于
的图像延迟加载操作的组成元素可能包括:

  • 创建用于下载操作的专用
    NSOperationQueue
    。通常,这是配置为4或5的
    maxConcurrentOperationCount
    ,这样您可以享受并发性,但不会超过并发网络操作的最大数量

    如果您不使用此
    maxConcurrentOperationCount
    ,且网络连接速度较慢(如蜂窝网络),则可能会导致网络请求超时

  • 具有支持集合视图或表视图的模型对象(例如数组)。这通常只有图像的一些标识符(例如URL),而不是图像本身

  • 实现缓存机制来存储下载的图像,以防止需要重新下载已下载的图像。有些实现只进行基于内存的缓存(通过
    NSCache
    )。其他(例如)将执行两层缓存,内存(
    NSCache
    ,以获得最佳性能)和辅助永久性存储缓存(这样,当内存压力迫使您清除
    NSCache
    ,您仍然在设备上保存了一个格式副本,因此您不必从网络中重新检索它)。其他人(例如)依靠
    NSURLCache
    将来自服务器的响应缓存到持久存储中

  • 编写用于下载单个图像的
    NSOperation
    子类。您希望使此操作可取消。这意味着两种不同的设计考虑

    • 首先,关于操作本身,您可能希望执行并发操作(请参阅《并发编程指南》中的部分)

    • 第二,关于网络请求,您需要一个可取消的网络请求。如果使用
      NSURLConnection
      ,这意味着使用基于委托的格式副本(不是任何方便的方法)。如果使用
      NSURLConnection
      ,诀窍是必须在持续的运行循环中安排它。有许多技巧可以实现这一点,但最简单的是在主运行循环中调度它(使用
      scheduleinrunlop:forMode:
      )(尽管有更优雅的方法),即使您将从
      NSOperationQueue
      中的操作运行它。就我个人而言,我为此目的启动了一个新的专用线程(像AFNetworking一样),但主运行循环更简单,适合这种过程

      如果使用
      NSURLSession
      ,这个过程可能会稍微容易一些,因为您可以不使用
      dataTaskWithRequest
      的完成块格式副本,而不必进入基于代理的实现(如果您不想)。但这只是iOS 7+版本(如果您需要处理身份验证质询请求之类的奇特操作,那么最终还是会采用基于代理的方法)

    • 结合这两个先前的点,定制的
      NSOperation
      子类将检测何时取消操作,然后取消网络请求并完成操作

    顺便说一句,操作实例永远不会被重用。为正在下载的每个映像创建一个新的操作实例UICollectionViewCell *cell = [UICollectionViewCell new]; cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellIdentifier" forIndexPath:indexPath]; cell.layer.borderWidth = 1; cell.layer.borderColor = [[UIColor whiteColor]CGColor]; UIImageView *image = [[UIImageView alloc]init]; if (self.imagesGalleryPaths.count != 0) { image.image = [UIImage imageNamed:@"profile_default.png"]; image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height); [cell.contentView addSubview:image]; } return cell;
  • if (self.imagesGalleryPaths.count != 0) {
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            [self.images reloadData];
            [self startOperationForVisibleCells];
        }];
    }
    
    [self.operation addOperationWithBlock:^{
        int i=0;
        while (i < visibleCellPaths.count) {
            NSIndexPath *indexPath = [visibleCellPaths objectAtIndex:i];
            if (![self.imageGalleryData.allKeys containsObject:indexPath]) {
                NSURL *url = [@"myurl"];
                NSData *responseData = [NSData dataWithContentsOfURL:url];
                if (responseData != nil) {
                    [self.imageGalleryData setObject:responseData forKey:indexPath];
                    [[NSOperationQueue mainQueue]addOperationWithBlock:^{
                        UICollectionViewCell *cell = [self.images cellForItemAtIndexPath:indexPath];
                        UIImageView *image = [UIImageView new];
                        image.image = [UIImage imageWithData:responseData];
                        image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
                        [cell.contentView addSubview:image];
                    }];
                }
            }
            i++;
        }
    }];
    
    - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    visibleCellPaths = [NSArray new];
    visibleCellPaths = self.images.indexPathsForVisibleItems;
    for (int i=0; i<visibleCellPaths.count; i++) {
        NSIndexPath *indexPath = [visibleCellPaths objectAtIndex:i];
        if ([self.imageGalleryData.allKeys containsObject:indexPath]) {
            UICollectionViewCell *cell = [self.images cellForItemAtIndexPath:indexPath];
            UIImageView *image = [UIImageView new];
            image.image = [UIImage imageWithData:[self.imageGalleryData objectForKey:indexPath]];
            image.frame = CGRectMake(0, 0, cell.frame.size.width, cell.frame.size.height);
            [cell.contentView addSubview:image];
        }else{
            [self startOperationForVisibleCells];
        }
    }
    }
    
    - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    //same functions as previous
    }
    
    static NSString * const kCellIdentifier = @"CustomCellIdentifier";
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init];
        layout.sectionInset = UIEdgeInsetsMake(10, 20, 10, 20);
        [layout setItemSize:CGSizeMake(75, 75)];
    
        // renamed `images` collection view to `collectionView` to follow common conventions
    
        self.collectionView = [[UICollectionView alloc]initWithFrame:CGRectMake(0, 230, self.view.frame.size.width, 200) collectionViewLayout:layout];
        self.collectionView.delegate = self;
        self.collectionView.dataSource = self;
    
        // you didn't show where you instantiated this in your examples, but I'll do it here
    
        self.imageGalleryData = [NSMutableDictionary dictionary];
    
        // register a custom class, not `UICollectionViewCell`
    
        [self.collectionView registerClass:[CustomCell class] forCellWithReuseIdentifier:kCellIdentifier];
        [self.view addSubview:self.collectionView];
    
        // (a) change queue variable name;
        // (b) set maxConcurrentOperationCount to prevent timeouts
    
        self.queue = [[NSOperationQueue alloc]init];
        self.queue.maxConcurrentOperationCount = 5;
    
        [self.queue addOperationWithBlock:^{
            NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"getgallery.php?user=%@", userId] relativeToURL:[NSURL URLWithString:@"http://www.mywebsite.com/"]];
            NSData *data = [NSData dataWithContentsOfURL:url];
            //datasource for all images
            self.imagesGalleryPaths = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
            [[NSOperationQueue mainQueue]addOperationWithBlock:^{
                [self.collectionView reloadData];
            }];
        }];
    }
    
    - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
    {
        return [self.imagesGalleryPaths count];          // just use whatever is the right value here, don't make this unnecessarily smaller
    }
    
    - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
    {
        CustomCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kCellIdentifier forIndexPath:indexPath];
    
        NSString *key = self.imagesGalleryPaths[indexPath.row];                 // I don't know whether this was simply array, or some nested structure, so tweak this accordingly
        UIImage *image = self.imageGalleryData[key]; 
    
        if (image) {
            cell.imageView.image = image;                                       // if we have image already, just use it
        } else {
            cell.imageView.image = [UIImage imageNamed:@"profile_default.png"]; // otherwise set the placeholder ...
    
            [self.queue addOperationWithBlock:^{                                // ... and initiate the asynchronous retrieval
                NSURL *url = [NSURL URLWithString:...];                         // build your URL from the `key` as appropriate
                NSData *responseData = [NSData dataWithContentsOfURL:url];
                if (responseData != nil) {
                    UIImage *downloadedImage = [UIImage imageWithData:responseData];
                    if (downloadedImage) {
                        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
                            self.imageGalleryData[key] = downloadedImage;
                            CustomCell *updateCell = (id)[collectionView cellForItemAtIndexPath:indexPath];
                            if (updateCell) {
                                updateCell.imageView.image = downloadedImage;
                            }
                        }];
                    }
                }
            }];
        }
    
        return cell;
    }
    
    // don't forget to purge your gallery data if you run low in memory
    
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        [self.imageGalleryData removeAllObjects];
    }
    
    //  CustomCell.h
    
    #import <UIKit/UIKit.h>
    
    @interface CustomCell : UICollectionViewCell
    
    @property (nonatomic, weak) UIImageView *imageView;
    
    @end
    
    //  CustomCell.m
    
    #import "CustomCell.h"
    
    @implementation CustomCell
    
    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
            self.layer.borderWidth = 1;
            self.layer.borderColor = [[UIColor whiteColor]CGColor];
    
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
            [self addSubview:imageView];
            _imageView = imageView;
        }
        return self;
    }
        
    @end