ios 有没有办法让 drawRect 现在工作?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4739748/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:35:51  来源:igfitidea点击:

Is there a way to make drawRect work right NOW?

iphoneioscocoaquartz-graphicsrunloop

提问by Fattie

The original question...............................................

原始问题......................................................

If you are an advanced user of drawRect, you will know that of course drawRect will not actually run until "all processing is finished."

如果您是 drawRect 的高级用户,您就会知道 drawRect 在“所有处理完成”之前不会真正运行。

setNeedsDisplay flags a view as invalidated and the OS, and basically waits until all processing is done. This can be infuriating in the common situation where you want to have:

setNeedsDisplay 将视图标记为无效和操作系统,并且基本上等待所有处理完成。在您想要拥有的常见情况下,这可能会令人愤怒:

  • a view controller 1
  • starts some function 2
  • which incrementally 3
    • creates a more and more complicated artwork and 4
    • at each step, you setNeedsDisplay (wrong!) 5
  • until all the work is done 6
  • 视图控制器 1
  • 启动一些功能 2
  • 其中递增 3
    • 创造出越来越复杂的艺术品和 4
    • 在每一步,你都设置了NeedsDisplay(错误!)5
  • 直到所有工作完成 6

Of course, when you do the above 1-6, all that happens is that drawRect is run once onlyafter step 6.

当然,当您执行上述 1-6 时,所发生的只是 drawRect在步骤 6 之后运行一次

Your goal is for the view to be refreshed at point 5. What to do?

您的目标是在第 5 点刷新视图。该怎么做?



Solution to the original question..............................................

原始问题的解决方案...................................................... .

In a word, you can (A) backgroundthe large painting, and call to the foreground for UI updates or (B) arguably controversiallythere are four 'immediate' methods suggested that do not use a background process. For the result of what works, run the demo program. It has #defines for all five methods.

总之,您可以(A)为大型绘画设置背景,并调用前台进行 UI 更新或(B) 有争议的有四种“立即”方法建议不使用后台进程。对于是什么在起作用的结果,运行演示程序。它对所有五种方法都有#defines。



Truly astounding alternate solution introduced by Tom Swift..................

Tom Swift 推出的真正令人震惊的替代解决方案......................

Tom Swift has explained the amazing idea of quite simply manipulating the run loop. Here's how you trigger the run loop:

Tom Swift 解释了非常简单地操纵运行循环的惊人想法。以下是触发运行循环的方式:

[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];

[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];

This is a truly amazing piece of engineering. Of course one should be extremely careful when manipulating the run loop and as many pointed out this approach is strictly for experts.

这是一项真正了不起的工程。当然,在操作 run loop 时应该非常小心,正如许多人指出的那样,这种方法只适合专家使用。



The Bizarre Problem That Arises..............................................

出现的奇怪问题...................................................... .

Even though a number of the methods work, they don't actually "work" because there is a bizarre progressive-slow-down artifact you will see clearly in the demo.

尽管许多方法有效,但它们实际上并不“有效”,因为您将在演示中清楚地看到一个奇怪的渐进式减速工件。

Scroll to the 'answer' I pasted in below, showing the console output - you can see how it progressively slows.

滚动到我粘贴在下面的“答案”,显示控制台输出 - 您可以看到它是如何逐渐变慢的。

Here's the new SO question:
Mysterious "progressive slowing" problem in run loop / drawRect

这里是新的SO问题:
在运行循环神秘的“进步放缓”的问题/的drawRect

Here is V2 of the demo app...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html

这是演示应用程序的 V2...
http://www.fileswap.com/dl/p8lU3gAi/stepwiseDrawingV2.zip.html

You will see it tests all five methods,

你会看到它测试了所有五种方法,

#ifdef TOMSWIFTMETHOD
 [self setNeedsDisplay];
 [[NSRunLoop currentRunLoop]
      runMode:NSDefaultRunLoopMode beforeDate:[NSDate date]];
#endif
#ifdef HOTPAW
 [self setNeedsDisplay];
 [CATransaction flush];
#endif
#ifdef LLOYDMETHOD
 [CATransaction begin];
 [self setNeedsDisplay];
 [CATransaction commit];
#endif
#ifdef DDLONG
 [self setNeedsDisplay];
 [[self layer] displayIfNeeded];
