How to set TimeInterval in NSRunLoop

Discussion in 'iOS Programming' started by mikezang, Aug 30, 2010.

  1. mikezang macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #1
    I use code as below to download data from Internet, that file is very large about 10MB, I want to know if I have to change 60 to 600 or 6000?

    I read document about [NSRunLoop currentRunLoop ] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:], but I am still not sure about it, does anyone explain to me?

    Code:
    -(void)fetchURL {
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
      [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
      [[NSRunLoop currentRunLoop ] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
      [pool release];
    }
    
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    Why do you need the NSRunloop stuff at all? NSURLConnection will download in the background using it's async API all by itself.
     
  3. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #3
    really? My code as below, you mean the line before last line doesn't need?
    Code:
    -(void) fetchURL {
    	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
    	[[NSURLConnection alloc] initWithRequest:request delegate:self];
    	[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:600]];
    	[pool release];
    }
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    I am asking you what you think it's doing and why you have it.
     
  5. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #5
    I am not sure so I post here, if you don't want to answer me it is ok!

    But don't tell me to check documents on apple site, I have read!

    Well, If you are a expert, don't think all people like you.

    This is a forum to answer people's question, they want to get answer ASAP, though they might get some info after read all documents, but that will spend a lot of time, so they ask here.
     
  6. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    You must have had a reason to think you needed that. To write that you'd have had to read quite a bit of documentation on NSRunLoop. Anyway you absolutely don't need it in any way. NSURLConnection attaches itself to the run loop and will call your delegate methods as required.

    As for reading the documentation: you need to do so with more care. No where in the NSURLConnection documentation does it even suggest that such code is required. Randomly adding weird code will just cause you issues. Slow down and think more about what you are writing does. If you cannot explain why a line or section of code is there and exactly what it is doing you should not be writing it as you do not know what it's effect will be and the behaviour of your application will not be predictable (to you).
     
  7. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #7
  8. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #8
    OK, I'm only telling you what I use successfully without issue and understand 100% as I've not copied it from somewhere without knowing what the hell I am doing. Why don't you explain more about what's going on. Are you initiating your downloads from a thread other than the main thread? If so I can see that you might need to do something odd to attach your NSURLConnection to the main run loop to make it work. The simpler solution would be to just use the standard NSURLConnection async API on the main thread: it's non-blocking and runs in the background anyway. I would have to have an amazingly good reason to run the download in another thread.
     
  9. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #9
    You like to use NSObject's performSelectorInBackground message, but this way that you can't communicate with downloading and don't know when download is finished.

    I used NSThread, so that I can send results back to the main thread by
    Code:
    [self performSelectorOnMainThread:@selector(updateContent:)  
    withObject:str waitUntilDone:NO]; 
    
    this is what I need.
     
  10. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #10
    My point is you don't need anything to be in another thread: the NSURLConnection async API runs in the background and when the download is complete you can call whatever you need. No extra threads. No messing with NSRunLoop. No complication.
     
  11. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #11
    Can you show me your method if you don't mind? I mean don't use NSRunLoop and doing NSURLConnection async.

    I want compare the two ways.
     
  12. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #12
    I'm not on my Mac right now so don't have code to hand. There is a decent looking set of code here showing async usage.
     
  13. firewood macrumors 604

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #13
    Are you trying to make an async routine synchronous in the main UI thread? Bad idea.

    Return/exit from your code (whatever is calling fetchURL). Let the NSURL callback call your code later and let you know that the data is ready. Then continue on.
     
  14. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #14
    Well, the source like this, how can I do follow your method?
    Code:
    #import "ImagesViewController.h"
    #import "InternetResource.h"
    
    #define SOME_RESOURCE_URL @"http://media.wiley.com/product_data/coverImage/28/04707428/0470742828.jpg"
    #define BAD_RESOURCE_URL2 @"http://media.wiley.com/product_data/coverImage/5.jpg"
    
    @implementation ImagesViewController
    
    @synthesize iResources;
    
    - (id)initWithStyle:(UITableViewStyle)style {
      if (self = [super initWithStyle:style]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleFinishedLoading:) name:FinishedLoading object:nil];
        self.iResources = [NSArray  arrayWithObjects:
                           [[[InternetResource alloc] initWithTitle:@"First pic" andURL:BAD_RESOURCE_URL2] autorelease],
                           [[[InternetResource alloc] initWithTitle:@"Second pic" andURL:SOME_RESOURCE_URL] autorelease],
                           [[[InternetResource alloc] initWithTitle:@"Third pic" andURL:SOME_RESOURCE_URL] autorelease],
                           [[[InternetResource alloc] initWithTitle:@"Fourth pic" andURL:SOME_RESOURCE_URL] autorelease],
                           [[[InternetResource alloc] initWithTitle:@"Fifth pic" andURL:SOME_RESOURCE_URL] autorelease],  
                           [[[InternetResource alloc] initWithTitle:@"Sixth pic" andURL:SOME_RESOURCE_URL] autorelease],
                           nil];
      }
      return self;
    }
    
    -(void)handleFinishedLoading:(NSNotification*)notification{
      [self performSelectorOnMainThread:@selector(reloadTheData:) withObject:notification.object waitUntilDone:NO]; 
    }
    
    -(void)reloadTheData:(InternetResource*)_resource{
      [self.tableView reloadData];
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.iResources.count;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
      return 140;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
      static NSString *CellIdentifier = @"Cell";
      UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
      if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
      }
      InternetResource  *iResource = [self.iResources objectAtIndex:indexPath.row];
      cell.textLabel.text = iResource.title;
      cell.imageView.image = nil;
      @synchronized(iResource){
        switch (iResource.status) {
          case NEW:
            cell.imageView.image = [UIImage imageNamed:@"loading.png"];
            [iResource start];
            break;
          case COMPLETE:
            cell.imageView.image = iResource.image;
            break;
          case FAILED:
            cell.imageView.image = [UIImage imageNamed:@"failed.png"];
            break;
          case FETCHING:
            cell.imageView.image = [UIImage imageNamed:@"loading.png"];
            break;
          default:
            cell.imageView.image = nil;
            break;
        }
      }
      return cell;
    }
    
    - (void)dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      self.iResources = nil;
      [super dealloc];
    }
    @end
    
    Code:
    #import "InternetResource.h"
    
    @implementation InternetResource
    
    @synthesize url, title, image, status;
    
    -(id)initWithTitle:(NSString*)_title andURL:(NSString*)_url{
      if(self = [super init]){
        self.title = _title;
        self.url = _url;
        self.status = NEW;
      }
      return self;
    }
    
    -(void)start{
      self.status = FETCHING;
      receivedData = [[NSMutableData data] retain];
      [NSThread detachNewThreadSelector:@selector(fetchURL) toTarget:self withObject:nil];
    }
    
    -(void)fetchURL {
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
      NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];
      [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
      [[NSRunLoop currentRunLoop ] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60]];
      [pool release];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
      int statusCode = ((NSHTTPURLResponse*) response).statusCode;
      if(statusCode != 200){
        @synchronized(self){
          self.status = FAILED;
        }
      }
      [receivedData setLength:0];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
      [receivedData appendData:data];
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
      [connection release];
      if(receivedData){
        [receivedData release];
        receivedData = nil;
      }
      @synchronized(self){
        self.status = FAILED;
      }
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
      @synchronized(self){
        if(self.status != FAILED){
          self.status = COMPLETE;
          self.image = [UIImage imageWithData:receivedData];    
          [receivedData release];
          receivedData = nil;
        }
      }
      [[NSNotificationCenter defaultCenter] postNotificationName:FinishedLoading object:self];
      [connection release];  
    }          
    
    -(void)dealloc{
      if(receivedData){
        [receivedData release];
      }
      self.title = nil;
      self.url = nil;
      self.image = nil;
      [super dealloc];
    }
    @end
    
     
  15. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #15
    Maybe just modify to code as below is ok

    old:
    Code:
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:600]];
    
    new:
    Code:
    [[NSRunLoop currentRunLoop] run];
     
  16. firewood macrumors 604

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #16
    You will need to break things up at a higher level so that you don't depend on your table view being loaded until the data actually arrives. Don't wait to finish the entire data array and table initialization in the init. Let the table view get loaded and refreshed over time (lazy loading, etc.) That way the user isn't stuck with a frozen UI waiting for table items that aren't even on the display.

    For example, the NPR News iPhone app spins little activity indicators in each unloaded table item as it loads each story, one at a time. But the UI isn't blocked waiting, and the user can jump into the first story without waiting the 5 minutes it takes to load the entire story list.
     
  17. mikezang thread starter macrumors 6502a

    Joined:
    May 22, 2010
    Location:
    Tokyo, Japan
    #17
    If you can provide a skeleton code, that will be helpful...
     
  18. Luke Redpath macrumors 6502a

    Joined:
    Nov 9, 2007
    Location:
    Colchester, UK
    #18
    Fair enough...you got the code from the book. All I can say is to anybody reading this, don't buy that book, if that's the sort of code its recommending you use, as it's rubbish.

    Some general points on networking:

    1) Always prefer asynchronous APIs over threading if one is available
    2) Even if you want to wrap up your tasks as a discrete operation, don't use threads. Use NSOperation (you can use it with a synchronous API, or in concurrent mode with asynchronous APIs. Read this article).
    3) If you think you need to use threads, I repeat, once again, consider using NSOperation or GrandCentralDispatch if it is available on your targted platform as they are almost always the better choice.
     

Share This Page