how to make a simple multithreading? - objective-c

Okay, now I was making a mobile application like restaurant finder, I want to show a photo of the restaurant
the example : restaurant x <image x>
It's a code:
if (ImageToDisplay != nil)
{
NSData * imageData = [[[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: ImageToDisplay.URL]]autorelease];
ImageForRestaurant.image = [UIImage imageWithData: imageData];
}
The problem is this process of downloading pictures may take too long. So I want the process to be run on a different thread.
That way the code after that can run without having to wait this one to finish.
How can I do so?

if (ImageToDisplay != nil) {
[self performSelectorInBackground:#selector(loadImage:) object:ImageToDisplay];
}
- (void)loadImage:(ImageToDisplay *)image { //Background method
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData * imageData = [[[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: ImageToDisplay.URL]]autorelease];
[self performSelectorOnMainThread:#selector(setImageForRestaurant:) withObject:imageData waitUntilDone:NO];
[pool release];
}
- (void)setImageForRestaurant:(NSData *)imageData { //Change UI in main thread
ImageForRestaurant.image = [UIImage imageWithData: imageData];
}
I've just included basics in multi-threading; I guess it will serve your purpose

Did you try
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
I think it's the easiest way.

Use the ASIHTTPRequest library. See 'Creating an asynchronous request' on this page: http://allseeing-i.com/ASIHTTPRequest/How-to-use
Also the new library AFNetworking looks promising. As they say:
If you're tired of massive libraries that try to do too much, if you've taken it upon yourself to roll your own hacky solution, if you want a library that actually makes iOS networking code kinda fun, try out AFNetworking.

Related

How to implement lazy loading of image without using SDImageCache?

How to implement lazy loading of image without using below link:
https://github.com/rs/SDWebImage
Note: I don't want to use any third party tool. I want to do from my side.
If you don't want to use any library (some are MIT or public domain licensed, so I think the only reason not to use them is that you want to learn how to build it yourself).
Here is how to do it with a simple and effective way:
1 : Put a temporary placeholder image in your imageView.
2 : Get your image in background thread.
2-a : If you want the cache feature, search for a cached image.
2-b : If no cache feature or no cached image, get your image from its source.
2-c : If cache feature, save the image to cache.
3 : In main thread show your image in the imageView.
Pseudo Code : (I wrote it on the go, it is not meant to run and it may have errors, sorry for that).
-(void) lazilyLoadImageFromURL :(NSURL *)url{
imageView.image = [UIImage imageNamed:#"Placeholder.png];
if([self cachedImageAvailableForURL:url){
imageView.image= [self cachedImageForURL:url];
}
else{
NSOperationQueue *queue = [NSOperationQueue mainQueue];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
imageView.image = urlImage;
[self saveImageInCache:image forURL:url];
}
});
}];
}
}
-(BOOL) cachedImageAvailableForURL:(NSURL*):url{
// check if there is a saved cached image for this url
}
-(UIImage *) cachedImageForURL:(NSURL*):url{
// returns the cached image for that url
}
-(void) saveImageInCache:(UIImage*) image forURL:(NSURL*)url{
// saves the image in cache for the url
}
Of course, this is only ONE POSSIBLE WAY to do it. I tried to make it simple, but there are plenty better and more complicated ways to do it.
Try it:
NSURL *imageURL = [NSURL URLWithString:#"www...."];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
dispatch_async(dispatch_get_main_queue(), ^{
// Update the UI
self.imgVWprofile.image=[UIImage imageWithData:imageData];
});
});

UIImageView category to download images with GCD fails with a large number simultaneous downloads