#endif
#ifdef BACKGROUNDMETHOD
 // here, the painting is being done in the bg, we have been
 // called here in the foreground to inval
 [self setNeedsDisplay];
#endif
  • You can see for yourself which methods work and which do not.

  • you can see the bizarre "progressive-slow-down". why does it happen?

  • you can see with the controversial TOMSWIFT method, there is actually no problem at all with responsiveness. tap for response at any time. (but still the bizarre "progressive-slow-down" problem)

  • 您可以亲眼看看哪些方法有效,哪些无效。

  • 你可以看到奇怪的“渐进式减速”。为什么会发生?

  • 你可以看到有争议的 TOMSWIFT 方法,响应性实际上完全没有问题。随时点击响应。(但仍然是奇怪的“渐进式减速”问题)

So the overwhelming thing is this weird "progressive-slow-down": on each iteration, for unknown reasons, the time taken for a loop deceases. Note that this applies to both doing it "properly" (background look) or using one of the 'immediate' methods.

所以压倒性的事情是这种奇怪的“渐进式减速”:在每次迭代中,由于未知原因,循环所花费的时间减少。请注意,这适用于“正确”(背景外观)或使用“立即”方法之一。



Practical solutions ........................

实际解决方案......................

For anyone reading in the future, if you are actually unable to get this to work in production code because of the "mystery progressive slowdown" ... Felz and Void have each presented astounding solutions in the other specific question, hope it helps.

对于将来阅读的任何人,如果您实际上由于“神秘的渐进式减速”而无法在生产代码中使用它...... Felz 和 Void 在另一个特定问题中分别提出了惊人的解决方案,希望它有所帮助。

采纳答案by Brad Larson

Updates to the user interface happen at the end of the current pass through the run loop. These updates are performed on the main thread, so anything that runs for a long time in the main thread (lengthy calculations, etc.) will prevent the interface updates from being started. Additionally, anything that runs for a while on the main thread will also cause your touch handling to be unresponsive.

用户界面的更新发生在当前通过运行循环的末尾。这些更新是在主线程上执行的,所以任何在主线程中运行时间长的(冗长的计算等)都会阻止界面更新的启动。此外,在主线程上运行一段时间的任何内容也会导致您的触摸处理无响应。

This means that there is no way to "force" a UI refresh to occur from some other point in a process running on the main thread.The previous statement is not entirely correct, as Tom's answer shows. You can allow the run loop to come to completion in the middle of operations performed on the main thread. However, this still may reduce the responsiveness of your application.

这意味着无法从主线程上运行的进程中的某个其他点“强制”UI 刷新。正如汤姆的回答所示,先前的陈述并不完全正确。您可以允许运行循环在主线程上执行的操作中间完成。但是,这仍然可能会降低应用程序的响应能力。

In general, it is recommended that you move anything that takes a while to perform to a background thread so that the user interface can remain responsive. However, any updates you wish to perform to the UI need to be done back on the main thread.

在一般情况下,建议您移动任何东西,需要一段时间来执行的后台线程使用户界面能保持响应。但是,您希望对 UI 执行的任何更新都需要在主线程上完成。

Perhaps the easiest way to do this under Snow Leopard and iOS 4.0+ is to use blocks, like in the following rudimentary sample:

也许在 Snow Leopard 和 iOS 4.0+ 下执行此操作的最简单方法是使用块,如下面的基本示例所示:

dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
    // Do some work
    dispatch_async(main_queue, ^{
        // Update the UI
    });
});

The Do some workpart of the above could be a lengthy calculation, or an operation that loops over multiple values. In this example, the UI is only updated at the end of the operation, but if you wanted continuous progress tracking in your UI, you could place the dispatch to the main queue where ever you needed a UI update to be performed.

Do some work上面的部分可能是一个冗长的计算,或者一个循环多个值的操作。在此示例中,UI 仅在操作结束时更新,但如果您希望在 UI 中持续跟踪进度,则可以将调度放置到需要执行 UI 更新的主队列中。

For older OS versions, you can break off a background thread manually or through an NSOperation. For manual background threading, you can use

对于较旧的操作系统版本,您可以手动或通过 NSOperation 中断后台线程。对于手动后台线程,您可以使用

[NSThread detachNewThreadSelector:@selector(doWork) toTarget:self withObject:nil];

or

或者

[self performSelectorInBackground:@selector(doWork) withObject:nil];

