UIImage dowload by URL

Discussion in 'iOS Programming' started by MACloop, Mar 29, 2010.

  1. MACloop macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #1
    Hello,
    I have an image which I get from a server ie. via an url. It works fine so far. My problem is - the user navigates through some tableViews and ends up on a detailed view. This view shows an image for this current view. Everytime when this view is pushed, the img is downloaded and that it not very good practice I suppose. The img is added as subview to the current viewcontroller and then released. I think the memory aspect is ok, but it seems a bit ineffective somehow, doing the same thing over and over. How could I do this alternatively? I have thought about dowloading all img on startup and save it into a dictionary or something like that, but the app contains alot of different views and also different detail images. This means such an action would take some time on startup and would also mean that some kind of sorting/getting the correct img later on would have to be done. However, is it also possible that the user only wants to display some of the detailed views per app-run and in such a case is the dowload on startup unneccesary?... Any suggestions on a clever solution on this?
    Thanks in advance!
    MACloop
     
  2. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #2
    Implement an image cache with a retention policy. This is non-trivial but not too hard. Use a folder within your applications sandbox area. Store a plist in there that keeps, in order, the number of items you decide to keep in cache, which URL they are from and what the filename is.

    Every time you want an image check the cache first. If you have the image in cache load that and move that item to the first position in the cache list. If not download the image, store it in the cache folder and put that item first in the cache list. If this results in more than your max cache items being stored delete the last item in the list from disk and the plist.

    Assuming you have a well-formed data-access object then this will all be 100% transparent to the rest of your code: they simply ask for the images as before.
     
  3. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #3
    Cool! Thanks for you excellent answer!!! That is exactly what I was looking for but I did not know how to define it. I think I have to read some in order to be able to implement it and I will most likley be back with more questions ;-)

    Thanks alot!
    MACloop
     
  4. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #4
    Other things to consider:

    1) Do you want the cache to persist across application runs? Pretty easy to do but what about stale data

    2) To deal with stale data you probably want a cache timeout. After the timeout the item in the cache should be considered stale and redownloaded on next request

    3) The above may be inefficient if the item has not changed. You can use the http header If-Modified-Since in a request to find out if the item has changed. You get a 304 response if it has not and a 200 (with the timestamp of change) if it has
     
  5. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #5
    Thanks again! Interesting points! About the img being stale...well I parse all the data for the objects and every object has atleast one images. This imagepath is saved to the object as a NSString variable and I intended to use this path when dowloading the img... is that the wrong way of doing it? This would mean that a path to an img cannot be stale, because the current link to the image is always fetched in app-start...?
    MACloop
     
  6. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #6
    You misunderstand the concept of cache staleness. You have an image in memory/on disk. You are assuming that it is the same as the one on the server. It is possible that the image on the server has changed since you downloaded it. And you are, therefore, displaying the wrong image.
     
  7. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #7
    Ups, that's right! I missunderstood that :eek: That makes sence though! Thanks alot!
    MACloop
     
  8. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #8
    So, I decided to start very basic on this. I have used my DataAccess class in order to send in an url for a specific image. It seems to work and the img is loaded into the view. This is how far I have come for now:

    the viewController .h file is set to be DataAccessDelegate

    in the viewDidLoad method:
    Code:
    //I intend to write an alternative init method here to be able to send in a single url aswell
    NSMutableArray *urls = [NSMutableArray arrayWithObjects:[self.theObject imgPath],nil];
    [[DataAccess sharedDataAccess] initWithUrls:urls andDelegate:self];
    The delegate method didFinishDownload
    Code:
    - (void) didFinishDownload:(NSNumber*)idx{
    UIImage *i = [UIImage imageWithData:[[DataAccess sharedDataAccess]dataAtIndex: [idx intValue]]];
    UIImageView *iv = [[UIImageView alloc] initWithImage:i];
    iv.frame = CGRectMake(0, 0, 320, 154);
    [self.view addSubview:iv];
    [iv release];
    }
    My questions at this point are:
    Is this a good start? Is the structure ok?
    How do I implement the cache into this solution? I assume I have to define a plist as you wrote and also a folder. Should the cache be done in the DataAccess class (I found it interesting what you mentioned about the transparency)? I did not figure that out yet...

    Thanks in advance!
    MACloop
     
  9. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #9
    The caching should be use by DataAccess. Whether you write a cache class and DataAccess uses it or you simply include the whole lot in DataAccess is up to you.
     
  10. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #10
    Thanks alot! I think I try with a cache class.
    MACloop
     
  11. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #11
    Adding a folder to the sandbox... do you mean that I should add a folder to the app-tree-structure?
    MACloop
     
  12. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #12
    No. I 100% don't mean that. For one thing you can't write into the app bundle without breaking the code signing. I mean exactly what I wrote. I suggest you go and read the documentation, in particular the iPhone OS Programming Guide to find out where you can and cannot write to. I am not going to spoon-feed you answers in tiny bite-sized chunks.
     
  13. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #13
    ok, I will do some more reading!
    Thanks!
    MACloop
     
  14. Luke Redpath macrumors 6502a

    Joined:
    Nov 9, 2007
    Location:
    Colchester, UK
    #14
  15. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #15
    Thanks a lot! I will rake a look at that!
    MACloop
     
  16. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #16
    Ok, I think I did understand the thing about the sandbox, at least almost... I have written two method, one checking for a saved version of the img and if there is no saved version it calls the other method, which fetches the image via an url. I call the getTheCachedImage method via my singleton DataAccess class, from my tableview where the img is supposed to appear. My question now is how can I/should I override the didFinishLoading method in order to get a message to the delegate that the download/getting the img from cache is done? Or is it better to use a notification here, ie when the cache methods are done - send a notification to the delegate and display the img?

    Thanks in advance!
    MACloop
     
  17. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #17
    I don't really understand the design decisions you have/are taking. The way this should work is that your table data source sends a request to DataAccess for an image. DataAccess can then either return an image from the cache or download it. Either way it returns the image in the exact same way: outside of DataAccess no one needs to make any distinction between cached and non-cached data.
     
  18. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #18
    That is right - but I have to let the delegate know that there is an image...before the delegate has somthing to show, I hvae chosen to display an UIActivityIndicatorView in order to let the rest of the view load...

    I tried this out and it seems to work.
    Code:
    [self.delegate performSelector:@selector(didFinishDownloadImg:) withObject:image];
    My problem now is; it seems like the value (name, url etc) for each img is not added to the cache. I am trying to locate that problem now. How and where (in the data access class) should I create the cache directory? I tryied
    Code:
    #define CACHE NSCachesDirectory()
    but it did not seem to work. I suppose I did not create/init it properly...
    MACloop
     
  19. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #19
    Without reading and understanding the entirety of your code (which I am not about to do) these questions are unanswerable. You seem to be at the stage of debugging: do that.

    I would note that #define just defines a macro: it doesn't actually execute or do anything unless you use the macro in your code (and your code is correct and the macro makes sense in your usage).
     
  20. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #20
    Ok, thanks for your comment. I will go on analyzing here. The app "works" ie. the img is dowloaded and showed in the tableview. Now I have to get into this cache problem, because it does not seem to be saved into the directory.
    MACloop
     
  21. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #21
    hmm...I think it could be a big help if you could just write some pseudocode...only so that I can see if I have the structure/flow right...is that possible?
    Thanks very much in advance!
    MACloop
     
  22. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #22
    So I have got it working now and have some detail questions about the directory size. I assume that when I am about to add a new img, I have to look up the size of the directory. If it has reached the max-size I intend to delete the oldest img in the directory.

    What max-size for such a directory would you use?
    How do I use the directory cleverly, I want it to work like a FIFO structure.
    I suppose I could look up the NSFileCreationDate for the objects saved, but perhaps there is some good method to use here, in order to get the oldest object...?

    Any advice?
    Thanks in advance!
    MACloop
     
  23. robbieduncan Moderator emeritus

    robbieduncan

    Joined:
    Jul 24, 2002
    Location:
    London
    #23
    I would suggest a 1-200Mb at most
    Which is where all the array manipulation in my suggestion came from: that's your FIFO queue, not the directory.
     
  24. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #24
    Ok, thanks for your help! I will look at the older posts here and see if I can implement that.
    MACloop
     
  25. MACloop thread starter macrumors 6502

    Joined:
    May 18, 2009
    Location:
    Germany
    #25
    So, I have managed to save the default data for each element I save into a dictionary. My problem now is how to create the plist and write to it. I assuem I shall create it in the same directory as the images self and that path do I have. I have been seaching for the syntax for creating a plist in the code, but I did not find anything helpful... do you have any suggestions?
    Thanks in advance!
    MACloop
     

Share This Page