iphone - 为什么在iOS上启动了几个线程后,我的线程似乎失败了?

标签 iphone ios multithreading cocoa-touch grand-central-dispatch

我在- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath委托(delegate)调用中有以下代码:

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];
    });
});

每个单元在后台缓慢地将seconds加载到该单元上的updatecell.detailTextLabel.text中。问题是我滚动后,加载了大约3或4个单元后,其余的仅在detailTextLabel中迅速显示为0,并且不加载。

有任何想法为什么会这样吗?我没有正确进行线程吗?

最佳答案

有两个想法:

  • 许多服务器对它们从给定客户端接受的并发请求施加了约束。我建议您使用NSOperationQueue将对服务器的并发请求限制为4或5,而不要使用调度队列。
  • 您可能使问题变得更糟,因为如果您向下滚动表格 View 然后又向上备份,那么当您重新显示前几个单元格时,您将重新下载AVPlayerItem并尝试进行其他并发操作您服务器的请求。您确实应该保存以前下载的结果,以消除重复请求相同数据的需要。
  • 您当前未在尝试更新UI之前检查是否仅看到了刚刚下载的单元格。您确实应该检查一下。

  • 因此,我可能建议以下内容:
  • 在 View Controller 的viewDidLoad中,创建我们将用于下载的NSOperationQueue。还指定您的服务器将允许多少个并发操作:
    downloadQueue = [[NSOperationQueue alloc] init];
    downloadQueue.maxConcurrentOperationCount = 4; // replace this with an appropriate value for your server
    
  • 之前,您有一个数组webUrls,它是一个NSURL对象的数组。在下面的第4点,我们将讨论退出该数组,并创建一个新的行对象数组。但是在我们可以做到这一点之前,我们应该创建这个新的RowData对象。

    每个行对象不仅具有webURL,而且还具有其他内容,例如durationText甚至是AVPlayerItem本身。 (通过保留这些其他对象属性,当单元格滚动回 View 时,我们不需要重新下载数据。)因此,此新类的公共(public)接口(interface)可能类似于:
    //
    //  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
    

    顺便说一句,我并不为类名RowData着迷。这有点模棱两可。但是我对您的模型数据的性质了解不足,无法建议一个更好的名称。无论您认为合适如何,都可以随时致电此类(class)。
  • 您的新RowData类可以具有一个名为downloadInQueue的实例方法,该方法执行下载,适本地设置durationText等。通过在此处移动下载逻辑,我们成功地将cellForRowAtIndexPath与下载中涉及的一些细节相隔离。同样重要的是,虽然此downloadInQueue方法不会更新用户界面本身,但它具有completion提供的cellForRowAtIndexPath块(在下面的第5点处演示),因此,此downloadInQueue方法不必担心UI注意事项。无论如何,downloadInQueue的实现可能看起来像:
    //
    //  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
    
  • 在主 View Controller 中,而不是创建旧的webUrls数组,而是创建这些RowData对象的新数组,例如objects。当然,为每个webURL对象设置RowData属性。 (再次,我对objects的模棱两可的名称并不感到疯狂,但是我对您的应用程序不够了解,无法提出更具体的建议。您可以随意调用此名称。但是下面的代码将使用objects。)
  • 最后,修改您的cellForRowAtIndexPath以使用此新的RowData对象及其downloadInQueue方法。另外,请注意,completion块检查以确保该单元格仍然可见:
    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];
            }
        }];
    }
    
  • 如果使用iOS 6,如果要在单元格滚动离开屏幕时取消待处理的下载,则可以使用didEndDisplayingCell协议(protocol)的UITableViewDelegate方法。
    - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        RowData *rowData = self.objects[indexPath.row];
    
        if ([rowData isDownloading])
            [rowData cancelDownload];
    }
    

    如果支持iOS的早期版本,则必须使用UIScrollViewDelegate协议(protocol)方法(例如 scrollViewDidScroll )来手动确定哪些单元格已滚动出屏幕(例如indexPathsForVisibleRows中不包括这些单元格),但是想法是相同的。

  • 顺便说一句,在上面的示例RowData中,我保存了AVPlayerItem。仅当以后需要AVPlayerItem时,才应该这样做。我们已经保存了duration,它满足了UITableViewCell所需的所有功能,但是我想您以后可能想对AVPlayerItem做些事情,所以我也保存了它。但是,如果以后不再需要该AVPlayerItem,则不要将其保存在RowData对象中。另外,我不知道它们的大小,但是您可能想编写一个didReceiveMemoryWarning,它将迭代您的objects并将每个项目的playerItem对象设置为nil

    关于iphone - 为什么在iOS上启动了几个线程后,我的线程似乎失败了?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14422802/

    相关文章:

    ios - Google Maps iOS + Storyboard + Segue = 崩溃

    ios - 从不同的 View Controller 访问 textField.text 变量

    ios - 动态添加一个 UIView 到另一个

    multithreading - 防止Tomcat中的线程阻塞

    iphone - iPhone 应用程序中 2D 图形元素的编辑器

    ios - UIWebView 如何用于自动抓取需要身份验证的页面?

    ios - Swift 崩溃 libobjc.A.dylib objc_msgSend

    ios - 在 aac 流中检测信号/标志的最佳实践

    python - cython.并行 : variable assignment without thread-locality

    c - 您如何分析多核处理器的所有内核?