为什么我的线程似乎失败后几个线程已经开始在iOS上? [英] Why does my threading seem to fail after a few threads have started on iOS?

查看:146
本文介绍了为什么我的线程似乎失败后几个线程已经开始在iOS上?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个代码在 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 委托调用:



dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {

AVPlayerItem * playerItem = [AVPlayerItem playerItemWithURL:[webUrls objectAtIndex:indexPath .row]];
CMTime timeduration = playerItem.duration;
float seconds = CMTimeGetSeconds(timeduration);
NSString * duration = [NSString stringWithFormat:@%f,seconds] b
$ b dispatch_async(dispatch_get_main_queue(),^ {

UITableViewCell * updatecell = [tblView cellForRowAtIndexPath:indexPath];
updatecell.detailTextLabel.text = duration;
[updatecell setNeedsLayout];
});
});

每个单元格在后台缓慢加载导入单元格中的 updatecell.detailTextLabel.text 。问题是我滚动后,约3或4个单元格加载后,其余只是显示0在detailTextLabel快速,不加载。



任何想法为什么这是?

解决方案

有几个想法:


  1. 许多服务器对从给定客户端接受的并发请求数量有约束。我建议您使用 NSOperationQueue 将您对服务器的并发请求数限制为4或5,而不是使用调度队列。


  2. 您可能会使问题比需要的更糟糕,因为如果您向下滚动表格视图,然后备份,当您重新显示前几个单元格,下载 AVPlayerItem 并尝试对您的服务器发出其他并发请求。您真的应该保存您之前下载的结果,以避免对相同数据的冗余重新请求。


  3. 您目前未检查查看刚下载的单元格是否在尝试更新UI之前仍然可见。你真的应该检查一下。


所以,我可能会建议如下:


  1. 在您的视图控制器的 viewDidLoad 中,创建 NSOperationQueue 我们将用于下载。还指定您的服务器将允许多少并发操作:

      downloadQueue = [[NSOperationQueue alloc] init]; 
    downloadQueue.maxConcurrentOperationCount = 4; //用适当的值替换你的服务器


  2. webUrls ,它是一个 NSURL 对象的数组。在下面的第4点,我们将讨论退出该数组,并创建一个新的行对象数组。但在我们可以这样做之前,我们应该创建这个新的 RowData 对象。



    webURL ,还有其他一些事情,比如 durationText ,甚至还有 AVPlayerItem 本身。 (通过保留这些其他对象属性,当单元格回滚到视图中时,我们不需要重新下载数据。)因此,这个新类的公共接口可能如下所示:

      // 
    // RowData.h
    //

    #import< Foundation / Foundation.h>

    @class AVPlayerItem;

    @interface RowData:NSObject

    @property(nonatomic,strong)NSURL * webURL;
    @property(nonatomic,strong)NSString * durationText;
    @property(nonatomic,strong)AVPlayerItem * playerItem;
    @property(nonatomic,getter = isDownloaded,readonly)BOOL下载;
    @property(nonatomic,getter = isDownloading,readonly)BOOL下载;

    - (void)downloadInQueue:(NSOperationQueue *)队列完成:(void(^)(BOOL success))block;
    - (void)cancelDownload;

    @end

    顺便说一句,类名, RowData 。这有点太含糊。但我不知道你的模型数据的性质,以建议一个更好的名字。


  3. 您的新 RowData 类可以有一个实例方法,调用 downloadInQueue ,执行下载,适当地设置 durationText 等。通过移动下载逻辑,我们从下载涉及的某些细节成功地隔离了 cellForRowAtIndexPath 。同样重要的是, downloadInQueue 方法不会更新用户界面本身,而是完成 cellForRowAtIndexPath 提供(在下面的第5点演示),因此这个 downloadInQueue 方法不必担心UI注意事项。无论如何, downloadInQueue 的实现可能如下所示:

      // 
    // RowData.m
    //

    #importRowData.h
    #import< AVFoundation / AVFoundation.h>

    @interface RowData()

    @property(非原子,getter = isDownloaded)BOOL下载;
    @property(nonatomic,getter = isDownloading)BOOL下载;
    @property(nonatomic,weak)NSOperation * operation;

    @end

    @implementation RowData

    - (void)downloadInQueue:(NSOperationQueue *)队列完成: ))completion
    {
    if(!self.isDownloading)
    {
    self.downloading = YES;

    NSOperation * currentOperation = [NSBlockOperation blockOperationWithBlock:^ {
    BOOL success = NO;

    self.playerItem = [AVPlayerItem playerItemWithURL:self.webURL];
    if(self.playerItem)
    {
    success = YES;
    CMTime timeduration = self.playerItem.duration;
    float seconds = CMTimeGetSeconds(timeduration);
    self.durationText = [NSString stringWithFormat:@%f,seconds];
    }
    self.downloading = NO;
    self.downloaded = YES;

    [[NSOperationQueue mainQueue] addOperationWithBlock:^ {
    completion(success);
    }];
    }];

    [queue addOperation:currentOperation];
    self.operation = currentOperation;
    }
    }

    - (void)cancelDownload
    {
    if([self isDownloading]&& self.operation)
    {
    self.downloading = NO;
    [self.operation cancel];
    }
    }

    @end


  4. 在您的主视图控制器中,而不是创建 webUrls 的旧数组,创建一个新的数组 RowData 调用的对象,例如 objects 。当然,为每个 RowData 对象设置 webURL 属性。 (同样,我对 objects 这个模糊的名称不感冒,但是我对你的应用程序不够了解,以提出更具体的建议。 。


  5. 最后,修改你的 cellForRowAtIndexPath 使用此新的 RowData 对象和 downloadInQueue 方法。此外,请注意,完成阻止检查以确保单元格仍然可见:

      RowData * rowData = self.objects [indexPath.row]; 

    if([rowData isDownloaded])
    {
    cell.detailTextLabel.text = rowData.durationText;
    }
    else
    {
    cell.detailTextLabel.text = @... //你真的应该初始化这个,所以我们下载期间显示的东西或删除以前的任何

    [rowData downloadInQueue:self.downloadQueue完成:^(BOOL成功){
    //注意,如果你想根据
    //下载是否成功改变你的行为,只需使用`success'变量

    UITableViewCell * updateCell = [tblView cellForRowAtIndexPath:indexPath];

    //顺便说一句,确保单元格仍在屏幕上

    if(updateCell)
    {
    updateCell.detailTextLabel.text = rowData .durationText;
    [updateCell setNeedsLayout];
    }
    }];
    }


  6. 如果使用iOS 6,一个单元格在屏幕上滚动,可以使用 UITableViewDelegate 协议的 didEndDisplayingCell 方法。

       - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath 
    {
    RowData * rowData = self.objects [indexPath.row];

    if([rowData isDownloading])
    [rowData cancelDownload];
    }

    如果支持早期版本的iOS,您必须使用 UIScrollViewDelegate 协议方法,例如 scrollViewDidScroll ,确定哪些单元格已经从屏幕上滚动




包含在 indexPathsForVisibleRows

顺便说一下,在我的示例 RowData 上面,我保存 AVPlayerItem 。你应该这样做,如果你需要 AVPlayerItem 以后。我们已经保存了 duration ,它实现了 UITableViewCell 所需的所有功能,但我假设您可能需要使用 AVPlayerItem 做一些事情,所以我也保存。但是如果以后不再需要 AVPlayerItem ,那么不要将其保存在 RowData 对象中。另外,我不知道这些是多大,但你可能想写一个 didReceiveMemoryWarning ,将迭代你的对象并将每个项目的 playerItem 对象设置为 nil


I have this code in the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath delegate call:

dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:[webUrls objectAtIndex:indexPath.row]];
    CMTime timeduration = playerItem.duration;
    float seconds = CMTimeGetSeconds(timeduration);
    NSString *duration = [NSString stringWithFormat:@"%f", seconds];

    dispatch_async( dispatch_get_main_queue(), ^{

        UITableViewCell *updatecell = [tblView cellForRowAtIndexPath:indexPath];
        updatecell.detailTextLabel.text = duration;
        [updatecell setNeedsLayout];
    });
});

