菜鸟教程小白 发表于 2022-12-13 00:05:24

ios - 使用 NSCondition 等待异步方法


                                            <p><p>我正在通过 Internet 异步下载四个 plist 文件。我需要等到所有四个文件都下载完毕,直到我在第一次运行时推送 UIViewController,或者在所有后续运行时刷新数据并重新加载我的所有 UITableView。</p>

<p>在第一次运行时,一切正常。刷新时,所有四个 url 请求都被调用并启动,但从不调用它们的完成或失败 block ,并且 UI 卡住。这很奇怪,因为我在后台线程中执行所有操作。我一直无法弄清楚为什么会这样。</p>

<p>第一次加载和刷新方法调用四个“更新”方法的方式相同,使用NSCondition的方式相同。</p>

<p>第一次运行:</p>

<pre><code>- (void)loadContentForProgram:(NSString *)programPath
{
    NSLog(@&#34;Start Load Program&#34;);
    AppDelegate *myDelegate = (AppDelegate *).delegate;
    hud = [ initWithView:myDelegate.window];
    ;
    hud.labelText = @&#34;Loading...&#34;;
    hud.detailsLabelText = @&#34;Loading Data&#34;;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
      //Do stuff here to load data from files

      //Update From online files
      hud.detailsLabelText = @&#34;Updating Live Data&#34;;
      resultLock = NO;
      progressLock = NO;
      recallLock = NO;
      stageLock = NO;

      condition = [ init];
      ;

      ;
      ;
      ;
      ;


      while (!resultLock) {
            ;
      }
      NSLog(@&#34;Unlock&#34;);
      while (!stageLock) {
            ;
      }
      NSLog(@&#34;Unlock&#34;);
      while (!recallLock) {
            ;
      }
      NSLog(@&#34;Unlock&#34;);
      while (!progressLock) {
            ;
      }
      NSLog(@&#34;Unlock&#34;);
      ;
      updateInProgress = NO;
      //Reset Refresh controls and table views
      self.refreshControlsArray = [ init];
      self.tableViewsArray = [ init];
      NSLog(@&#34;Finished Loading Program&#34;);
      [ postNotificationName:@&#34;WMSOFinishedLoadingProgramData&#34; object:nil]; //Pushes view controller
      dispatch_async(dispatch_get_main_queue(), ^{
            ;
      });
    });
}
</code></pre>

<p>刷新数据时:</p>

<pre><code>- (void)updateProgramContent
{
    if (!updateInProgress) {
      updateInProgress = YES;
      for (int i = 0; i &lt; self.refreshControlsArray.count; i++) {
            if (!((UIRefreshControl *)self.refreshControlsArray).refreshing) {
                beginRefreshing];
                setContentOffset:CGPointMake(0.0, 0.0) animated:YES];
            }
      }

      resultLock = NO;
      stageLock = NO;
      recallLock = NO;
      progressLock = NO;
      dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{

            condition = [ init];
            ;

            ;
            ;
            ;
            ;

            while (!resultLock) {
                ;
            }
            NSLog(@&#34;Unlock&#34;);
            while (!stageLock) {
                ;
            }
            NSLog(@&#34;Unlock&#34;);
            while (!recallLock) {
                ;
            }
            NSLog(@&#34;Unlock&#34;);
            while (!progressLock) {
                ;
            }
            NSLog(@&#34;Unlock&#34;);
            ;
      });

      for (int i = 0; i &lt; self.refreshControlsArray.count; i++) {
             performSelector:@selector(endRefreshing) withObject:nil afterDelay:1.0];
             performSelector:@selector(reloadData) withObject:nil afterDelay:1.0];
      }
      updateInProgress = NO;
    }
}
</code></pre>

<p>上面每个加载方法中出现的下面的 block ,对应于下载和更新特定数据的方法。</p>

<pre><code>;
;
;
;
</code></pre>

<p>运行:</p>

<pre><code>- (void)updateCompetitionResults
{
    __block NSDictionary *competitionResultsData = nil;
    NSURLRequest *request = ]] cachePolicy:NSURLCacheStorageNotAllowed timeoutInterval:20.0];
    AFPropertyListRequestOperation *operation = [AFPropertyListRequestOperation propertyListRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id propertyList) {
      competitionResultsData = (NSDictionary *)propertyList;
       atomically:NO];
      ;
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id propertyList) {
      competitionResultsData = ];
      NSLog(@&#34;Failed to retreive competition results: %@&#34;, error);
      ;
    }];
    ;
}
</code></pre>

