2013-10-09 45 views
3

我的应用使用JSON解析来自Rails应用的信息。我正在寻找一种异步加载JSON的方法,但由于代码的复杂性,我无法让我的代码与我找到的示例一起工作。我需要做些什么才能使我的JSON加载异步?谢谢。如何异步加载JSON(iOS)

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    NSURL *upcomingReleaseURL = [NSURL URLWithString:@"http://obscure-lake-7450.herokuapp.com/upcoming.json"]; 

    NSData *jsonData = [NSData dataWithContentsOfURL:upcomingReleaseURL]; 

    NSError *error = nil; 

    NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; 

    NSArray *upcomingReleasesArray = [dataDictionary objectForKey:@"upcoming_releases"]; 

    //This is the dateFormatter we'll need to parse the release dates 
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 
    [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"]; 
    NSTimeZone *est = [NSTimeZone timeZoneWithAbbreviation:@"EST"]; 
    [dateFormatter setTimeZone:est]; 
    [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]]; //A bit of an overkill to avoid bugs on different locales 

    //Temp array where we'll store the unsorted bucket dates 
    NSMutableArray *unsortedReleaseWeek = [[NSMutableArray alloc] init]; 
    NSMutableDictionary *tmpDict = [[NSMutableDictionary alloc] init]; 

    for (NSDictionary *upcomingReleaseDictionary in upcomingReleasesArray) { 

     //We find the release date from the string 
     NSDate *releaseDate = [dateFormatter dateFromString:[upcomingReleaseDictionary objectForKey:@"release_date"]]; 

     //We create a new date that ignores everything that is not the actual day (ignoring stuff like the time of the day) 
     NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; 
     NSDateComponents *components = 
     [gregorian components:(NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit) fromDate:releaseDate]; 

     //This will represent our releases "bucket" 
     NSDate *bucket = [gregorian dateFromComponents:components]; 

     //We get the existing objects in the bucket and update it with the latest addition 
     NSMutableArray *releasesInBucket = [tmpDict objectForKey:bucket]; 
     if (!releasesInBucket){ 
      releasesInBucket = [NSMutableArray array]; 
      [unsortedReleaseWeek addObject:bucket]; 
     } 

     UpcomingRelease *upcomingRelease = [UpcomingRelease upcomingReleaseWithName:[upcomingReleaseDictionary objectForKey:@"release_name"]]; 
     upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:@"release_date"]; 
     upcomingRelease.release_price = [upcomingReleaseDictionary objectForKey:@"release_price"]; 
     upcomingRelease.release_colorway = [upcomingReleaseDictionary objectForKey:@"release_colorway"]; 
     upcomingRelease.release_date = [upcomingReleaseDictionary objectForKey:@"release_date"]; 
     upcomingRelease.thumb = [upcomingReleaseDictionary valueForKeyPath:@"thumb"]; 
     upcomingRelease.images = [upcomingReleaseDictionary objectForKey:@"images"]; 
     [releasesInBucket addObject:upcomingRelease]; 
     [tmpDict setObject:releasesInBucket forKey:bucket]; 
    } 

    [unsortedReleaseWeek sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { 
     NSDate* date1 = obj1; 
     NSDate* date2 = obj2; 
     //This will sort the dates in ascending order (earlier dates first) 
     return [date1 compare:date2]; 
     //Use [date2 compare:date1] if you want an descending order 
    }]; 

    self.releaseWeekDictionary = [NSDictionary dictionaryWithDictionary:tmpDict]; 
    self.releaseWeek = [NSArray arrayWithArray:unsortedReleaseWeek]; 

} 
+0

注意,如果viewDidLoad中启动下载,您将需要以某种方式导致下载结束“刷新”你的观点,因为下载将viewDidLoad中后结束已经返回。 –

回答

0

下载数据异步在这样的回答:Object-c/iOS :How to use ASynchronous to get a data from URL?

然后通过JSON解析器运行它。

+0

另外,我建议在另一个线程上执行所有JSON解析和对象创建。最好初始化'gregorian'一次(不在循环中),因为'NSCalendar'初始化缓慢。 –

1

如果你只想得到这个只有json数据,你不需要设置很多东西。

使用下面的代码。创建获取NSData对象的jsonParse方法。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul); 
dispatch_async(queue, ^{ 

    NSData *data = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:@"http://obscure-lake-7450.herokuapp.com/upcoming.json"]]; 
    dispatch_sync(dispatch_get_main_queue(), ^{ 

     [self jsonParse:data]; 

    }); 
}); 
+0

为什么不使用'NSURLConnection'方便的类方法'sendAsynchronousRequest:queue:completionHandler':[NSURLConnection Class Reference](https://developer.apple。COM /库/ IOS /文档/可可/参考/基础/班/ NSURLConnection_Class /参考/#的reference.html // apple_ref/OCC/CLM/NSURLConnection的/ sendAsynchronousRequest:队列:completionHandler :)?虽然这仍然是一个天真的方法,但至少有更好的错误处理。 – CouchDeveloper

0

在后台一般运行代码线程,你可以使用这个方法:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
    // Code here is run on a background thread 

    dispatch_async(dispatch_get_main_queue(), ^{ 
      // Code here is run on the main thread (the UI thread) after your code above has completed so you can update UI after the JSON call has completed if you need to. 
    }); 
}); 

但请记住,苹果不允许你更新在后台线程的UI元素。另外,它们不允许你从后台线程产生更多的线程,它必须从主线程完成。