Each cell slowly, in the background, loads up the seconds into the updatecell.detailTextLabel.text on the cell. The problem is after I scroll, after about 3 or 4 cells loaded, the rest just show 0 in the detailTextLabel quickly and don't load.

Any ideas why this is? Am I not doing my threading correctly?

解决方案

A couple of thoughts:

  1. Many servers impose a constraint of how many concurrent requests they accept from a given client. I'd suggest you use NSOperationQueue to constrain how many concurrent requests you make of your server to 4 or 5, rather than using dispatch queues.

  2. You're potentially making the issue worse than it needs to be, because if you scroll the table view down and then back up, when you redisplay the first few cells, you're re-downloading the AVPlayerItem and trying to make additional concurrent requests of your server. You really should save the results of your previous downloads, to eliminate the need for redundant re-requests of the same data.

  3. You're not currently checking to see if the cell that just downloaded is still visible before trying to update the UI. You really should check that.

So, I might suggest the following:

  1. In your view controller's viewDidLoad, create the NSOperationQueue that we'll use for downloading. Also specify how many concurrent operations your server will permit:

    downloadQueue = [[NSOperationQueue alloc] init];
    downloadQueue.maxConcurrentOperationCount = 4; // replace this with an appropriate value for your server
    

  2. Before, you had an array, webUrls, which was an array of NSURL objects. In point #4, below, we'll discuss retiring that array, and create a new array of row objects. But before we can do that, we should create this new RowData object.

    Each row object will have not only the webURL, but also other things, such as the durationText and perhaps even the AVPlayerItem itself. (By keeping these other object properties, when a cell scrolls back into view, we don't need to re-download the data.) So the public interface for this new class might look like:

    //
    //  RowData.h
    //
    
    #import <Foundation/Foundation.h>
    
    @class AVPlayerItem;
    
    @interface RowData : NSObject
    
    @property (nonatomic, strong) NSURL *webURL;
    @property (nonatomic, strong) NSString *durationText;
    @property (nonatomic, strong) AVPlayerItem *playerItem;
    @property (nonatomic, getter = isDownloaded, readonly) BOOL downloaded;
    @property (nonatomic, getter = isDownloading, readonly) BOOL downloading;
    
    - (void)downloadInQueue:(NSOperationQueue *)queue completion:(void (^)(BOOL success))block;
    - (void)cancelDownload;
    
    @end
    

    By the way, I'm not crazy about the class name, RowData. It's a little too ambiguous. But I don't know enough about the nature of your model data to suggest a better name. Feel free to call this class whatever you think is appropriate.

  3. Your new RowData class can have an instance method, called downloadInQueue, that performs the download, sets the durationText appropriately, etc. By moving the download logic here, we successfully isolate cellForRowAtIndexPath from some of the gory details involved with downloading. Just as importantly, though, this downloadInQueue method won't update the user interface itself, but rather it has completion block provided by cellForRowAtIndexPath (demonstrated in point #5, below), so this downloadInQueue method doesn't have to worry about UI considerations. Anyway, the implementation of downloadInQueue might look like:

    //
    //  RowData.m
    //
    
    #import "RowData.h"
    #import <AVFoundation/AVFoundation.h>
    
    @interface RowData ()
    
    @property (nonatomic, getter = isDownloaded) BOOL downloaded;
    @property (nonatomic, getter = isDownloading) BOOL downloading;
    @property (nonatomic, weak) NSOperation *operation;
    
    @end
    
    @implementation RowData
    
    - (void)downloadInQueue:(NSOperationQueue *)queue completion:(void (^)(BOOL success))completion
    {
        if (!self.isDownloading)
        {
            self.downloading = YES;
    
            NSOperation *currentOperation = [NSBlockOperation blockOperationWithBlock:^{
                BOOL success = NO;
    
                self.playerItem = [AVPlayerItem playerItemWithURL:self.webURL];
                if (self.playerItem)
                {
                    success = YES;
                    CMTime timeduration = self.playerItem.duration;
                    float seconds = CMTimeGetSeconds(timeduration);
                    self.durationText = [NSString stringWithFormat:@"%f", seconds];
                }
                self.downloading = NO;
                self.downloaded = YES;
    
                [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                    completion(success);
                }];
            }];
    
            [queue addOperation:currentOperation];
            self.operation = currentOperation;
        }
    }
    
    - (void)cancelDownload
    {
        if ([self isDownloading] && self.operation)
        {
            self.downloading = NO;
            [self.operation cancel];
        }
    }
    
    @end
    

  4. In your main view controller, rather than creating your old array of webUrls, create a new array of these RowData objects called, for example, objects. Set the webURL property for each of those RowData objects, of course. (Again, I'm not crazy about the ambiguous name of objects, but I don't know enough about your app to make a more specific suggestion. Call this whatever you want. But my code below will use objects.)

  5. Finally, modify your cellForRowAtIndexPath to use this new RowData object and it's downloadInQueue method. Also, note, the completion block checks to make sure the cell is still visible:

    RowData *rowData = self.objects[indexPath.row];
    
    if ([rowData isDownloaded])
    {
        cell.detailTextLabel.text = rowData.durationText;
    }
    else
    {
        cell.detailTextLabel.text = @"..."; // you really should initialize this so we show something during download or remove anything previously there
    
        [rowData downloadInQueue:self.downloadQueue completion:^(BOOL success) {
            // note, if you wanted to change your behavior based upon whether the 
            // download was successful or not, just use the `success` variable
    
            UITableViewCell *updateCell = [tblView cellForRowAtIndexPath:indexPath];
    
            // by the way, make sure the cell is still on screen
    
            if (updateCell)
            {
                updateCell.detailTextLabel.text = rowData.durationText;
                [updateCell setNeedsLayout];
            }
        }];
    }
    

  6. If using iOS 6, if you want to cancel pending downloads when a cell scrolls off the screen, you can use the didEndDisplayingCell method of the UITableViewDelegate protocol.

    - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        RowData *rowData = self.objects[indexPath.row];
    
        if ([rowData isDownloading])
            [rowData cancelDownload];
    }
    

    If supporting earlier versions of iOS, you'd have to use UIScrollViewDelegate protocol methods, such as scrollViewDidScroll, determine which cells have scrolled off the screen (e.g. are not included in indexPathsForVisibleRows) manually, but the idea is the same.

By the way, in my sample RowData above, I'm saving the AVPlayerItem. You should only do that if you need the AVPlayerItem later. We've saved the duration, which achieves all we need for the UITableViewCell, but I assume you might later want to do something with the AVPlayerItem, so I save that, too. But if you're not going to need that AVPlayerItem later, then don't save it in the RowData object. Also, I don't know how big those are, but you might want to write a didReceiveMemoryWarning that will iterate through your objects and set each item's playerItem object to nil.

这篇关于为什么我的线程似乎失败后几个线程已经开始在iOS上?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