I've used AFNetworking to download and cache images in my project, but I wanted to replace the framework with a lightest in-house category, obviously I'm facing a lot of issues with dispatch queues, and I'm having a hard time debugging them.
This is the category I'm using right now, It works with no problem with a small number of simultaneous downloads but, if I start a lot of downloads it looks like the application hangs an an endless load.
I need some help debugging and I would like to understand what I'm missing here.
Here are the two main functions:
- (void) imageWithUrl: (NSURL*) imageUrl placeHolderImage: (UIImage *) placeHolderImage shouldAlwaysRefresh: (BOOL) shouldRefresh {
self.image= placeHolderImage;
TMCache *sharedCache = [TMCache sharedCache];
[sharedCache trimToDate: [NSDate dateWithTimeIntervalSinceNow: -(60.0*24.0*7.0)]];
UIImage *cachedImage = [sharedCache objectForKey: [imageUrl absoluteString]];
if(cachedImage){
self.image = cachedImage;
if(shouldRefresh)
[self fetchImageFromUrl: imageUrl];
}
else{
[self fetchImageFromUrl: imageUrl];
}
}
- (void) fetchImageFromUrl: (NSURL *) imageUrl{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = [UIImage imageWithData:imageData];
if(image){
[[TMCache sharedCache] setObject:image forKey: [imageUrl absoluteString]];
dispatch_sync(dispatch_get_main_queue(), ^{
self.image = image;
});
}
});
}
Your problem is most likely the dispatch_sync back to the main queue creating a deadlock when the image has finished loading.
The easiest way to solve that is to simply replace your dispatch_sync with dispatch_async.
if(image) {
[[TMCache sharedCache] setObject:image forKey: [imageUrl absoluteString]];
dispatch_async(dispatch_get_main_queue(), ^{
self.image = image;
});
}
I don't see any reason to use a dispatch_sync there since you're already in a dispatch_async block; this shouldn't have any negative effects on your code. If fact, you should usually try to avoid using dispatch_sync if at all possible, precisely for the issue you're running in to here.
Sidenote: AFNetworking actually gives you quite a bit for not all that much code. I'd seriously think twice before using your own solution over AFNetworking.

