Please note, this blog entry is from a previous course. You might want to check out the current one.
Still allow the user to force a fetch using the refreshControl in the appropriate table views. And use the cellular network to fetch only when the user prompts you via a refreshControl in this manner (otherwise restrict all your URLforRecentGeoreferencedPhotos fetches to WiFi only). If you are a bit stumped about how to know when to endRefreshing in a table view, think about whether there are some NSNotifications flying around that you could listen in on.
The refresh-control view is handled by the regions-table-view controller. The downloads are handled by the application delegate. To communicate between those two classes use notifications. The table-view controller will post a notification when a new cellular download should start. It will listen to another notification to know when the download has been finished. The application delegate will listen for the first notification and start the download. It will post the second notification when the download has finished.
The common constants are provided by the Flickr helper:
#define STARTCELLULARFLICKRFETCHNOTIFICATION @"startCellularFlickrFetch" #define FINISHEDCELLULARFLICKRFETCHNOTIFICATION @"finishedCellularFlickrFetch"
Connect both refresh controls from the storyboard to a common action method of the table-view controller (it might be they are still connected to the one from the previous assignment, remove those links). From this method post the notification to start the download:
- (IBAction)fetchRegions { [[NSNotificationCenter defaultCenter] postNotificationName:STARTCELLULARFLICKRFETCHNOTIFICATION object:self]; }
Setup the observer to know when the download has finished when the table-view has been loaded:
- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(finishedRefreshing) name:FINISHEDCELLULARFLICKRFETCHNOTIFICATION object:nil]; }
… and trigger a method which will hide the refresh control (in addition we check if the current download session has been triggered by the user):
- (void)finishedRefreshing { if ([FlickrHelper isCellularDownloadSession]) { [self.refreshControl endRefreshing]; } }
Setup the observer to trigger a cellular download when the application has been launched:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(startCellularFlickrFetch) name:STARTCELLULARFLICKRFETCHNOTIFICATION object:nil]; ... }
Move the code from the previous startFlickrFetch to a new method adding an additional attribute to enable or disable cellular downloads and use it for the different kind of downloads:
- (void)startFlickrFetchAllowingCellularAccess:(BOOL)cellular { [FlickrHelper startBackgroundDownloadRecentPhotosOnCompletion:^(NSArray *photos, void (^whenDone)()) { [self useDocumentWithFlickrPhotos:photos]; if (whenDone) whenDone(); } allowingCellularAccess:cellular]; } - (void)startCellularFlickrFetch { [self startFlickrFetchAllowingCellularAccess:YES]; } - (void)startFlickrFetch { [self startFlickrFetchAllowingCellularAccess:NO]; }
We need also to adjust the public method of the Flickr helper to handle the additional attribute:
+ (void)startBackgroundDownloadRecentPhotosOnCompletion:(void (^)(NSArray *photos, void(^whenDone)()))completionHandler allowingCellularAccess:(BOOL)cellular;
For the mandatory tasks cellular downloads are already disabled for background fetches. To disable cellular downloads by default, just do the same for the common download session:
- (NSURLSession *)downloadSession { ... NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:FLICKR_FETCH]; config.allowsCellularAccess = NO; _downloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; ... }
Unfortunately enabling cellular access later on does not work by changing allowsCellularAccess. The configuration of a session can not be changed once the session has been created. Thus, create three additional properties to allow enabling and disabling the cellular access:
@property (strong, nonatomic) NSURLSession *cellularDownloadSession; @property (strong, nonatomic) NSURLSession *currentDownloadSession; @property (nonatomic) BOOL allowingCellularAccess;
The cellular download session is identical to the existing download session, but has cellular access enabled:
#define FLICKR_FETCH_CELLULAR @"Cellular Flickr Download Session" - (NSURLSession *)cellularDownloadSession { if (!_cellularDownloadSession) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfiguration:FLICKR_FETCH_CELLULAR]; config.allowsCellularAccess = YES; _cellularDownloadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; }); } return _cellularDownloadSession; }
Depending on the property – presenting our wish to enable or disable cellular access – currentDownloadSession will provide those two sessions:
- (NSURLSession *)currentDownloadSession { if (self.allowingCellularAccess) return self.cellularDownloadSession; return self.downloadSession; }
Back to the class method with the adjusted attributes. Set the selection property, and use the proper session:
+ (void)startBackgroundDownloadRecentPhotosOnCompletion:(void (^)(NSArray *photos, void(^whenDone)()))completionHandler allowingCellularAccess:(BOOL)cellular { FlickrHelper *fh = [FlickrHelper sharedFlickrHelper]; fh.allowingCellularAccess = cellular; NSURLSession *session = fh.currentDownloadSession; [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { ... }]; }
Adjust also the download method for the regions to work the “correct” session. The identifier of a task is a simple integer which gets incremented with each new tasks. Since there are now two different sessions adjust the key to identify the completion handler for the task to incorporate also the description of the session:
+ (void)startBackgroundDownloadRegionForPlaceID:(NSString *)placeID onCompletion:(RegionCompletionHandler)completionHandler { FlickrHelper *fh = [FlickrHelper sharedFlickrHelper]; NSURLSession *session = fh.currentDownloadSession; [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { NSURLSessionDownloadTask *task = [session downloadTaskWithURL:[FlickrFetcher URLforInformationAboutPlace:placeID]]; task.taskDescription = FLICKR_FETCH_REGION; [fh.regionCompletionHandlers setObject:[completionHandler copy] forKey:[NSString stringWithFormat:@"%@%d", session.description, task.taskIdentifier]]; [task resume]; }]; }
Even though the background fetch is already set to allow no cellular access, it triggers also background-session downloads for the regions. Set the session selector to make sure no cellular access is used:
+ (void)loadRecentPhotosOnCompletion:(void (^)(NSArray *places, NSError *error))completionHandler { ... [FlickrHelper sharedFlickrHelper].allowingCellularAccess = NO; ... }
Since the key for the completion handler changed above, adjust it also when it gets called after the download has finished:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { ... RegionCompletionHandler regionCompletionHandler = [self.regionCompletionHandlers[[NSString stringWithFormat:@"%@%d", session.description, downloadTask.taskIdentifier]] copy]; ... }
The error handler watches only for the general download session, make that more generic (maybe a bad idea):
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { if (error) { ... } }
Finally use the current session to call the final completion handler:
- (void)downloadTasksMightBeComplete { ... NSURLSession *session = self.currentDownloadSession; [session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { ... }]; } }
… nearly forgot, provide the public method the check for the current session:
+ (BOOL)isCellularDownloadSession { FlickrHelper *fh = [FlickrHelper sharedFlickrHelper]; return fh.currentDownloadSession.configuration.allowsCellularAccess; }
The notification to let the table-view controller know about the finished download has to called in two places. Once, when there are no new regions, and a second time when there are regions and the document is saved:
+ (void)loadRegionNamesFromFlickrIntoManagedObjectContext:(NSManagedObjectContext *)context { ... if (!matches || ![matches count]) { [[NSNotificationCenter defaultCenter] postNotificationName:FINISHEDCELLULARFLICKRFETCHNOTIFICATION object:self]; } else { ... if (saveDocument) { ... [[NSNotificationCenter defaultCenter] postNotificationName:FINISHEDCELLULARFLICKRFETCHNOTIFICATION object:self]; } ... } }
The complete code for the extra task #1 is available on github.
hmm… it seems you fixed the fetch UI freezing problem in this task…
Sorry, problem isn’t fixed in this task. I re-tested it….