<p>并且完成和失败 block 调用相同的方法来更新数据</p>

<pre><code>- (void)updateCompetitionResultsWithDictionary:(NSDictionary *)competitionResultsData
{
    //Do Stuff with the data here
    resultLock = YES;
    ;
}
</code></pre>

<p>那么,为什么这在第一次运行时有效,但在任何后续运行中都无效?</p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>正如我在上面的评论中提到的,最明显的问题是您在初始化 <code>condition</code> 之前调用了使用 <code>condition</code> 的方法。确保在开始调用 <code>updateCompetitionResults</code> 等之前初始化 <code>condition</code>。</p>

<hr/>

<p>就更彻底的改变而言,我可能会建议完全停用 <code>NSCondition</code>,并使用操作队列:</p>

<ol>
<li><p>我可能会使用 <a href="https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html" rel="noreferrer noopener nofollow"><code>NSOperationQueue</code></a> (或者你也可以使用调度组,如果你愿意,但我喜欢操作队列配置你可以操作多少并发操作的能力......如果你到了想要取消操作的地步,我认为 <code>NSOperationQueue</code> 也提供了一些不错的功能)。然后,您可以将每个下载和处理定义为单独的 <a href="http://developer.apple.com/library/ios/#documentation/cocoa/reference/NSOperation_class/Reference/Reference.html" rel="noreferrer noopener nofollow"><code>NSOperation</code></a> (每个下载都应该同步发生,因为它们在操作队列中运行,您可以获得异步操作的好处,但您可以在下载完成后立即启动后处理)。然后,您只需将它们排队以异步运行,但定义一个依赖于其他四个的最终操作将在四个下载完成后立即启动。 (顺便说一下,我使用了 <a href="https://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSBlockOperation_class/Reference/Reference.html" rel="noreferrer noopener nofollow"><code>NSBlockOperation</code></a>,它为 <code>NSOperation</code> 对象提供了 block 功能,但您可以按照自己的方式进行操作。)</p></li>
<li><p>虽然您的 <code>updateProgramContent</code> 可能会异步下载,但它会依次处理四个下载的文件,一个接一个。因此,如果第一次下载需要一段时间才能下载,它将阻碍其他人的后期处理。相反,我喜欢将四个 plist 文件中的每一个的下载和后处理都封装在一个 <code>NSOperation</code> 中。因此,我们不仅享受下载的最大并发性,还享受后处理的最大并发性。</p></li>
<li><p>我可能倾向于使用 <code>NSDictionary</code> 和 <code>NSArray</code> 功能允许您从网络下载 plist 并将它们加载到适当的结构中。这些 <code>dictionaryWithContentsOfURL</code> 和 <code>arrayWithContentsOfURL</code> 同步运行,但是因为我们在后台操作中执行此操作,所以一切都按照您的意愿异步运行。这也绕过了将它们保存到文件中。如果您希望将它们保存到 <code>Documents</code> 目录中的文件中,您也可以轻松地做到这一点。显然,如果您在下载 plist 文件时做一些复杂的事情(例如,您的服务器正在进行一些质询-响应身份验证),则不能使用方便的 <code>NSDictionary</code> 和 <code>NSArray </code> 方法。但如果你不需要所有这些,简单的 <code>NSDictionary</code> 和 <code>NSArray</code> 方法,<code>___WithContentsOfURL</code> 让生活变得非常简单。</p></li>
</ol>

<p>综合起来,它可能看起来像:</p>

