【问题标题】:NSOperationqueue background, download imagesNSOperationqueue 背景,下载图片
【发布时间】:2013-01-21 17:27:56
【问题描述】:

我创建了一个NSOperationQueue 来下载图片(来自 Twitter for Cell):

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
   [queue addOperationWithBlock:^{
    NSString *ImagesUrl = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
        NSURL *imageurl = [NSURL URLWithString:ImagesUrl];
        UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            if (img.size.width == 0 || [ImagesUrl isEqualToString:@"<null>"]) {
                [statusCell.imageCellTL setFrame:CGRectZero];
                statusCell.imageCellTL.image = [UIImage imageNamed:@"Placeholder"] ;
            }else

            [statusCell.imageCellTL setImage:img];

这工作正常,但是当它似乎移动滚动并查看图像时仍在加载,并且它们会更改几次,直到您获得图片。

而且我不喜欢诊断时间档案,所以我想以某种方式在后台制作这个NSOperationQueue

还可以展示如何使“Imagecache”无需下载已下载的图像。

**(状态 = Twitter 时间线的 NSDictionary)。

编辑::(所有单元格)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{


    static NSString *CellIdentifier = @"Celulatime";
    UITableViewCell *Cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];


    if ( [Cell isKindOfClass:[TimeLineCell class]] ) {
        TimeLineCell *statusCell = (TimeLineCell *) Cell;
        status = [self.dataSource objectAtIndex:indexPath.row];


        statusCell.TextCellTL.text = [status objectForKey:@"text"];
        statusCell.NomeCellTL.text = [status valueForKeyPath:@"user.name"];
        statusCell.UserCellTL.text = [NSString stringWithFormat:@"@%@", [status valueForKeyPath:@"user.screen_name"]];


        NSDate *created_at = [status valueForKey:@"created_at"];
        if ( [created_at isKindOfClass:[NSDate class] ] ) {
            NSTimeInterval timeInterval = [created_at timeIntervalSinceNow];
            statusCell.timeCellTL.text = [self timeIntervalStringOf:timeInterval];
        } else if ( [created_at isKindOfClass:[NSString class]] ) {
            NSDate *date = [self.twitterDateFormatter dateFromString: (NSString *) created_at];
            NSTimeInterval timeInterval = [date timeIntervalSinceNow];
            statusCell.timeCellTL.text = [self timeIntervalStringOf:timeInterval];
        }

        NSString *imageUrlString = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
        UIImage *imageFromCache = [self.imageCache objectForKey:imageUrlString];

        if (imageFromCache) {
            statusCell.imageCellTL.image = imageFromCache;
            [statusCell.imageCellTL setFrame:CGRectMake(9, 6, 40, 40)]; 
        }
        else
        {
            statusCell.imageCellTL.image = [UIImage imageNamed:@"TweHitLogo57"];
            [statusCell.imageCellTL setFrame:CGRectZero]; 

            [self.imageluckluck addOperationWithBlock:^{
                NSURL *imageurl = [NSURL URLWithString:imageUrlString];
                UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];

                if (img != nil) {


                    [self.imageCache setObject:img forKey:imageUrlString];

                    // now update UI in main queue
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

                        TimeLineCell *updateCell = (TimeLineCell *)[tableView cellForRowAtIndexPath:indexPath];

                        if (updateCell) {
                            [updateCell.imageCellTL setFrame:CGRectMake(9, 6, 40, 40)]; 
                            [updateCell.imageCellTL setImage:img];
                        }
                    }];
                }
            }];
        }


        }
    return Cell;
    }

【问题讨论】:

  • 作为替代方案,请查看 SDWebImage。它是一个库,可让您在后台下载图像并根据需要自动将它们缓存到内存或磁盘。 github.com/rs/SDWebImage
  • 我发现非常有用..但想从零开始

标签: ios cocoa-touch asynchronous grand-central-dispatch nsoperationqueue


【解决方案1】:

几个观察:

  1. 您可能应该在您的类中定义一个NSOperationQueue 并在viewDidLoad(以及NSCache)中对其进行初始化,并向该队列添加操作,而不是为每个图像创建一个新的NSOperationQueue。此外,许多服务器限制了它们将支持来自每个客户端的并发请求数,因此请确保相应地设置 maxConcurrentOperationCount

    @interface ViewController ()
    @property (nonatomic, strong) NSOperationQueue *imageOperationQueue;
    @property (nonatomic, strong) NSCache *imageCache;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
         [super viewDidLoad];
    
        self.imageOperationQueue = [[NSOperationQueue alloc]init];
        self.imageOperationQueue.maxConcurrentOperationCount = 4;
    
        self.imageCache = [[NSCache alloc] init];
    }
    
    // the rest of your implementation
    
    @end
    
  2. 您的tableView:cellForRowAtIndexPath: 应该 (a) 在开始异步图像加载之前初始化image(这样您就不会从那里看到重用单元格中的旧图像); (b) 在更新之前确保单元格仍然可见:

    NSString *imageUrlString = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
    UIImage *imageFromCache = [self.imageCache objectForKey:imageUrlString];
    
    if (imageFromCache) {
        statusCell.imageCellTL.image = imageFromCache;
        [statusCell.imageCellTL setFrame: ...]; // set your frame accordingly
    }
    else
    {
        statusCell.imageCellTL.image = [UIImage imageNamed:@"Placeholder"];
        [statusCell.imageCellTL setFrame:CGRectZero]; // not sure if you need this line, but you had it in your original code snippet, so I include it here
    
        [self.imageOperationQueue addOperationWithBlock:^{
            NSURL *imageurl = [NSURL URLWithString:imageUrlString];
            UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
    
            if (img != nil) {
    
                // update cache
                [self.imageCache setObject:img forKey:imageUrlString];
    
                // now update UI in main queue
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    // see if the cell is still visible ... it's possible the user has scrolled the cell so it's no longer visible, but the cell has been reused for another indexPath
                    TimeLineCell *updateCell = (TimeLineCell *)[tableView cellForRowAtIndexPath:indexPath];
    
                    // if so, update the image
                    if (updateCell) {
                        [updateCell.imageCellTL setFrame:...]; // I don't know what you want to set this to, but make sure to set it appropriately for your cell; usually I don't mess with the frame.
                        [updateCell.imageCellTL setImage:img];
                    }
                }];
            }
        }];
    }
    
  3. 不需要对UIApplicationDidReceiveMemoryWarningNotification 进行特殊处理,因为尽管NSCache 不会响应此内存警告,但它会在内存变低时自动驱逐其对象。

我没有测试过上面的代码,但希望你能明白。这是典型的模式。你的原始代码检查了[ImagesUrl isEqualToString:@"<null>"],我不知道怎么会这样,但如果你需要一些额外的逻辑,除了我的if (img != nil) ...,然后相应地调整那行。

【讨论】:

  • 几个cmets:(1)也可以用dispatch_async()来完成。仍然 NSOperation 提供了更多的控制。例如,它可能会被取消; (2) 为了将重用单元格的 img 设置为零状态,可以使用以下命令:cell.imageView.image = nil; (3) 为什么要有statusCell.imageCellTL?已经有 cell.imageView 了。
  • @Umka 1. 是的,您可以使用dispatch_async(),但是您无法控制并发请求的数量。这就是为什么我更喜欢NSOperationQueue。 2. 您可以使用cell.imageView.image = nil,但默认的UITableViewCell 有时会根据image 是否存在来重新格式化单元格,所以我总是使用空白图像来避免重新格式化单元格(或者更糟的是,失败)在加载图像时重新格式化单元格)。 3. 您有时会使用自定义单元格布局来避免我在前面提到的问题(或者如果它不符合标准布局)。
  • 一个很好的答案,但是我的 Cell 是自定义单元格(报错)
  • @Umka 另外,虽然我没有演示它,但有时能够取消积压的图像下载请求(例如当您关闭 tableview 时)是件好事。通过使用NSOperationQueue,您可以使用cancelAllOperations。如果您正在处理小缩略图,您通常不必担心它(因为 tableview 可以很好地跟上),但如果不是,能够取消请求是很好的。
  • 是我的错误,现在运行完美:D 谢谢朋友,我这周正在努力工作。
【解决方案2】:

Ray Wenderlich 的一个很好的例子:http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues

它还具有cancel功能,可以在用户按下取消按钮时取消操作。

【讨论】:

    【解决方案3】:

    使用 swift 3 在 tableview 中下载异步图像

    class ViewController: UIViewController {
        var queue = OperationQueue()
    
        let imageURLs = ["https://amazingslider.com/wp-content/uploads/2012/12/dandelion.jpg", "https://media.treehugger.com/assets/images/2011/10/tiger-running-snow.jpg.600x315_q90_crop-smart.jpg", "https://www.w3schools.com/css/trolltunga.jpg", "https://www.w3schools.com/w3css/img_lights.jpg", "https://cdn.pixabay.com/photo/2015/04/28/20/55/aurora-borealis-744351_960_720.jpg", "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2L0pETnybA5sld783iz1mgtOFS8vxBTjB4tYXeRtQWDxig3dc"]
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
        }
    
    
    }
    
    extension ViewController: UITableViewDelegate, UITableViewDataSource {
        func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return imageURLs.count
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell: ImagesTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ImagesTableViewCell
            var img: UIImage!
            let operation = BlockOperation(block: {
                img  = Downloader.downloadImageWithURl(self.imageURLs[indexPath.row])
            })
    
            operation.completionBlock = {
                DispatchQueue.main.async {
                    cell.imgView?.image = img
                }
    
            }
            queue.addOperation(operation)
    
            return cell
        }
    }
    
    class Downloader {
        class func downloadImageWithURl(_ url: String) -> UIImage! {
            if let data = try? Data(contentsOf: URL(string: url)!) {
                return UIImage(data: data)!
            }
            return nil
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-08-17
      • 2015-08-24
      • 2016-12-14
      • 2020-04-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多