NSOperationQueue 和线程问题
嗯,我有一个严重的问题。我有一个类管理与 NSOperationQueue 的连接以及该类的委托方法,这些方法实际上是 NSURLConnection 委托方法。有时,我必须弹出具有委托类答案的 viewController,但它被困在委托函数的范围内,填充 UITableView 并且应用程序就在那里崩溃。我在 viewController dealloc 和 NSURLConnection 委托上使用 @synchronized 解决了这个问题,但我认为这不是最干净的方法,因为当我向 tableView 添加大量信息时,UI 有时会冻结。那么,有没有办法做到这一点干净呢?
- (void)viewDidLoad:(BOOL)animated {
[myManager startRequest];
}
//myManager class callback
- (void)managerDelegate {
//Doing things and just poped the viewController while in the function scope
//Program crashes, most of logs are "UIViewController message sent to deallocated instance"
}
//viewController dealloc
- (void)dealloc
{
@synchronized([Manager class])
{
alert.delegate = nil;
searchResultsTableView.delegate = nil;
searchResultsTableView.dataSource = nil;
[searchResultsTableView release]; searchResultsTableView = nil;
[serviceSearch release]; serviceSearch = nil;
[searchResults release]; searchResults = nil;
[XMLTag release]; XMLTag = nil;
[XMLParserServicesKeys release]; XMLParserServicesKeys = nil;
[XMLParserKeys release]; XMLParserKeys = nil;
[searchString release]; searchString = nil;
[__managedObjectContext release]; __managedObjectContext = nil;
manager.delegate = nil;
[manager stopAllRequests];
[manager release]; manager = nil;
[super dealloc];
}
}
编辑:更多代码,现在是 myManager 类
- (void) stopAllRequests
{
#ifdef _WSMANAGER_DEBUG_
NSLog(@"stopAllRequests %d", [connectionsArray count]);
#endif
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
for(NNSURLConnection* connection in connectionsArray)
{
[connection cancel];
[connection release];
}
[connectionsArray removeAllObjects];
[queue cancelAllOperations];
}
- (BOOL)startRequest
{
//Data initialization
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(beginConnection:) object:[NSDictionary dictionaryWithObjectsAndKeys:request, kRequestKey, keyInfo, kUserInfoKey, nil]];
[queue addOperation:operation];
[operation release];
return YES;
}
-(void) beginConnection:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NNSURLConnection* connection = [[NNSURLConnection alloc] initWithRequest:[object objectForKey:kRequestKey] delegate:self];
if(connection)
{
NSMutableData *requestData = [[NSMutableData alloc] init];
connection.url = [((NSURLRequest*)[object objectForKey:kRequestKey]) URL];
connection.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[object objectForKey:kUserInfoKey], kUserInfoKey, requestData, kRequestDataKey, nil];
[connectionsArray addObject:connection];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:TIMEOUT]];
if([connectionsArray indexOfObject:connection] != NSNotFound)
{
[connection cancel];
if([delegate conformsToProtocol:@protocol(ManagerDelegate)] && [delegate respondsToSelector:@selector(managerFailed:withKey:errorCode:)]) {
[delegate managerFailed:self withKey:[connection.userInfo objectForKey:kUserInfoKey] errorCode:ManagerErrorCodeTimeout];
if([connectionsArray count] < 1)
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
[connectionsArray removeObject:connection];
}
[pool drain];
}
- (void)connectionDidFinishLoading:(NNSURLConnection *)connection {
@synchronized([Manager class])
{
NSMutableData *requestData = [connection.userInfo objectForKey:kRequestDataKey];
NSString* responseString = [[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding];
if([delegate conformsToProtocol:@protocol(PLMWSManagerDelegate)] && [delegate respondsToSelector:@selector(managerSuccess:responseString:withKey:)])
[delegate managerSuccess:self responseString:responseString withKey:[connection.userInfo objectForKey:kUserInfoKey]];
[responseString release];
[requestData release];
[connectionsArray removeObject:connection];
[connection cancel];
[connection release]; connection = nil;
if([connectionsArray count] < 1)
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
Well I have a serious problem. I have a class that manages connections with NSOperationQueue and delegate methods for that class that are actually NSURLConnection delegate methods. Sometimes, I have to pop the viewController that has the answer of my delegate class but it gets trapped in the scope of the delegate function, populating a UITableView and the app crashes just there. I solved it using @synchronized on the viewController dealloc and on the NSURLConnection delegates, but I don't think is the cleanest way since UI freezes sometimes when I'm adding a lot of info to the tableView. So, is there a way to do this clean?
- (void)viewDidLoad:(BOOL)animated {
[myManager startRequest];
}
//myManager class callback
- (void)managerDelegate {
//Doing things and just poped the viewController while in the function scope
//Program crashes, most of logs are "UIViewController message sent to deallocated instance"
}
//viewController dealloc
- (void)dealloc
{
@synchronized([Manager class])
{
alert.delegate = nil;
searchResultsTableView.delegate = nil;
searchResultsTableView.dataSource = nil;
[searchResultsTableView release]; searchResultsTableView = nil;
[serviceSearch release]; serviceSearch = nil;
[searchResults release]; searchResults = nil;
[XMLTag release]; XMLTag = nil;
[XMLParserServicesKeys release]; XMLParserServicesKeys = nil;
[XMLParserKeys release]; XMLParserKeys = nil;
[searchString release]; searchString = nil;
[__managedObjectContext release]; __managedObjectContext = nil;
manager.delegate = nil;
[manager stopAllRequests];
[manager release]; manager = nil;
[super dealloc];
}
}
Edit: some more code, now of myManager class
- (void) stopAllRequests
{
#ifdef _WSMANAGER_DEBUG_
NSLog(@"stopAllRequests %d", [connectionsArray count]);
#endif
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
for(NNSURLConnection* connection in connectionsArray)
{
[connection cancel];
[connection release];
}
[connectionsArray removeAllObjects];
[queue cancelAllOperations];
}
- (BOOL)startRequest
{
//Data initialization
NSInvocationOperation* operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(beginConnection:) object:[NSDictionary dictionaryWithObjectsAndKeys:request, kRequestKey, keyInfo, kUserInfoKey, nil]];
[queue addOperation:operation];
[operation release];
return YES;
}
-(void) beginConnection:(id)object
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NNSURLConnection* connection = [[NNSURLConnection alloc] initWithRequest:[object objectForKey:kRequestKey] delegate:self];
if(connection)
{
NSMutableData *requestData = [[NSMutableData alloc] init];
connection.url = [((NSURLRequest*)[object objectForKey:kRequestKey]) URL];
connection.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:[object objectForKey:kUserInfoKey], kUserInfoKey, requestData, kRequestDataKey, nil];
[connectionsArray addObject:connection];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
}
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:TIMEOUT]];
if([connectionsArray indexOfObject:connection] != NSNotFound)
{
[connection cancel];
if([delegate conformsToProtocol:@protocol(ManagerDelegate)] && [delegate respondsToSelector:@selector(managerFailed:withKey:errorCode:)]) {
[delegate managerFailed:self withKey:[connection.userInfo objectForKey:kUserInfoKey] errorCode:ManagerErrorCodeTimeout];
if([connectionsArray count] < 1)
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
[connectionsArray removeObject:connection];
}
[pool drain];
}
- (void)connectionDidFinishLoading:(NNSURLConnection *)connection {
@synchronized([Manager class])
{
NSMutableData *requestData = [connection.userInfo objectForKey:kRequestDataKey];
NSString* responseString = [[NSString alloc] initWithData:requestData encoding:NSUTF8StringEncoding];
if([delegate conformsToProtocol:@protocol(PLMWSManagerDelegate)] && [delegate respondsToSelector:@selector(managerSuccess:responseString:withKey:)])
[delegate managerSuccess:self responseString:responseString withKey:[connection.userInfo objectForKey:kUserInfoKey]];
[responseString release];
[requestData release];
[connectionsArray removeObject:connection];
[connection cancel];
[connection release]; connection = nil;
if([connectionsArray count] < 1)
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
当应用程序从 INTERNET 获取数据来填充 GUI 时,保持 GUI 和 INTERNET 线程完全分离将很有帮助。在同样的情况下,以下选项确实对我有用。
使用NSOperationQueue的
-cancelAllOperations
方法。这将在当前正在运行的操作之后取消队列。如果您不取消该操作,它将在后台运行。在表和INTERNET线程之间保留一个桥梁数据结构。将其保留在 viewController 之后的其他类中,以便在弹出视图控制器时它不会被释放。
从该数据结构填充表并在其中填充新的可用数据。当新数据可用时,使用 NSNotification 或其他方法通知表。因此,在这种情况下,如果您让操作队列在后台运行,它将不会去更新 tableView。
编辑:
此外,当您使用 NSURLconnection 时,您可以使用
- (void)cancel
方法取消连接。一旦您取消连接,代表将不再接到呼叫。它可以通过两种方式提供帮助。取消上次操作。
在您的类中实现类似的内容,调用自定义委托的子级。
希望以上方法对大家有所帮助。
When an application fetches data from INTERNET to populate GUI, keeping GUI and INTERNET thread totally separate will be help full. Following options did worked for me in same situation.
Use
– cancelAllOperations
method of NSOperationQueue. this will cancel the quesue after operation that is running at present. If you do not cancel the operation then it will run on background.Keep a bridge data structure between table and INTERNET thread. Keep this in some other class then viewController so that it do not get released when you pop the viewcontroller.
Populate table from that data structure and fill newly available data in that. Notify table using a NSNotification OR some other method when new data is available. So here in this case if you keep you operation queue running in background it will not go to update the tableView.
EDIT:
Moreover, As you are using NSURLconnection you can use
- (void)cancel
method to cancel the connection. Delegate will no longer get calls once you cancel the connection. It can be helpful in two ways.Cancel your last operation.
Implement something like this in you class that calls child of custom delegate.
Hope above method is helpful.
标准开场白,着眼于未来:Apple 对 LLVM 项目的贡献表明 iOS 5 将带有自清零弱引用。因此,对象可以保存对其他对象的引用而不拥有它们,并且当这些对象被释放时,所有指向它们的指针都被神奇地设置为nil。我们必须等待开发者工具的公开发布才能查明 iOS 是否实现了该功能(出于明显的原因,它需要一些运行时支持),如果是,Apple 是否将其用于委托。然而,这可能很快就会发生,作为开发人员,您应该能够获得当前可用工具的 NDA 版本,因此可能值得根据项目的其他实用性考虑解决方案的途径。
此时此刻更有帮助:
NSURLConnection
的委托是不可变的。假设 NSURLConnection 是由您提到的 NSOperationQueue 上的任务创建的,您也可能在附加到主线程之外的某个运行循环上创建它们,从而带来整个将线程安全问题纳入其中。我建议明智的做法是:
NSURLConnection
附加到主运行循环。为此,您应该创建连接,以便它们不会立即启动,请使用scheduleInRunLoop:forMode:
指定[NSRunLoop mainRunLoop]
(可能还有NSDefaultRunLoopMode
代码>),然后启动它们。waitUntilAllOperationsAreFinished
在操作队列上,然后发送cancel
到所有正在进行的连接(例如[self.arrayOfConnections makeObjectsPerformSelector:@selector(cancel)]
)连接在收到
cancel
消息后保证不会与其委托进行通信。您希望等到队列中的所有操作完成,以避免潜在的竞争条件,即视图控制器在某个相关时间成功终止所有连接后被释放,但尚未完成的操作随后尝试添加新连接。另外,根据我们对问题的评论讨论:
NSURLConnection 有一个内置系统可以在运行循环上异步运行。运行循环是 Apple 版本的事件循环,粗略地说是要发布的消息列表。因此,它们允许您使用单个线程来做很多事情,但前提是没有任何事情会阻塞线程。
Apple 实际上建议进行异步 URL 访问的最有效方法(就处理和电池寿命而言)是允许 NSURLConnection 在主线程上异步运行。您可以将当前的
-(void) startConnection
调整为:然后将连接结束后您正在做的其他事情放入您的
connectionDidFinishLoading:
中:我自始至终都假设
NNSURLConnection
是NSURLConnection
的子类,它仅添加一些额外的属性,不会以其他方式影响行为。Standard opening comment, given prominence with an eye to the future: Apple's contributions to the LLVM project suggest that iOS 5 will come with self-zeroing weak references. So objects can hold references to other objects without owning them, and when those objects are deallocated all the pointers to them are magically set to
nil
. We'll have to wait for the public release of the developer tools to find out whether iOS implements that functionality (it needs some runtime support for obvious reasons) and, if so, whether Apple are using it for delegates. However, that's likely to be quite soon and as a developer you should be able to get the NDA version of the tools that is currently available, so it may be worth considering that route to a solution depending on the other practicalities of your project.More helpful for the here and now:
The delegate for
NSURLConnection
s is immutable. Assuming theNSURLConnection
s are created by tasks on theNSOperationQueue
that you mention, you may also be creating them on some runloop other than that attached to the main thread, bringing a whole sweep of thread safety issues into the mix.I'd suggest that the smart thing to do is:
NSURLConnection
s to the main runloop. To do that you should create connections so that they don't start immediately, usescheduleInRunLoop:forMode:
to nominate[NSRunLoop mainRunLoop]
(and probablyNSDefaultRunLoopMode
), then start them.@synchronized
block to add then to a suitableNSMutableArray
waitUntilAllOperationsAreFinished
on the operation queue and then sendcancel
to all ongoing connections (e.g.[self.arrayOfConnections makeObjectsPerformSelector:@selector(cancel)]
)Connections are guaranteed not to communicate with their delegates after they have received the
cancel
message. You want to wait until all operations in the queue are finished to avoid a potential race condition whereby the view controller is deallocated having successfully killed all connections at some relevant time but a not-yet-completed operation then tries to add a new one.Additional, based on our comment discussion on the question:
NSURLConnection has a built-in system to run asynchronously on a run loop. Run loops are Apple's version of an event loop, being in rough terms a list of messages to post. So they let you use a single thread to do lots of things but only if no single thing blocks the thread.
Apple actually recommend that the most efficient way (in terms of processing and battery life) to do an asynchronous URL access is by allowing NSURLConnection to run asynchronously on the main thread. You might adjust your current
-(void) startConnection
to:Then put the other things you were doing after the connection has ended into your
connectionDidFinishLoading:
:I've assumed throughout that
NNSURLConnection
is a subclass ofNSURLConnection
that just adds a few extra properties, not otherwise affecting behaviour.也许你这样做有一个很好的理由,但为什么你不使用第三方库 ASIHttpConnection 呢?
它管理 URL 连接详细信息,甚至带有内置操作队列。
另一件值得注意的事情是,Tommy Obliquely 指出 ARC 即将推出(ARC 已公开宣布,因此可以提及它的存在)。由于 ARC 支持 4.0 设备,因此如果可能的话,迁移到 4.0 设备将是一个非常好的主意。您可能会遇到一些问题,但它解决的问题可能比它引起的问题还要多。
There is perhaps a very good reason why you are doing things this way, but why are you not using the third party library ASIHttpConnection?
It manages URL connection details and even comes with a built in operation queue.
One other thing of note, as Tommy Obliquely notes ARC is upcoming (ARC has been publicly announced, therefore it's OK to mention it exists). Since ARC supports 4.0 devices, it would be a REALLY good idea to move to that if at all possible. You may run into some issues but it will probably cure more problems than it will cause.