and then to update the UI you can use

然后更新您可以使用的用户界面

[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:NO];

Note that I've found the NO argument in the previous method to be needed to get constant UI updates while dealing with a continuous progress bar.

请注意,我发现在处理连续进度条时需要使用前一个方法中的 NO 参数来获取持续的 UI 更新。

This sample applicationI created for my class illustrates how to use both NSOperations and queues for performing background work and then updating the UI when done. Also, my Moleculesapplication uses background threads for processing new structures, with a status bar that is updated as this progresses. You can download the source code to see how I achieved this.

我为我的班级创建的这个示例应用程序说明了如何使用 NSOperations 和队列来执行后台工作,然后在完成后更新 UI。此外,我的Molecules应用程序使用后台线程来处理新结构,状态栏会随着处理的进行而更新。你可以下载源代码来看看我是如何实现这一点的。

回答by TomSwift

If I understand your question correctly, there is a simple solution to this. During your long-running routine you need to tell the current runloop to process for a single iteration (or more, of the runloop) at certain points in your own processing. e.g, when you want to update the display. Any views with dirty update regions will have their drawRect: methods called when you run the runloop.

如果我正确理解您的问题,那么有一个简单的解决方案。在长时间运行的例程中,您需要告诉当前的 runloop 在您自己的处理过程中的某些点处理单个迭代(或多个 runloop)。例如,当您想要更新显示时。任何带有脏更新区域的视图都会在运行 runloop 时调用它们的 drawRect: 方法。

To tell the current runloop to process for one iteration (and then return to you...):

告诉当前的 runloop 处理一次迭代(然后返回给你......):

[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];

Here's an example of an (inefficient) long running routine with a corresponding drawRect - each in the context of a custom UIView:

这是一个(低效的)长时间运行的例程和相应的 drawRect 的示例 - 每个例程都在自定义 UIView 的上下文中:

- (void) longRunningRoutine:(id)sender
{
    srand( time( NULL ) );

    CGFloat x = 0;
    CGFloat y = 0;

    [_path moveToPoint: CGPointMake(0, 0)];

    for ( int j = 0 ; j < 1000 ; j++ )
    {
        x = 0;
        y = (CGFloat)(rand() % (int)self.bounds.size.height);

        [_path addLineToPoint: CGPointMake( x, y)];

        y = 0;
        x = (CGFloat)(rand() % (int)self.bounds.size.width);

        [_path addLineToPoint: CGPointMake( x, y)];

        x = self.bounds.size.width;
        y = (CGFloat)(rand() % (int)self.bounds.size.height);

        [_path addLineToPoint: CGPointMake( x, y)];

        y = self.bounds.size.height;
        x = (CGFloat)(rand() % (int)self.bounds.size.width);

        [_path addLineToPoint: CGPointMake( x, y)];

        [self setNeedsDisplay];
        [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
    }

    [_path removeAllPoints];
}

- (void) drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor( ctx, [UIColor blueColor].CGColor );

    CGContextFillRect( ctx,  rect);

    CGContextSetStrokeColorWithColor( ctx, [UIColor whiteColor].CGColor );

    [_path stroke];
}

And here is a fully working sample demonstrating this technique.

这是一个完整的工作示例,演示了这种技术

With some tweaking you can probably adjust this to make the rest of the UI (i.e. user-input) responsive as well.

通过一些调整,您可能可以调整它以使 UI 的其余部分(即用户输入)也具有响应性。

Update (caveat for using this technique)

更新(使用此技术的警告)

I just want to say that I agree with much of the feedback from others here saying this solution (calling runMode: to force a call to drawRect:) isn't necessarily a great idea. I've answered this question with what I feel is a factual "here's how" answer to the stated question, and I am not intending to promote this as "correct" architecture. Also, I'm not saying there might not be other (better?) ways to achieve the same effect - certainly there may be other approaches that I wasn't aware of.

我只想说我同意这里其他人的大部分反馈,说这个解决方案(调用 runMode: 强制调用 drawRect:)不一定是个好主意。我已经回答了这个问题,我认为这是对所述问题的事实“这里是如何”的答案,我不打算将其宣传为“正确”架构。另外,我并不是说可能没有其他(更好的?)方法来达到相同的效果——当然可能还有其他我不知道的方法。

