NSOperationQueue和线程问题

时间:2011-09-11 04:17:54

标签: iphone objective-c multithreading 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];
    }
}

3 个答案:

答案 0 :(得分:1)

当应用程序从INTERNET获取数据以填充GUI时,保持GUI和INTERNET线程完全分离将有助于完整。在相同的情况下,以下选项对我有效。

  1. 使用NSOperationQueue的– cancelAllOperations方法。这将取消目前正在运行的操作后的问题。如果您不取消操作,那么它将在后台运行。

  2. 在表和INTERNET线程之间保持桥数据结构。将它保存在其他类中,然后保存在viewController中,以便在弹出viewcontroller时不会释放它。

  3. 从该数据结构填充表并填充其中的新可用数据。当新数据可用时,使用NSNotification或其他方法通知表。所以在这种情况下,如果你让你在后台运行的操作队列,它将不会更新tableView。

  4. 编辑:

    此外,在使用NSURLconnection时,可以使用- (void)cancel方法取消连接。取消连接后,代理将不再接听电话。它有两种方式有用。

    1. 取消上一次操作。

    2. 在您调用自定义委托子项的类中实现类似这样的内容。

    3. 希望以上方法有所帮助。

答案 1 :(得分:1)

标准开场评论,突出了未来:Apple对LLVM项目的贡献表明iOS 5将带有自我归零的弱引用。因此,对象可以在不拥有它们的情况下保存对其他对象的引用,并且当这些对象被释放时,所有指向它们的指针都被神奇地设置为nil。我们将不得不等待开发人员工具的公开发布,以确定iOS是否实现了该功能(由于显而易见的原因需要一些运行时支持),如果是这样,Apple是否将其用于代理。但是,这很可能很快,作为开发人员,您应该能够获得当前可用的工具的NDA版本,因此根据项目的其他实际情况,可能需要考虑到解决方案的路径。 / p>

对现在和现在更有帮助:

NSURLConnection的委托是不可变的。假设NSURLConnection是由你提到的NSOperationQueue上的任务创建的,你也可能在一些runloop上创建它们而不是附加到主线程的那些,从而将整个线程安全问题带入混合。

我建议聪明的事情是:

  1. 确保您将所有NSURLConnection附加到主runloop。为此,您应该创建连接,以便它们不会立即启动,使用scheduleInRunLoop:forMode:提名[NSRunLoop mainRunLoop](可能NSDefaultRunLoopMode),然后启动它们。
  2. 同时保留已启动的所有连接的列表。您可能希望使用@synchronized块将其添加到合适的NSMutableArray
  3. 当要取消分配被指定接收委托方法的对象时,在操作队列上执行waitUntilAllOperationsAreFinished,然后将cancel发送到所有正在进行的连接(例如[self.arrayOfConnections makeObjectsPerformSelector:@selector(cancel)]
  4. 保证连接在收到cancel消息后不与其代理通信。您希望等到队列中的所有操作都完成,以避免潜在的竞争条件,即视图控制器已取消分配,但在某个相关时间成功终止了所有连接,但尚未完成的操作则尝试添加新的连接。 / p>


    另外,根据我们对该问题的评论讨论:

    NSURLConnection有一个内置系统,可以在运行循环中异步运行。运行循环是Apple的事件循环版本,粗略地说是要发布的消息列表。所以他们让你使用一个线程做很多事情,但前提是没有一件东西阻止线程。

    Apple实际上建议通过允许NSURLConnection在主线程上异步运行来实现异步URL访问的最有效方式(在处理和电池寿命方面)。您可以将当前-(void) startConnection调整为:

    - (BOOL)startRequest
    {   
        // we're going to do all this right here on the main thread, so there's
        // no need to package 'request' and 'keyInfo' into a dictionary and create
        // an operation
    
        // create the connection
        NNSURLConnection* connection = [[NNSURLConnection alloc] initWithRequest:request delegate:self];
    
        if(connection)
        {
            NSMutableData *requestData = [[NSMutableData alloc] init];
    
            connection.url = [keyInfo URL];
    
            connection.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:keyInfo, kUserInfoKey, requestData, kRequestDataKey, nil];
            [connectionsArray addObject:connection];
    
            [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
        }
    
        // We used initWithRequest:delegate: so the request has already started.
        // The connection will now run asynchronously and we can wait for
        // delegate messages, which will be delivered via the runloop on this thread
    }
    

    然后在连接结束后将您正在做的其他事情放入connectionDidFinishLoading:

    - (void)connectionDidFinishLoading:(NNSURLConnection *)connection {
    
        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];
        }
    
        /* ...and all the other stuff here... */
    }
    

    我一直认为NNSURLConnectionNSURLConnection的子类,只是添加了一些额外的属性,而不会影响行为。

答案 2 :(得分:0)

为什么你这样做是有很好的理由,但你为什么不使用第三方库ASIHttpConnection?

它管理URL连接细节,甚至还带有内置的操作队列。

还有一点值得注意,正如Tommy Obliquely指出ARC即将上市(ARC已经公开宣布,因此可以提及它存在)。由于ARC支持4.0设备,如果可能的话,移动到那将是一个非常好的主意。你可能遇到一些问题,但它可能会解决比它造成的问题更多的问题。