I am working on a feed reader and i am doing it by parsing rss feeds using nsxmlparser. I also have thumbnail objects that i am taking from the CDATA block.
-(void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *someString = [[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding];
NSString *storyImageURL = [self getFirstImageUrl:someString];
NSURL *tempURL = [NSURL URLWithString:storyImageURL];
NSData *tempData = [NSData dataWithContentsOfURL:tempURL];
thumbnail = [UIImage imageWithData:tempData];
});
}
I am persisting the images and other tableview objects using archiving and encode and decode methods.
The issue is when i use the above code as it is, it does not persist images while other tableview objects like title and published date do persist. But when i use the above code without dispatch_async around it, then it starts persisting the images but locks the user interface.
How can i persist the images without locking the user interface?
Kindly do not answer with some library to store and cache images or Apple's lazy loading example. Thanks
UPDATE:
Based on the answer provided and after watching a lecture by Stanford in iTunes on Multithreading, i have implemented the above code as follow:
NSString *someString = [[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding];
NSString *storyImageURL = [self getFirstImageUrl:someString];
NSURL *tempURL = [NSURL URLWithString:storyImageURL];
dispatch_queue_t downloadQueue = dispatch_queue_create("image downloader", NULL);
dispatch_async(downloadQueue, ^{
NSData *tempData = [NSData dataWithContentsOfURL:tempURL];
dispatch_async(dispatch_get_main_queue(), ^{
thumbnail = [UIImage imageWithData:tempData];
});
});
}
Logically, everything is correct now but still it didn't work. I am all blocked how to solve this issue.
UPDATE:
I am using the Model View Controller Store approach and my store and connection code are defined in separate files. Below is the code where i am using NSKeyedArchiver.
- (FeedChannel *) FeedFetcherWithCompletion:(void (^)(FeedChannel *, NSError *))block {
NSURL *url = [NSURL URLWithString:@"http://techcrunch.com/feed/"];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
FeedChannel *channel = [[FeedChannel alloc] init];
TheConnection *connection = [[TheConnection alloc] initWithRequest:req];
NSString *pathOfCache = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
pathOfCache = [pathOfCache stringByAppendingPathComponent:@"check.archive"];
FeedChannel *channelCache = [NSKeyedUnarchiver unarchiveObjectWithFile:pathOfCache];
if (!channelCachel) {
channelCache = [[FeedChannel alloc] init];
}
FeedChannel *channelCopy = [channelCache copy];
[connection setCompletionBlock:^(FeedChannel *obj, NSError *err) {
if (!err) {
[channelCopy addItemsFromChannel:obj];
[NSKeyedArchiver archiveRootObject:channelCopy toFile:pathOfCache];
}
block(channelCopy, err);
}];
[connection setXmlObject:channel];
[connection start];
return channelCache;
}
and below are my encode and decode methods.
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:title forKey:@"title"];
[aCoder encodeObject:link forKey:@"link"];
[aCoder encodeObject:creator forKey:@"creator"];
[aCoder encodeObject:pubDate forKey:@"pubDate"];
[aCoder encodeObject:thumbnail forKey:@"thumbnail"];
//[aCoder encodeObject:UIImagePNGRepresentation(thumbnail) forKey:@"thumbnail"];
[aCoder encodeObject:publicationDate forKey:@"publicationDate"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
[self setTitle:[aDecoder decodeObjectForKey:@"title"]];
[self setLink:[aDecoder decodeObjectForKey:@"link"]];
[self setCreator:[aDecoder decodeObjectForKey:@"creator"]];
[self setPubDate:[aDecoder decodeObjectForKey:@"pubDate"]];
[self setThumbnail:[aDecoder decodeObjectForKey:@"thumbnail"]];
//[self setThumbnail:[UIImage imageWithData:[aDecoder decodeObjectForKey:@"thumbnail"]]];
[self setPublicationDate:[aDecoder decodeObjectForKey:@"publicationDate"]];
}
return self;
}
Based on the answer, i don't know whether i have to use some extra code to persist images. because other objects like title, creator etc. are persisting accurately.
After reading your comment, I tend to think that the issue has to do with how you persist the images relative to the dispatch_async block. I will try to explain, but this is just a guess, since I don't know where you persist the image:
at some point your
parser:foundCDATA:
is executed and you retrieve asynchronously your image;the image will not be there for some time (until the sync block completes), so the
thumbnail
ivar/property will not have the right value for some time after completingparser:foundCDATA:
;now, if you persist
thumbnail
before the async block completes, you will persist the wrong (nil?) value;the fact that if you do not execute the fetching of the image asynchronously, then everything works, makes me think that you are persisting the image too early (in the above sense).
The fix to this would be persisting the image only after the async block completes. This could mean something like:
<code here to make the thumbnail be persisted>
could be a method call, a notification post, whatever fits your current design. You might also need to use the trick of dispatching the call to the main thread if your persistence code is not thread safe:Hope it helps.
EDIT:
After our chat, the above analysis is confirmed. In fact, you are not simply downloading one image: you are downloading a bunch of images, all belonging to the same "channel". This means that the persist operation, which is applied to the whole channel, shall be executed only after all of those images have been downloaded.
I have thus forked your gist and added some logics enabling the use of GCD dispatch_group to keep track of all the download operations. You can find it here.