Update (response to the Joe's sample code and performance question)

更新(对 Joe 的示例代码和性能问题的回应)

The performance slowdown you're seeing is the overhead of running the runloop on each iteration of your drawing code, which includes rendering the layer to the screen as well as all of the other processing the runloop does such as input gathering and processing.

您看到的性能下降是在绘图代码的每次迭代中运行 runloop 的开销,其中包括将图层渲染到屏幕以及 runloop 执行的所有其他处理,例如输入收集和处理。

One option might be to invoke the runloop less frequently.

一种选择可能是降低调用 runloop 的频率。

Another option might be to optimize your drawing code. As it stands (and I don't know if this is your actual app, or just your sample...) there are a handful of things you could do to make it faster. The first thing I would do is move all the UIGraphicsGet/Save/Restore code outside the loop.

另一种选择可能是优化您的绘图代码。就目前情况而言(我不知道这是您的实际应用程序,还是您的示例……)您可以做一些事情来使其更快。我要做的第一件事是将所有 UIGraphicsGet/Save/Restore 代码移到循环之外。

From an architectural standpoint however, I would highly recommend considering some of the other approaches mentioned here. I see no reason why you can't structure your drawing to happen on a background thread (algorithm unchanged), and use a timer or other mechanism to signal the main thread to update it's UI on some frequency until the drawing is complete. I think most of the folks who've participated in the discussion would agree that this would be the "correct" approach.

然而,从架构的角度来看,我强烈建议考虑这里提到的其他一些方法。我认为没有理由不能将绘图结构化为发生在后台线程上(算法不变),并使用计时器或其他机制向主线程发出信号,以某种频率更新它的 UI,直到绘图完成。我认为参与讨论的大多数人都会同意这将是“正确”的方法。

回答by Christopher Lloyd

You can do this repeatedly in a loop and it'll work fine, no threads, no messing with the runloop, etc.

您可以在循环中重复执行此操作,它会正常工作,没有线程,也不会干扰 runloop 等。

[CATransaction begin];
// modify view or views
[view setNeedsDisplay];
[CATransaction commit];

If there is an implicit transaction already in place prior to the loop you need to commit that with [CATransaction commit] before this will work.

如果在循环之前已经存在隐式事务,您需要在此之前使用 [CATransaction commit] 提交该事务。

回答by hotpaw2

In order to get drawRect called the soonest (which is not necessarily immediately, as the OS may still wait until, for instance, the next hardware display refresh, etc.), an app should idle it's UI run loop as soon as possible, by exiting any and all methods in the UI thread, and for a non-zero amount of time.

为了尽快调用 drawRect(不一定是立即调用,因为操作系统可能仍会等到,例如,下一次硬件显示刷新等),应用程序应尽快空闲其 UI 运行循环,通过退出 UI 线程中的任何和所有方法,并且持续非零时间。

You can either do this in the main thread by chopping any processing that takes more than an animation frame time into shorter chunks and scheduling continuing work only after a short delay (so drawRect might run in the gaps), or by doing the processing in a background thread, with a periodic call to performSelectorOnMainThread to do a setNeedsDisplay at some reasonable animation frame rate.

您可以在主线程中执行此操作,方法是将需要超过动画帧时间的任何处理切割成更短的块并仅在短暂延迟后安排继续工作(因此 drawRect 可能会在间隙中运行),或者通过在一个后台线程,定期调用 performSelectorOnMainThread 以某种合理的动画帧速率执行 setNeedsDisplay。

A non-OpenGL method to update the display near immediately (which means at the very next hardware display refresh or three) is by swapping visible CALayer contents with an image or CGBitmap that you have drawn into. An app can do Quartz drawing into a Core Graphics bitmap at pretty much at any time.

一种立即更新显示的非 OpenGL 方法(这意味着在下一次硬件显示刷新或第三次刷新时)是将可见的 CALayer 内容与您绘制的图像或 CGBitmap 交换。应用程序几乎可以随时将 Quartz 绘制到 Core Graphics 位图中。

New added answer:

新添加的答案:

Please see Brad Larson's comments below and Christopher Lloyd's comment on another answer here as the hint leading towards this solution.

请参阅下面的 Brad Larson 的评论和 Christopher Lloyd 对此处另一个答案的评论,作为导致此解决方案的提示。

[ CATransaction flush ];

will cause drawRect to be called on views on which a setNeedsDisplay request has been done, even if the flush is done from inside a method that is blocking the UI run loop.

将导致在已完成 setNeedsDisplay 请求的视图上调用 drawRect,即使刷新是从阻塞 UI 运行循环的方法内部完成的。

Note that, when blocking the UI thread, a Core Animation flush is required to update changing CALayer contents as well. So, for animating graphic content to show progress, these may both end up being forms of the same thing.

请注意,当阻塞 UI 线程时,还需要刷新核心动画以更新更改的 CALayer 内容。因此,对于动画图形内容以显示进度,它们最终可能是同一事物的形式。

New added note to new added answer above:

上面新添加的答案的新添加注释:

Do not flush faster than your drawRect or animation drawing can complete, as this might queue up flushes, causing weird animation effects.

刷新速度不要超过 drawRect 或动画绘制完成的速度,因为这可能会排队刷新,导致奇怪的动画效果。

回答by Dave DeLong

Without questioning the wisdom of this (which you ought to do), you can do:

无需质疑这样做的智慧(您应该这样做),您可以:

[myView setNeedsDisplay];
[[myView layer] displayIfNeeded];

-setNeedsDisplaywill mark the view as needing to be redrawn. -displayIfNeededwill force the view's backing layer to redraw, but only if it has been marked as needing to be displayed.

-setNeedsDisplay会将视图标记为需要重绘。 -displayIfNeeded将强制视图的支持层重绘,但前提是它已被标记为需要显示。

I will emphasize, however, that your question is indicative of an architecture that could use some re-working. In all but exceptionally rare cases, you should never need to or want to force a view to redraw immediately. UIKit with not built with that use-case in mind, and if it works, consider yourself lucky.

但是,我要强调的是,您的问题表明可以使用一些返工的架构。除了极少数情况外,您永远不需要或想要强制立即重绘视图。UIKit 并没有考虑到该用例,如果它有效,请认为自己很幸运。

回答by tarmes

I realise that this is an old thread, but I'd like to offer a clean solution to the given problem.

我意识到这是一个旧线程,但我想为给定的问题提供一个干净的解决方案。

I agree with other posters that in an ideal situation all the heavy lifting should be done in a background thread, however there are times when this simply isn't possible because the time consuming part requires lots of accessing to non thread-safe methods such as those offered by UIKit. In my case, initialising my UI is time consuming and there's nothing I can run in the background, so my best option is to update a progress bar during the init.

我同意其他海报,在理想情况下,所有繁重的工作都应该在后台线程中完成,但是有时这根本不可能,因为耗时的部分需要大量访问非线程安全的方法,例如UIKit 提供的那些。就我而言,初始化我的 UI 很耗时,而且我无法在后台运行任何东西,所以我最好的选择是在 init 期间更新进度条。

However, once we think in terms of the ideal GCD approach, the solution is actually a simple. We do all the work in a background thread, dividing it into chucks that are called synchronously on the main thread. The run loop will be run for each chuck, updating the UI and any progress bars etc.

然而,一旦我们考虑理想的 GCD 方法,解决方案实际上很简单。我们在后台线程中完成所有工作,将其划分为在主线程上同步调用的卡盘。将为每个卡盘运行运行循环,更新 UI 和任何进度条等。

- (void)myInit
{
    // Start the work in a background thread.
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        // Back to the main thread for a chunk of code
        dispatch_sync(dispatch_get_main_queue(), ^{
            ...

            // Update progress bar
            self.progressIndicator.progress = ...: 
        });

        // Next chunk
        dispatch_sync(dispatch_get_main_queue(), ^{
            ...

            // Update progress bar
            self.progressIndicator.progress = ...: 
        });

        ...
    });
}

