NSURLSession load images on demand











up vote
3
down vote

favorite












I'm trying to come up with a system to populate my item images on demand, this is what I have so far. One obvious bug is that an item image could be downloaded multiple time since the _image will remain nil until the first download has completed. I could work around this with a loading property.



My main issue is about priority, ideally I would like the images currently visible in the tableview to be downloaded first. ie As soon as the tableview appears it starts downloading the first 20 or so initially visible item.images, but if the users scrolls down to the bottom then items 20-100 all get queued in order which results in the now visible images 80-100 being last to download. Not really sure how to deal with this.



@interface Item : NSObject
@property (nonatomic, strong) UIImage *image;
@end;

@implementation Item

- (UIImage *)image
{
if (!_image) {

NSURL *url = [NSURL URLWithString:@"http://loremflickr.com/50/50/paris"];
NSURLSessionDataTask *imageTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
_image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"ItemUpdated" object:self];
});

}];
[imageTask resume];
}
return _image;
}

@end


@interface TableViewController : UITableViewController
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController

- (void)viewDidLoad
{
[super viewDidLoad];

NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:100];
for (int i=0; i<100; i++) {
Item *item = [[Item alloc] init];
[items addObject:item];
}
self.items = items;
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemUpdated:) name:@"ItemUpdated" object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"ItemUpdated" object:nil];
}

- (void)itemUpdated:(NSNotification *)notification
{
Item *item = (Item *)notification.object;

NSUInteger index = [self.items indexOfObject:item];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Item *item = [self.items objectAtIndex:indexPath.row];

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.imageView.image = item.image;
cell.textLabel.text = [NSString stringWithFormat:@"Item %d", indexPath.row];

return cell;
}

@end









share|improve this question
























  • Have you taken a look at the approaches existing tools like github.com/rs/SDWebImage have used?
    – Jonah
    Jun 22 '16 at 21:10















up vote
3
down vote

favorite












I'm trying to come up with a system to populate my item images on demand, this is what I have so far. One obvious bug is that an item image could be downloaded multiple time since the _image will remain nil until the first download has completed. I could work around this with a loading property.



My main issue is about priority, ideally I would like the images currently visible in the tableview to be downloaded first. ie As soon as the tableview appears it starts downloading the first 20 or so initially visible item.images, but if the users scrolls down to the bottom then items 20-100 all get queued in order which results in the now visible images 80-100 being last to download. Not really sure how to deal with this.



@interface Item : NSObject
@property (nonatomic, strong) UIImage *image;
@end;

@implementation Item

- (UIImage *)image
{
if (!_image) {

NSURL *url = [NSURL URLWithString:@"http://loremflickr.com/50/50/paris"];
NSURLSessionDataTask *imageTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
_image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"ItemUpdated" object:self];
});

}];
[imageTask resume];
}
return _image;
}

@end


@interface TableViewController : UITableViewController
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController

- (void)viewDidLoad
{
[super viewDidLoad];

NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:100];
for (int i=0; i<100; i++) {
Item *item = [[Item alloc] init];
[items addObject:item];
}
self.items = items;
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemUpdated:) name:@"ItemUpdated" object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"ItemUpdated" object:nil];
}

- (void)itemUpdated:(NSNotification *)notification
{
Item *item = (Item *)notification.object;

NSUInteger index = [self.items indexOfObject:item];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Item *item = [self.items objectAtIndex:indexPath.row];

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.imageView.image = item.image;
cell.textLabel.text = [NSString stringWithFormat:@"Item %d", indexPath.row];

return cell;
}

@end









share|improve this question
























  • Have you taken a look at the approaches existing tools like github.com/rs/SDWebImage have used?
    – Jonah
    Jun 22 '16 at 21:10













up vote
3
down vote

favorite









up vote
3
down vote

favorite











I'm trying to come up with a system to populate my item images on demand, this is what I have so far. One obvious bug is that an item image could be downloaded multiple time since the _image will remain nil until the first download has completed. I could work around this with a loading property.



My main issue is about priority, ideally I would like the images currently visible in the tableview to be downloaded first. ie As soon as the tableview appears it starts downloading the first 20 or so initially visible item.images, but if the users scrolls down to the bottom then items 20-100 all get queued in order which results in the now visible images 80-100 being last to download. Not really sure how to deal with this.



@interface Item : NSObject
@property (nonatomic, strong) UIImage *image;
@end;