<pre><code>@interface ViewController ()

@property (nonatomic, strong) NSArray *competitions;
@property (nonatomic, strong) NSDictionary *competitionResults;
@property (nonatomic, strong) NSDictionary *competitionRecalls;
@property (nonatomic, strong) NSDictionary *competitionProgress;

@end

@implementation ViewController

- (void)viewDidLoad
{
    ;

    ;
}

- (void)allTransfersComplete
{
    BOOL success;

    if (self.competitions == nil)
    {
      success = FALSE;
      NSLog(@&#34;Unable to download competitions&#34;);
    }

    if (self.competitionResults == nil)
    {
      success = FALSE;
      NSLog(@&#34;Unable to download results&#34;);
    }

    if (self.competitionRecalls == nil)
    {
      success = FALSE;
      NSLog(@&#34;Unable to download recalls&#34;);
    }

    if (self.competitionProgress == nil)
    {
      success = FALSE;
      NSLog(@&#34;Unable to download progress&#34;);
    }

    if (success)
    {
      NSLog(@&#34;all done successfully&#34;);
    }
    else
    {
      NSLog(@&#34;one or more failed&#34;);
    }
}

- (void)transfer
{
    NSURL *baseUrl = ;
    NSURL *competitionsUrl = ;
    NSURL *competitionResultsUrl = ;
    NSURL *competitionRecallsUrl = ;
    NSURL *competitionProgressUrl = ;

    NSOperationQueue *queue = [ init];
    queue.maxConcurrentOperationCount = 4; // if your server doesn&#39;t like four concurrent requests, you can ratchet this back to whatever you want

    // create operation that will be called when we&#39;re all done

    NSBlockOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{

      // any stuff that can be done in background should be done here

      [ addOperationWithBlock:^{

            // any user interface stuff should be done here; I&#39;ve just put this in a separate method so this method doesn&#39;t get too unwieldy

            ;
      }];
    }];

    // a variable that we&#39;ll use as we create our four download/process operations

    NSBlockOperation *operation;

    // create competitions operation

    operation = [NSBlockOperation blockOperationWithBlock:^{

      // download the competitions and load it into the ivar
      //
      // note, if you *really* want to download this to a file, you can
      // do that when the download is done

      self.competitions = ;

      // if you wanted to do any post-processing of the download
      // you could do it here.            
      NSLog(@&#34;competitions = %@&#34;, self.competitions);
    }];
    ;

    // create results operation

    operation = [NSBlockOperation blockOperationWithBlock:^{

      self.competitionResults = ;

      NSLog(@&#34;competitionResults = %@&#34;, self.competitionResults);
    }];
    ;

    // create recalls operation

    operation = [NSBlockOperation blockOperationWithBlock:^{

      self.competitionRecalls = ;

      NSLog(@&#34;competitionRecalls = %@&#34;, self.competitionRecalls);
    }];
    ;

    // create progress operation

    operation = [NSBlockOperation blockOperationWithBlock:^{

      self.competitionProgress = ;

      NSLog(@&#34;competitionProgress = %@&#34;, self.competitionProgress);
    }];
    ;

    // queue the completion operation (which is dependent upon the other four)

    ;

    // now queue the four download and processing operations

    ;
}

@end
</code></pre>

<p>现在,我不知道您的 plist 中哪些是数组,哪些是字典(在我的示例中,我将比赛设为数组,其余的是由比赛 id 键入的字典),但希望您了解什么我正在拍摄。最大化并发,消除<code>NSCondition</code>逻辑,真正充分利用<code>NSOperationQueue</code>等</p>

<p>这可能是非常重要的,但我只提到它作为 <code>NSCondition</code> 的替代品。如果您当前的技术有效,那就太好了。但以上概述了我将如何应对这样的挑战。</p></p>
                                   
                                                <p style="font-size: 20px;">关于ios - 使用 NSCondition 等待异步方法,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/14013947/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/14013947/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - 使用 NSCondition 等待异步方法