Of course, this is essentially the same as Brad's technique, but his answer doesn't quite address the issue at hand - that of running a lot of non thread safe code while updating the UI periodically.

当然,这与 Brad 的技术本质上相同,但他的回答并没有完全解决手头的问题 - 在定期更新 UI 的同时运行大量非线程安全代码的问题。

回答by Jakub

I think, the most complete answer comes from the Jeffrey Sambell's blog post 'Asynchronous Operations in iOS with Grand Central Dispatch'and it worked for me! It's basically the same solution as proposed by Brad above but fully explained in terms of OSX/IOS concurrency model.

我认为,最完整的答案来自 Jeffrey Sambell 的博客文章“使用 Grand Central Dispatch 在 iOS 中的异步操作”,它对我有用 !它与上面 Brad 提出的解决方案基本相同,但根据 OSX/IOS 并发模型进行了充分解释。

The dispatch_get_current_queuefunction will return the current queue from which the block is dispatched and the dispatch_get_main_queuefunction will return the main queue where your UI is running.

The dispatch_get_main_queuefunction is very useful for updating the iOS app's UI as UIKitmethods are not thread safe (with a few exceptions) so any calls you make to update UI elements must always be done from the main queue.

A typical GCD call would look something like this:

// Doing something on the main thread
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{

// Perform long running process   
dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI   
    }); 
}); 