@implementation Item

- (UIImage *)image
{
if (!_image) {

NSURL *url = [NSURL URLWithString:@"http://loremflickr.com/50/50/paris"];
NSURLSessionDataTask *imageTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
_image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"ItemUpdated" object:self];
});

}];
[imageTask resume];
}
return _image;
}

@end


@interface TableViewController : UITableViewController
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController

- (void)viewDidLoad
{
[super viewDidLoad];

NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:100];
for (int i=0; i<100; i++) {
Item *item = [[Item alloc] init];
[items addObject:item];
}
self.items = items;
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemUpdated:) name:@"ItemUpdated" object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"ItemUpdated" object:nil];
}

- (void)itemUpdated:(NSNotification *)notification
{
Item *item = (Item *)notification.object;

NSUInteger index = [self.items indexOfObject:item];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Item *item = [self.items objectAtIndex:indexPath.row];

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.imageView.image = item.image;
cell.textLabel.text = [NSString stringWithFormat:@"Item %d", indexPath.row];

return cell;
}

@end









share|improve this question















I'm trying to come up with a system to populate my item images on demand, this is what I have so far. One obvious bug is that an item image could be downloaded multiple time since the _image will remain nil until the first download has completed. I could work around this with a loading property.



My main issue is about priority, ideally I would like the images currently visible in the tableview to be downloaded first. ie As soon as the tableview appears it starts downloading the first 20 or so initially visible item.images, but if the users scrolls down to the bottom then items 20-100 all get queued in order which results in the now visible images 80-100 being last to download. Not really sure how to deal with this.



@interface Item : NSObject
@property (nonatomic, strong) UIImage *image;
@end;

@implementation Item

- (UIImage *)image
{
if (!_image) {

NSURL *url = [NSURL URLWithString:@"http://loremflickr.com/50/50/paris"];
NSURLSessionDataTask *imageTask = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
_image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"ItemUpdated" object:self];
});

}];
[imageTask resume];
}
return _image;
}

@end


@interface TableViewController : UITableViewController
@property (nonatomic, strong) NSArray *items;
@end

@implementation TableViewController

- (void)viewDidLoad
{
[super viewDidLoad];

NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:100];
for (int i=0; i<100; i++) {
Item *item = [[Item alloc] init];
[items addObject:item];
}
self.items = items;
}

- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(itemUpdated:) name:@"ItemUpdated" object:nil];
}

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"ItemUpdated" object:nil];
}

- (void)itemUpdated:(NSNotification *)notification
{
Item *item = (Item *)notification.object;

NSUInteger index = [self.items indexOfObject:item];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Item *item = [self.items objectAtIndex:indexPath.row];

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
cell.imageView.image = item.image;
cell.textLabel.text = [NSString stringWithFormat:@"Item %d", indexPath.row];

return cell;
}

@end






image objective-c asynchronous ios http






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Sep 29 '17 at 18:44









200_success

127k15148412




127k15148412










asked Jun 20 '16 at 1:55









trapper

1665




1665












  • Have you taken a look at the approaches existing tools like github.com/rs/SDWebImage have used?
    – Jonah
    Jun 22 '16 at 21:10


















  • Have you taken a look at the approaches existing tools like github.com/rs/SDWebImage have used?
    – Jonah
    Jun 22 '16 at 21:10
















Have you taken a look at the approaches existing tools like github.com/rs/SDWebImage have used?
– Jonah
Jun 22 '16 at 21:10




Have you taken a look at the approaches existing tools like github.com/rs/SDWebImage have used?
– Jonah
Jun 22 '16 at 21:10










1 Answer
1






active

oldest

votes

















up vote
0
down vote













I have a possible solution for your case, this is what I am thinking.
1) You can implement scrollViewDidEndDragging delegate and load your image for the visible cells.
for ex.



  - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnScreenRows];
}


loadIamgesForOnScreenRows will look like:



- (void)loadImagesForOnScreenRows
{

NSArray *visiblePaths = [self.tracksListing indexPathsForVisibleRows];

for (NSIndexPath *indexPath in visiblePaths){
[[self.tableView cellForRowAtIndexPath:indexPath] bindItemObject:record];
}
}
}


In my case bindItemObject is for downloading the image implemented in tableViewCell class, and record contains the url for the image.



The only problem which you will encounter in this case is, when the tableView appears for the first time.
In that case I suggest you to programmatically call the



[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];