7

一个简单的方法是用NSURLConnection的方便的分类方法sendAsynchronousRequest:queue:error

以下代码片段是如何从服务器加载JSON以及完成处理程序在解析JSON的后台线程上执行的示例。它还执行所有推荐的错误检查:

NSURL* url = [NSURL URLWithString:@"http://example.com"]; 
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url]; 
[urlRequest addValue:@"application/json" forHTTPHeaderField:@"Accept"]; 
NSOperationQueue* queue = [[NSOperationQueue alloc] init]; 

[NSURLConnection sendAsynchronousRequest:urlRequest 
            queue:queue 
         completionHandler:^(NSURLResponse* response, 
              NSData* data, 
              NSError* error) 
{ 
    if (data) { 
     NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; 
     // check status code and possibly MIME type (which shall start with "application/json"): 
     NSRange range = [response.MIMEType rangeOfString:@"application/json"]; 

     if (httpResponse.statusCode == 200 /* OK */ && range.length != 0) { 
      NSError* error; 
      id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; 
      if (jsonObject) { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        // self.model = jsonObject; 
        NSLog(@"jsonObject: %@", jsonObject); 
       }); 
      } else { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        //[self handleError:error]; 
        NSLog(@"ERROR: %@", error); 
       }); 
      } 
     } 
     else { 
      // status code indicates error, or didn't receive type of data requested 
      NSString* desc = [[NSString alloc] initWithFormat:@"HTTP Request failed with status code: %d (%@)", 
           (int)(httpResponse.statusCode), 
           [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode]]; 
      NSError* error = [NSError errorWithDomain:@"HTTP Request" 
               code:-1000 
              userInfo:@{NSLocalizedDescriptionKey: desc}]; 
      dispatch_async(dispatch_get_main_queue(), ^{ 
       //[self handleError:error]; // execute on main thread! 
       NSLog(@"ERROR: %@", error); 
      }); 
     } 
    } 
    else { 
     // request failed - error contains info about the failure 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      //[self handleError:error]; // execute on main thread! 
      NSLog(@"ERROR: %@", error); 
     }); 
    } 
}]; 

虽然,它显得有些复杂的,IMO这是一种简约,仍然幼稚的做法。在其他的缺点,主要问题是:

  • 它缺乏取消请求的可能性,并
  • 没有办法来处理更复杂的认证。

更复杂的方法需要利用NSURLConnection代表。通常,第三方库确实以这种方式实现它,将NSURLConnection请求和其他相关状态信息封装到NSOperation的子类中。您可以从自己的实施开始,例如使用this code作为模板。

+0

你能解释你的'dispatch_async'调用的目的吗?如果我的理解正确,这个块被放置在一个作为主线程的一部分的队列中,所以它应该在main_queue的同一线程上执行,对吧? –

+1

@CodeCommander便捷类方法'sendAsynchronousRequest:queue:completionHandler:'的完成块将在指定的_queue_上执行。 _queue_是一个'NSOperationQueue',它是使用'[[NSOperationQueue alloc] init]'创建的,它使用了“私有执行上下文”,即它不是主线程。你可以在官方文档“NSOperationQueue”和“NSURLConnection”中阅读更多内容。 ;) – CouchDeveloper

+0

我明白了,如果将'dispatch_get_main_queue()'的结果作为队列传递给'sendAsynchronousRequest:queue:completionHandler:'会导致主线程在等待请求时阻塞吗? (击败异步调用的目的) –

-1

试试这个代码:

 NSURL * inkURL = [NSURL URLWithString:@"your url"]; 
     NSURLRequest * request = [[NSURLRequest alloc]initWithURL:inkURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0]; 
     NSOperationQueue * queue = [[NSOperationQueue alloc]init]; 
     [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * response, NSData * data, NSError * error) { 
      NSData * jsonData = [NSData dataWithContentsOfURL:inkURL]; 
      NSDictionary * dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; 
      self.inkArray = [dataDictionary objectForKey:@"users"]; 


     }]; 
0
NSString *[email protected]"http://itunes.apple.com/in/rss/topsongs/limit=25/json"; 
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:urlstr]]; 

[NSURLConnection sendAsynchronousRequest:request 
            queue:[[NSOperationQueue alloc] init] 
         completionHandler:^(NSURLResponse* response, 
              NSData* data, NSError* error) 
{ 
    NSError *myError = nil; 
    NSDictionary *dic1 = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&myError]; 

    if (myError ==nil) { 
     NSDictionary*feed =[dic1 objectForKey:@"feed"]; 
     NSArray*arrayofentry =[feed objectForKey:@"entry"]; 

     for(NSDictionary *dic2 in arrayofentry) { 
      requestReply=[dic2 objectForKey:@"title"]; 
      [arr1 addObject:requestReply]; 
     } 
     [self.table reloadData]; 
    } 
}]; 
+0

也许你可以很快解释你在做什么? –

+0

这种方法有两个问题:1)检查out参数myError不是检查方法JSONObjectWithData失败的正确方法。相反,你需要检查_return value_,也就是检查'dic1'是否等于'nil',然后_只有那么_你可以访问out参数'myError'。否则,该方法是成功的。 2)完成处理程序在私有队列上执行(由于'queue:[[NSOperationQueue alloc] init') - 也就是说,代码不会在主线程上执行 - 并且'[self.table reloadData]'会崩溃 - 因为UIKit方法不能在非主线程上运行。 ;) – CouchDeveloper