// Continue doing other stuff on the  
// main thread while process is running.

dispatch_get_current_queue函数将返回从中调度块的当前队列,该dispatch_get_main_queue函数将返回您的 UI 正在运行的主队列。

dispatch_get_main_queue函数对于更新 iOS 应用程序的 UI 非常有用,因为UIKit方法不是线程安全的(有一些例外),因此您为更新 UI 元素所做的任何调用都必须始终从主队列中完成。

典型的 GCD 调用如下所示:

// Doing something on the main thread
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{

// Perform long running process   
dispatch_async(dispatch_get_main_queue(), ^{
    // Update the UI   
    }); 
}); 

// Continue doing other stuff on the  
// main thread while process is running.

And here goes my working example (iOS 6+). It displays frames of a stored video using the AVAssetReaderclass:

这是我的工作示例(iOS 6+)。它使用以下AVAssetReader类显示存储视频的帧:

//...prepare the AVAssetReader* asset_reader earlier and start reading frames now:
[asset_reader startReading];

dispatch_queue_t readerQueue = dispatch_queue_create("Reader Queue", NULL);
dispatch_async(readerQueue, ^{
    CMSampleBufferRef buffer;
    while ( [asset_reader status]==AVAssetReaderStatusReading )
    {
        buffer = [asset_reader_output copyNextSampleBuffer];
        if (buffer!=nil)
        {
            //The point is here: to use the main queue for actual UI operations
            dispatch_async(dispatch_get_main_queue(), ^{
                // Update the UI using the AVCaptureVideoDataOutputSampleBufferDelegate style function
                [self captureOutput:nil didOutputSampleBuffer:buffer fromConnection:nil];
                CFRelease (buffer);
            });
        }
    }
});

The first part of this sample may be found herein Damian's answer.

该样品的第一部分,可以发现这里在达米安的答案。

回答by kubi

Have you tried doing the heavy processing on a secondary thread and calling back to the main thread to schedule view updates? NSOperationQueuemakes this sort of thing pretty easy.

您是否尝试过在辅助线程上进行繁重的处理并回调主线程以安排视图更新?NSOperationQueue使这种事情变得非常容易。



Sample code that takes an array of NSURLs as input and asynchronously downloads them all, notifying the main thread as each of them is finished and saved.

示例代码将一组 NSURL 作为输入并异步下载它们,并在每个完成并保存时通知主线程。

- (void)fetchImageWithURLs:(NSArray *)urlArray {
    [self.retriveAvatarQueue cancelAllOperations];
    self.retriveAvatarQueue = nil;

    NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];

    for (NSUInteger i=0; i<[urlArray count]; i++) {
        NSURL *url = [urlArray objectAtIndex:i];

        NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:@selector(cacheImageWithIndex:andURL:)]];
        [inv setTarget:self];
        [inv setSelector:@selector(cacheImageWithIndex:andURL:)];
        [inv setArgument:&i atIndex:2];
        [inv setArgument:&url atIndex:3];

        NSInvocationOperation *invOp = [[NSInvocationOperation alloc] initWithInvocation:inv];
        [opQueue addOperation:invOp];
        [invOp release];
    }

    self.retriveAvatarQueue = opQueue;
    [opQueue release];
}

- (void)cacheImageWithIndex:(NSUInteger)index andURL:(NSURL *)url {
    NSData *imageData = [NSData dataWithContentsOfURL:url];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *filePath = PATH_FOR_IMG_AT_INDEX(index);
    NSError *error = nil;

    // Save the file      
    if (![fileManager createFileAtPath:filePath contents:imageData attributes:nil]) {
        DLog(@"Error saving file at %@", filePath);
    }

    // Notifiy the main thread that our file is saved.
    [self performSelectorOnMainThread:@selector(imageLoadedAtPath:) withObject:filePath waitUntilDone:NO];

}

