2013-07-02 51 views
4

我想在后台线程的NSOperation内部做一个Asynchrous NSURLConnection。这是因为我们在数据回复时正在对数据进行一些非常昂贵的操作。如何在NSOperation内启动异步NSURLConnection?

这是一个非常类似的问题他们问什么位置: How do I do an Asychronous NSURLConnection inside an NSOperation?

,但不同的是,我运行另一个类的连接。

这是我第一次尝试:

在我MainViewController:

@property (nonatomic, strong) NSOperationQueue *requestQueue; 

#pragma mark - Lazy initialization 
- (NSOperationQueue *)requestQueue 
{ 
    if (!_requestQueue) { 
     _requestQueue = [[NSOperationQueue alloc] init]; 
     _requestQueue.name = @"Request Start Application Queue"; 
     _requestQueue.maxConcurrentOperationCount = 1; 
    } 
    return _requestQueue; 
} 

-(void)callToServer 
{ 
URLJsonRequest *request = [URLRequestFactory createRequest:REQUEST_INTERFACE_CLIENT_VERSION 
                 delegate:self]; 

    RequestSender *requestSender = [[RequestSender alloc]initWithPhotoRecord:request delegate:self]; 

    [self.requestQueue addOperation:requestSender]; 
} 

这里是我的操作:

- (id)initWithPhotoRecord:(URLJsonRequest *)request 
       delegate:(id<RequestSenderDelegate>) theDelegate{ 

    if (self = [super init]) 
    { 
     self.delegate = theDelegate; 
     self.jsonRequest = request; 
    } 
    return self; 
} 

- (void)main { 

    //Apple recommends using @autoreleasepool block instead of alloc and init NSAutoreleasePool, because blocks are more efficient. You might use NSAuoreleasePool instead and that would be fine. 
    @autoreleasepool 
    { 

     if (self.isCancelled) 
      return; 

     [self.jsonRequest start]; 

    } 
} 

这里是我的请求启动功能:

-(void) start 
{ 
    NSURL *url = [NSURL URLWithString:@"http://google.com"]; 
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url]; 
    urlConnection = [[[NSURLConnection alloc] initWithRequest:theRequest delegate:self]autorelease]; 

[urlConnection start]; 
[theRequest release] 
} 


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ 
    NSLog(@"Received reponse from connection"); 
} 

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ 



} 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{ 

} 

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ 

} 

我没有收到服务器的回应。 感谢

+0

顺便说一句,如果你使用'initWithRequest'没有设置为'NO'了'startImmediately'选项,你不应该',否则自动start'连接(因为它会开始,并发出另一个'开始'会干扰)。考虑到你需要在运行循环中安排它,使用'startImmediately'设置为'NO'的方式,在运行循环中调度它,然后'开始'它。 – Rob

+0

