UIActivityIndicator在iPhone上需要手动线程吗?

8

我正在开发一个iPhone应用程序,它执行了一个耗时操作,我想创建一个activityIndicator来告知用户应用程序没有冻结。

该操作完全在一个事件调用中执行...因此UI框架没有机会接收控制以显示和动画化这个指示器。

使用UIActivityIndicator(或任何其他类似动画)的示例应用程序在不同的事件中启动和停止动画,分别在程序的不同阶段触发。

我需要手动创建一个单独的线程来运行我的操作吗?还是已经默认支持这种行为?

5个回答

13

我刚刚在苹果iPhone论坛上找到了一个很相似的问题,并得到了一个好答案。

https://devforums.apple.com/message/24220#24220

基本上,是的,你需要在不同的线程中启动你的进程,但这很容易做到(来自用户 'eskimo1' 的转述)。

- (IBAction)syncOnThreadAction:(id)sender
{
    [self willStartJob];

    id myObject = [MyObjectClass createNewObject];
    [self performSelectorInBackground:
        @selector(inThreadStartDoJob:)
        withObject:myObject
    ];
}

- (void)inThreadStartDoJob:(id)theJobToDo
{
    NSAutoreleasePool * pool;
    NSString *          status;

    pool = [[NSAutoreleasePool alloc] init];
    assert(pool != nil);

    status = [theJobToDo countGrainsOfSandOnBeach];

    [self performSelectorOnMainThread:
        @selector(didStopJobWithStatus:)
        withObject:status
        waitUntilDone:NO
    ];

    [pool drain];
}

这里的-willStartJob-didStopJobWithStatus是我自己定义的方法,它们会:

  • 禁用UI,以防止用户同时启动两个任务
  • 设置活动指示器

我认为我的问题与你的类似,你能告诉我 status = [...] 中的意思吗?我不明白,请给我一个例子。 - R. Dewi
inThreadStartDoJob 在不同的线程中运行,因此您可以在该线程中执行任何一系列复杂的、长时间运行的操作。我已经更新了答案,并提供了一个简单的示例。 - Akusete
我不明白这行代码中的theJobToDo是什么意思:[self performSelectorInBackground:@selector(inThreadStartDoJob:) withObject:theJobToDo];?它是全局变量吗? - R. Dewi
这只是我在撰写问题时编造的一个占位符名称,因此,在那种情况下,我想它可能是在其他地方(或全局)声明的变量。 - Akusete
countGrainsOfSandOnBeach方法的实现是否在同一个类中? - R. Dewi
+1,这真的非常有帮助。你完全让我的项目变得更好了。谢谢! - dev

9
是的,当UIActivityIndicatorView调用保持在主线程中时,您需要将操作放在单独的线程中。
原因在于:如果您开始动画指示器视图,然后立即开始进程,您的进程将一直阻塞直到完成,用户将永远看不到任何“活动”。
相反,应该先开始动画指示器,然后为您的进程弹出新线程。使用通知、委托模式或performSelectorOnMainThread:withObject:waitUntilDone:来让主线程知道进程已经完成。然后停止动画并继续。
这将确保用户知道正在发生某些事情,而您的进程完成其任务。

开发人员不必将其操作放在单独的线程中。相反,他们应该权衡各种选择并做出最佳选择——如果操作需要几秒钟时间,并且生成新线程会产生明显的用户界面冲突,则在主线程上阻塞并显示活动指示器是更好的选择。需要谨慎、理解和关注地向应用程序添加额外的线程。 - ctpenrose

4

如果您想执行应该在主线程上启动的操作,那么启动新线程可能会过度杀伤和增加复杂性。

在我的代码中,我需要通过按下按钮来启动MailComposer,但是它可能需要一些时间才能出现,我希望同时确保UIActivityIndicator正在旋转。

这是我的做法:

-(void)submit_Clicked:(id)event
{
    [self.spinner startAnimating];
    [self performSelector:@selector(displayComposerSheet) 
               withObject:nil afterDelay:0];    
}

它将排队显示“displayComposerSheet”,而不是立即执行它。这足以使旋转器开始动画!

1
当一个应用程序已经大量使用线程时,再添加另一个线程可能会使情况变得非常混乱。有时在主线程上阻塞是可以的 -- 对于许多操作来说,这是一种有效的替代方法。 - ctpenrose

-1
一个 UIProgressView(进度条)不能自己运行,您需要定期增加其值。
一个 UIActivityIndicatorView(旋转齿轮)可以自己运行:只需在开始操作时调用 startAnimating,在完成操作后调用 stopAnimating。
因此,UIActivityIndicatorView可能是您正在寻找的内容。

-1

哪个更容易:

1> Have 1 method that starts the spinner (in the background).
2> Run your long-running code right here.
3> Have 1 method that ends the spinner.

或者...

A> Have 1 method that starts the spinner.
B> Have 20 methods (1 for each thing you want to do in the background)
C> Have 1 method that ends the spinner.

我总是看到大家都建议用A、B、C...的困难方式。


你能澄清每个步骤 (1,2,3,A,B,C) 使用了哪些线程吗? - Akusete
当“2>在此处运行您的长时间运行的代码”正在运行时,旋转器会停止,导致用户永远看不到旋转。 - Bryan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接