回答by William Jockusch

Joe -- if you are willing to set it up so that your lengthy processing all happens inside of drawRect, you can make it work. I just wrote a test project. It works. See code below.

Joe——如果你愿意设置它以便你的冗长处理都发生在 drawRect 内,你可以让它工作。我刚刚写了一个测试项目。有用。请参阅下面的代码。

LengthyComputationTestAppDelegate.h:

LongyComputationTestAppDelegate.h:

#import <UIKit/UIKit.h>
@interface LengthyComputationTestAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end

LengthComputationTestAppDelegate.m:

LengthComputationTestAppDelegate.m:

#import "LengthyComputationTestAppDelegate.h"
#import "Incrementer.h"
#import "IncrementerProgressView.h"

@implementation LengthyComputationTestAppDelegate

@synthesize window;


#pragma mark -
#pragma mark Application lifecycle

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch.
    IncrementerProgressView *ipv = [[IncrementerProgressView alloc]initWithFrame:self.window.bounds];
    [self.window addSubview:ipv];
    [ipv release];
    [self.window makeKeyAndVisible];
    return YES;
}

Incrementer.h:

增量器.h:

#import <Foundation/Foundation.h>

//singleton object
@interface Incrementer : NSObject {
    NSUInteger theInteger_;
}

@property (nonatomic) NSUInteger theInteger;

+(Incrementer *) sharedIncrementer;
-(NSUInteger) incrementForTimeInterval: (NSTimeInterval) timeInterval;
-(BOOL) finishedIncrementing;

incrementer.m:

增量器.m:

#import "Incrementer.h"

@implementation Incrementer

@synthesize theInteger = theInteger_;

static Incrementer *inc = nil;

-(void) increment {
    theInteger_++;
}

-(BOOL) finishedIncrementing {
    return (theInteger_>=100000000);
}

-(NSUInteger) incrementForTimeInterval: (NSTimeInterval) timeInterval {
    NSTimeInterval negativeTimeInterval = -1*timeInterval;
    NSDate *startDate = [NSDate date];
    while (!([self finishedIncrementing]) && [startDate timeIntervalSinceNow] > negativeTimeInterval)
        [self increment];
    return self.theInteger;
}

-(id) init {
    if (self = [super init]) {
        self.theInteger = 0;
    }
    return self;
}

#pragma mark --
#pragma mark singleton object methods

+ (Incrementer *) sharedIncrementer { 
    @synchronized(self) {
        if (inc == nil) {
            inc = [[Incrementer alloc]init];        
        }
    }
    return inc;
}

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (inc == nil) {
            inc = [super allocWithZone:zone];
            return inc;  // assignment and return on first allocation
        }
    }
    return nil; // on subsequent allocation attempts return nil
}

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

- (id)retain {
    return self;
}

- (unsigned)retainCount {
    return UINT_MAX;  // denotes an object that cannot be released
}

- (void)release {
    //do nothing
}

- (id)autorelease {
    return self;
}

@end

IncrementerProgressView.m:

IncrementerProgressView.m:

#import "IncrementerProgressView.h"


@implementation IncrementerProgressView
@synthesize progressLabel = progressLabel_;
@synthesize nextUpdateTimer = nextUpdateTimer_;

-(id) initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame: frame]) {
        progressLabel_ = [[UILabel alloc]initWithFrame:CGRectMake(20, 40, 300, 30)];
        progressLabel_.font = [UIFont systemFontOfSize:26];
        progressLabel_.adjustsFontSizeToFitWidth = YES;
        progressLabel_.textColor = [UIColor blackColor];
        [self addSubview:progressLabel_];
    }
    return self;
}

-(void) drawRect:(CGRect)rect {
    [self.nextUpdateTimer invalidate];
    Incrementer *shared = [Incrementer sharedIncrementer];
    NSUInteger progress = [shared incrementForTimeInterval: 0.1];
    self.progressLabel.text = [NSString stringWithFormat:@"Increments performed: %d", progress];
    if (![shared finishedIncrementing])
        self.nextUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:0. target:self selector:(@selector(setNeedsDisplay)) userInfo:nil repeats:NO];
}

- (void)dealloc {
    [super dealloc];
}

@end