也许我忽略了一些东西,但是我也没有看到你为[isExecuting]和'isFinished'做了必要的KVN通知,当[子类化你自己的操作](http:// developer。 apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW16)。还是你不支持并发操作? – Rob

+0

如果要控制同时活动连接的最大数量,则只需要'NSOperation'的子类。否则,从NSOperation继承子是毫无意义的。无论如何,'NSURLConnection'不会在队列的执行上下文中运行 - 除了start方法。什么是重要的是你的委托方法将被执行。使用专用线程是一种方法,将工作(处理块)分派到另一个队列是另一种方法。两种方法都有优点和缺点。 – CouchDeveloper

回答

2

一对夫妇的方法:

  1. 计划的主运行循环NSURLConnection,通过使用NOstartImmediately参数,设置运行循环,然后才能开始连接,例如:

    urlConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self startImmediately:NO]; 
    [urlConnection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; 
    [urlConnection start]; 
    
  2. 为连接创建一个专用线程并在为该线程创建的运行循环中计划连接。请参阅AFURLConnectionOperation.mAFNetworking源文件中的示例。

  3. 实际上使用AFNetworking,它给你基于NSOperation的操作,你可以添加到你的队列中,并为你处理这个运行循环的东西。


所以,AFNetworking确实是这样的:

+ (void)networkRequestThreadEntryPoint:(id)__unused object { 
    @autoreleasepool { 
     [[NSThread currentThread] setName:@"NetworkingThread"]; 

     NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
     [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; 
     [runLoop run]; 
    } 
} 

+ (NSThread *)networkRequestThread { 
    static NSThread *_networkRequestThread = nil; 
    static dispatch_once_t oncePredicate; 

    dispatch_once(&oncePredicate, ^{ 
     _networkRequestThread = [[NSThread alloc] initWithTarget:self 
                 selector:@selector(networkRequestThreadEntryPoint:) 
                  object:nil]; 
     [_networkRequestThread start]; 
    }); 

    return _networkRequestThread; 
} 

所以我这样做以下。首先,我有一些私人性质:

@property (nonatomic, readwrite, getter = isExecuting) BOOL executing; 
@property (nonatomic, readwrite, getter = isFinished) BOOL finished; 
@property (nonatomic, weak) NSURLConnection *connection; 

然后,网络操作则可以这样做:

@synthesize executing = _executing; 
@synthesize finished = _finished; 

- (instancetype)init { 
    self = [super init]; 
    if (self) { 
     _executing = NO; 
     _finished = NO; 
    } 
    return self; 
} 

- (void)start { 
    if (self.isCancelled) { 
     [self completeOperation]; 
     return; 
    } 

    self.executing = YES; 

    [self performSelector:@selector(startInNetworkRequestThread) 
       onThread:[[self class] networkRequestThread] 
       withObject:nil 
      waitUntilDone:NO]; 
} 

- (void)startInNetworkRequestThread { 
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:self.request 
                    delegate:self 
                  startImmediately:NO]; 
    [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
    [connection start]; 

    self.connection = connection; 
} 

- (void)completeOperation { 
    self.executing = NO; 
    self.finished = YES; 
} 

- (void)setFinished:(BOOL)finished { 
    if (finished != _finished) { 
     [self willChangeValueForKey:@"isFinished"]; 
     _finished = finished; 
     [self didChangeValueForKey:@"isFinished"]; 
    } 
} 

- (void)setExecuting:(BOOL)executing { 
    if (executing != _executing) { 
     [self willChangeValueForKey:@"isExecuting"]; 
     _executing = executing; 
     [self didChangeValueForKey:@"isExecuting"]; 
    } 
} 

- (BOOL)isConcurrent { 
    return YES; 
} 

- (BOOL)isAsynchronous { 
    return YES; 
} 

// all of my NSURLConnectionDataDelegate stuff here, for example, upon completion: 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { 
    // I call the appropriate completion blocks here, do cleanup, etc. and then, when done: 

    [self completeOperation]; 
} 
+0

@ Rob.Option 2非常适合我。 我可以从你那里获得有关如何编写将与服务器联系并解析信息的线程的另一个信息。 谢谢。 –

+0

@GuyKahlonMatrix我已经用示例代码更新了我的答案,以便在此操作中执行下载。 – Rob

+0

谢谢,帮了我很多。 –

2
-(void)start 
{ 
    [self willChangeValueForKey:@"isExecuting"]; 
    _isExecuting = YES; 
    [self didChangeValueForKey:@"isExecuting"]; 
    NSURL* url = [[NSURL alloc] initWithString:@"http://url.to/feed.xml"]; 
    NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20]; 
    _connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; // ivar 
    [request release]; 
    [url release]; 
    // Here is the trick 
    NSPort* port = [NSPort port]; 
    NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop 
    [rl addPort:port forMode:NSDefaultRunLoopMode]; 
    [_connection scheduleInRunLoop:rl forMode:NSDefaultRunLoopMode]; 
    [_connection start]; 
    [rl run]; 
} 

更多细节可以在这里找到:link

+0

您是否知道在此链接中提及的解决方案? http://www.russellj.co.uk/blog/2011/07/09/nsurlconnection-in-a-background-thread/#comment-168 如果你可以扩大一点在两个解决方案之间的差异。 –

0

我知道这个帖子是一岁多,但我想补充一些建议试图创建自己的异步网络操作的人可能会遇到同样的问题。 您需要将runloop添加到在后台运行的操作中,并且在操作完成后应该停止它。

实际上有两个简单的选择:

选项1 - 使用NSRunLoop

NSPort *port  = [NSPort port]; 
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
[runLoop addPort:port forMode:NSDefaultRunLoopMode]; 
[self.connection scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode]; 
[self.connection start]; 
[runLoop run]; 

,你需要停止时,操作完成:

NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 
NSDate *date  = [NSDate distantFuture]; 
while (!runLoopIsStopped && [runLoop runMode:NSDefaultRunLoopMode beforeDate:date]); 

选项2 - 使用CF

您需要添加

CFRunLoopRun(); 

当你开始操作

,并呼吁

CFRunLoopStop(CFRunLoopGetCurrent()); 

当你完成操作。

阅读下面的帖子:CFRunLoopRun() vs [NSRunLoop run]