and give indexpath as indexpath for row:0 and section:0



I think this can solve your problem.






share|improve this answer





















  • You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
    – trapper
    May 8 at 2:48











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f132476%2fnsurlsession-load-images-on-demand%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
0
down vote













I have a possible solution for your case, this is what I am thinking.
1) You can implement scrollViewDidEndDragging delegate and load your image for the visible cells.
for ex.



  - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnScreenRows];
}


loadIamgesForOnScreenRows will look like:



- (void)loadImagesForOnScreenRows
{

NSArray *visiblePaths = [self.tracksListing indexPathsForVisibleRows];

for (NSIndexPath *indexPath in visiblePaths){
[[self.tableView cellForRowAtIndexPath:indexPath] bindItemObject:record];
}
}
}


In my case bindItemObject is for downloading the image implemented in tableViewCell class, and record contains the url for the image.



The only problem which you will encounter in this case is, when the tableView appears for the first time.
In that case I suggest you to programmatically call the



[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];


and give indexpath as indexpath for row:0 and section:0



I think this can solve your problem.






share|improve this answer





















  • You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
    – trapper
    May 8 at 2:48















up vote
0
down vote













I have a possible solution for your case, this is what I am thinking.
1) You can implement scrollViewDidEndDragging delegate and load your image for the visible cells.
for ex.



  - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnScreenRows];
}


loadIamgesForOnScreenRows will look like:



- (void)loadImagesForOnScreenRows
{

NSArray *visiblePaths = [self.tracksListing indexPathsForVisibleRows];

for (NSIndexPath *indexPath in visiblePaths){
[[self.tableView cellForRowAtIndexPath:indexPath] bindItemObject:record];
}
}
}


In my case bindItemObject is for downloading the image implemented in tableViewCell class, and record contains the url for the image.



The only problem which you will encounter in this case is, when the tableView appears for the first time.
In that case I suggest you to programmatically call the



[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];


and give indexpath as indexpath for row:0 and section:0



I think this can solve your problem.






share|improve this answer





















  • You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
    – trapper
    May 8 at 2:48













up vote
0
down vote










up vote
0
down vote









I have a possible solution for your case, this is what I am thinking.
1) You can implement scrollViewDidEndDragging delegate and load your image for the visible cells.
for ex.



  - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnScreenRows];
}


loadIamgesForOnScreenRows will look like:



- (void)loadImagesForOnScreenRows
{

NSArray *visiblePaths = [self.tracksListing indexPathsForVisibleRows];

for (NSIndexPath *indexPath in visiblePaths){
[[self.tableView cellForRowAtIndexPath:indexPath] bindItemObject:record];
}
}
}


In my case bindItemObject is for downloading the image implemented in tableViewCell class, and record contains the url for the image.



The only problem which you will encounter in this case is, when the tableView appears for the first time.
In that case I suggest you to programmatically call the



[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];


and give indexpath as indexpath for row:0 and section:0



I think this can solve your problem.






share|improve this answer












I have a possible solution for your case, this is what I am thinking.
1) You can implement scrollViewDidEndDragging delegate and load your image for the visible cells.
for ex.



  - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
[self loadImagesForOnScreenRows];
}


loadIamgesForOnScreenRows will look like:



- (void)loadImagesForOnScreenRows
{

NSArray *visiblePaths = [self.tracksListing indexPathsForVisibleRows];

for (NSIndexPath *indexPath in visiblePaths){
[[self.tableView cellForRowAtIndexPath:indexPath] bindItemObject:record];
}
}
}


In my case bindItemObject is for downloading the image implemented in tableViewCell class, and record contains the url for the image.



The only problem which you will encounter in this case is, when the tableView appears for the first time.
In that case I suggest you to programmatically call the



[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];


and give indexpath as indexpath for row:0 and section:0



I think this can solve your problem.







share|improve this answer












share|improve this answer



share|improve this answer










answered Aug 5 '16 at 10:50









Sanchit Kumar Singh

1012




1012












  • You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
    – trapper
    May 8 at 2:48


















  • You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
    – trapper
    May 8 at 2:48
















You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
– trapper
May 8 at 2:48




You don't need the scrollToRowAtIndexPath bit Just call [self loadImagesForOnScreenRows]; manually
– trapper
May 8 at 2:48


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f132476%2fnsurlsession-load-images-on-demand%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Сан-Квентин

8-я гвардейская общевойсковая армия

Алькесар