NSURL of UIImage from UIImagePickerController returns (null)

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editInfo {
userURL = [editInfo objectForKey:UIImagePickerControllerMediaURL];
userImage = image;
userImageView.image=userImage;
[self dismissViewControllerAnimated:YES completion:nil];}
I then take NSURL userURL, and put it in an UIActivityViewController to use to upload the image. However this never works, and always fails as it's trying to upload (null). However, when I use a preset image included in the xcode project and the following code, it always works and uploads correctly:
NSURL *url = [[NSBundle mainBundle] URLForResource:#"kitten.jpg" withExtension:nil];
If it helps, I'm using https://github.com/goosoftware/GSDropboxActivity
When I use UIImagePickerControllerReferenceURL instead of UIImagePickerControllerMediaURL, I get the following error:
[WARNING] DropboxSDK: File does not exist (/asset.JPG)
Failed to upload assets-library://asset/asset.JPG?id=EECAF4D0-A5ED-40E7-8E6F-3A586C0AB06E&ext=JPG
In theory, UIImagePickerControllerMediaURL is only populated for a movie, not an image. If you use the UIImagePickerControllerReferenceURL then the URL that you're given is not a URL for the file system, but rather the assets library. To get the file from that you'll need to use the asset library. Use something like the following :
typedef void (^ALAssetsLibraryAssetForURLResultBlock)(ALAsset *asset);
typedef void (^ALAssetsLibraryAccessFailureBlock)(NSError *error);
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset){
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref){
UIImage *myImage = [UIImage imageWithCGImage:iref scale:[rep scale] orientation:(UIImageOrientation)[rep orientation]];
// Do whatever you now want with your UIImage
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror){
//failed to get image.
};
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:[filePath objectAtIndex:0] resultBlock:resultblock failureBlock:failureblock];
This takes care of the blocks that will handle your request, but you still need to actually request the resource from the assets library. For that, try this :
ALAssetsLibrary* assetslibrary = [[[ALAssetsLibrary alloc] init] autorelease];
NSURL myAssetUrl = [NSURL URLWithString:[editInfo objectForKey:UIImagePickerControllerMediaURL]];
[assetslibrary assetForURL:myAssetUrl resultBlock:resultblock failureBlock:failureblock];
And in theory, you should get what you're after :)
The code for this was given by Ramshad and further discussion of it can be found here
Hopefully this will help you with what you're after. Sorry if it's a bit too late :(
Edit
Note that if you're not using ARC, you'll need to tidy up the memory in this example as I haven't included any memory management at all.

Reduce lag with UITableView and GCD

I have a UITableView consisting of roughly 10 subclassed UITableViewCells named TBPostSnapCell. Each cell, when initialised, sets two of its variables with UIImages downloaded via GCD or retrieved from a cache stored in the user's documents directory.
For some reason, this is causing a noticeable lag on the tableView and therefore disrupting the UX of the app & table.
Please can you tell me how I can reduce this lag?
tableView... cellForRowAtIndexPath:
if (post.postType == TBPostTypeSnap || post.snaps != nil) {
TBPostSnapCell *snapCell = (TBPostSnapCell *) [tableView dequeueReusableCellWithIdentifier:snapID];
if (snapCell == nil) {
snapCell = [[[NSBundle mainBundle] loadNibNamed:#"TBPostSnapCell" owner:self options:nil] objectAtIndex:0];
[snapCell setPost:[posts objectAtIndex:indexPath.row]];
[snapCell.bottomImageView setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%d", (indexPath.row % 6) +1]]];
}
[snapCell.commentsButton setTag:indexPath.row];
[snapCell.commentsButton addTarget:self action:#selector(comments:) forControlEvents:UIControlEventTouchDown];
[snapCell setSelectionStyle:UITableViewCellSelectionStyleNone];
return snapCell;
}
TBSnapCell.m
- (void) setPost:(TBPost *) _post {
if (post != _post) {
[post release];
post = [_post retain];
}
...
if (self.snap == nil) {
NSString *str = [[_post snaps] objectForKey:TBImageOriginalURL];
NSURL *url = [NSURL URLWithString:str];
[TBImageDownloader downloadImageAtURL:url completion:^(UIImage *image) {
[self setSnap:image];
}];
}
if (self.authorAvatar == nil) {
...
NSURL *url = [[[_post user] avatars] objectForKey:[[TBForrstr sharedForrstr] stringForPhotoSize:TBPhotoSizeSmall]];
[TBImageDownloader downloadImageAtURL:url completion:^(UIImage *image) {
[self setAuthorAvatar:image];
}];
...
}
}
TBImageDownloader.m
+ (void) downloadImageAtURL:(NSURL *)url completion:(TBImageDownloadCompletion)_block {
if ([self hasWrittenDataToFilePath:filePathForURL(url)]) {
[self imageForURL:filePathForURL(url) callback:^(UIImage * image) {
_block(image); //gets UIImage from NSDocumentsDirectory via GCD
}];
return;
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_async(dispatch_get_main_queue(), ^{
[self writeImageData:UIImagePNGRepresentation(image) toFilePath:filePathForURL(url)];
_block(image);
});
});
}
First thing to try is converting DISPATCH_QUEUE_PRIORITY_HIGH (aka ONG MOST IMPORTANT WORK EVER FORGET EVERYTHING ELSE) to something like DISPATCH_QUEUE_PRIORITY_LOW.
If that doesn't fix it you could attempt to do the http traffic via dispatch_sources, but that is a lot of work.
You might also just try to limit the number of in flight http fetches with a semaphore, the real trick will be deciding what the best limit is as the "good" number will depend on the network, your CPUs, and memory pressure. Maybe benchmark 2, 4, and 8 with a few configurations and see if there is enough pattern to generalize.
Ok, lets try just one, replace the queue = ... with:
static dispatch_once_t once;
static dispatch_queue_t queue = NULL;
dispatch_once(&once, ^{
queue = dispatch_queue_create("com.blah.url-fetch", NULL);
});
Leave the rest of the code as is. This is likely to be the least sputtery, but may not load the images very fast.
For the more general case, rip out the change I just gave you, and we will work on this:
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_async(dispatch_get_main_queue(), ^{
[self writeImageData:UIImagePNGRepresentation(image) toFilePath:filePathForURL(url)];
_block(image);
});
});
Replacing it with:
static dispatch_once_t once;
static const int max_in_flight = 2; // Also try 4, 8, and maybe some other numbers
static dispatch_semaphore_t limit = NULL;
dispatch_once(&once, ^{
limit = dispatch_semaphore_create(max_in_flight);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(limit, DISPATCH_TIME_FOREVER);
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// (or you might want the dispatch_semaphore_signal here, and not below)
dispatch_async(dispatch_get_main_queue(), ^{
[self writeImageData:UIImagePNGRepresentation(image) toFilePath:filePathForURL(url)];
_block(image);
dispatch_semaphore_signal(limit);
});
});
NOTE: I haven't tested any of this code, even to see if it compiles. As written it will only allow 2 threads to be executing the bulk of the code in your two nested blocks. You might want to move the dispatch_semaphore_signal up to the commented line. That will limit you to two fetches/image creates, but they will be allowed to overlap with writing the image data to a file and calling your _block callback.
BTW you do a lot of file I/O which is faster on flash then any disk ever was, but if you are still looking for performance wins that might be another place to attack. For example maybe keeping the UIImage around in memory until you get a low memory warning and only then writing them to disk.

iOS load an image based on user input

fairly new to Objective-C and iOS development (coming from PHP) and I have a relatively simple question that I can't seem to find an answer to:
I am following along with an example for split View design where a web page is loaded into the Detail View when a user clicks an item in the master view. I got all this working, but would like to substitute web view for an image. So I've amended the app to load a UIImage instead of a WebView. What I'm looking for is the equivalent to this code:
NSString *urlString = [pagesAddress objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:urlString];
// these 2 is where I get lost with the images.
NSURLRequest = *request = [NSURLRequest requestWithURL:url];
[detailViewController.webView loadRequest:request];
I came up with this:
NSString *imageName = [pagesAddress objectAtIndex:indexPath.row];
UIImage *myImage = [UIImage imageNamed:imageName];
// missing the last 2 calls: one to tell Xcode that it's an image "request" I want and the second to load the actual image (based on it's name that is already in an array) into the ImageView.
Thanks.
PS
I tried this:
NSString *imageName = [pagesAddress objectAtIndex:indexPath .row];
[detailViewController.imageView setImage:[UIImage imageNamed:imageName]];
And it shows just the first image, then crashes when I try to show the last one.
In the end, the solution were those 2 lines when I amended the code:
NSString *imageName = [pagesAddress objectAtIndex:indexPath.row];
[detailViewController.imageView setImage:[UIImage imageNamed:imageName]];
Notice that I had to change the setImage to convert the NSString to a UIImage or Xcode would complain. It turns out it was crashing because in the array where I had the image names, I had put 3 images into one entry (basically I forgot the commas!) so it was out of range.
Tim:
This line you gave me
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
is unnecessary because I already have a view created, it would create another view which I never used. Also, replacing it with CGRect seems overkill if I already have a UIImage placeholder no?
In any case, it works now and I'm very grateful for all the help. iPad development with Objectve-C is a very thorny road and I expect I'll be bugging you guys some more.
Cheers.
Try this:
UIImage *myImage = [[UIImage alloc] initWithData:[NSData dataWithConentsOfURL:[NSURL URLWithString:urlString];
// don't know if you already got the following?
UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
[imageView setImage:myImage];
[self.view addSubview:imageView];
The first line is synchronous (= blocking), so in production, you should rather use - [NSURLRequest start] for this (but that's a bit more complicated).
Or use this for your local images:
UIImage *myImage = [UIImage imageNamed:imageName];
// Now, follow the same steps as in the first code-example, just skip the first line.
Try this (on iOS 4.0 and later):
// Execute a block of code on a background thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^(void)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
UIImage* image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
// When IO is done and image created, set it on the main thread.
dispatch_async(dispatch_get_main_queue(),
^(void)
{
imageView.image = image;
});
[pool release];
});