问题
I created a NSOperationQueue
to download images (from Twitter for Cell):
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
NSString *ImagesUrl = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
NSURL *imageurl = [NSURL URLWithString:ImagesUrl];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
[[NSOperationQueue mainQueue]addOperationWithBlock:^{
if (img.size.width == 0 || [ImagesUrl isEqualToString:@"<null>"]) {
[statusCell.imageCellTL setFrame:CGRectZero];
statusCell.imageCellTL.image = [UIImage imageNamed:@"Placeholder"] ;
}else
[statusCell.imageCellTL setImage:img];
this working fine, but when it appears to move the scroll and view the images are still loading, and they are changing several times until you get a picture.
And I do not like the diagnosis Profile of Time, so I wanted to somehow make this NSOperationQueue
in Background
is also possible to show how to make a "Imagecache" no need to download an image already downloaded.
**(Status = NSDictionary of Twitter Timeline).
editing::(All Cell)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Celulatime";
UITableViewCell *Cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if ( [Cell isKindOfClass:[TimeLineCell class]] ) {
TimeLineCell *statusCell = (TimeLineCell *) Cell;
status = [self.dataSource objectAtIndex:indexPath.row];
statusCell.TextCellTL.text = [status objectForKey:@"text"];
statusCell.NomeCellTL.text = [status valueForKeyPath:@"user.name"];
statusCell.UserCellTL.text = [NSString stringWithFormat:@"@%@", [status valueForKeyPath:@"user.screen_name"]];
NSDate *created_at = [status valueForKey:@"created_at"];
if ( [created_at isKindOfClass:[NSDate class] ] ) {
NSTimeInterval timeInterval = [created_at timeIntervalSinceNow];
statusCell.timeCellTL.text = [self timeIntervalStringOf:timeInterval];
} else if ( [created_at isKindOfClass:[NSString class]] ) {
NSDate *date = [self.twitterDateFormatter dateFromString: (NSString *) created_at];
NSTimeInterval timeInterval = [date timeIntervalSinceNow];
statusCell.timeCellTL.text = [self timeIntervalStringOf:timeInterval];
}
NSString *imageUrlString = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];;
UIImage *imageFromCache = [self.imageCache objectForKey:imageUrlString];
if (imageFromCache) {
statusCell.imageCellTL.image = imageFromCache;
[statusCell.imageCellTL setFrame:CGRectMake(9, 6, 40, 40)];
}
else
{
statusCell.imageCellTL.image = [UIImage imageNamed:@"TweHitLogo57"];
[statusCell.imageCellTL setFrame:CGRectZero];
[self.imageluckluck addOperationWithBlock:^{
NSURL *imageurl = [NSURL URLWithString:imageUrlString];
UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]];
if (img != nil) {
[self.imageCache setObject:img forKey:imageUrlString];
// now update UI in main queue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
TimeLineCell *updateCell = (TimeLineCell *)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell) {
[updateCell.imageCellTL setFrame:CGRectMake(9, 6, 40, 40)];
[updateCell.imageCellTL setImage:img];
}
}];
}
}];
}
}
return Cell;
}
回答1:
A couple of observations:
You should probably define a
NSOperationQueue
in your class and initialize it inviewDidLoad
(as well as aNSCache
) and add operations to that queue, rather than creating a newNSOperationQueue
for every image. Also, many servers limit the number of concurrent requests they'll support from each client, so make sure to setmaxConcurrentOperationCount
accordingly.@interface ViewController () @property (nonatomic, strong) NSOperationQueue *imageOperationQueue; @property (nonatomic, strong) NSCache *imageCache; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.imageOperationQueue = [[NSOperationQueue alloc]init]; self.imageOperationQueue.maxConcurrentOperationCount = 4; self.imageCache = [[NSCache alloc] init]; } // the rest of your implementation @end
Your
tableView:cellForRowAtIndexPath:
should make (a) initialize theimage
before starting the asynchronous image load (so you don't see the old image from the reused cell there); and (b) make sure the cell is still visible before you update it:NSString *imageUrlString = [[NSString alloc]initWithFormat:@"http://api.twitter.com/1/users/profile_image/%@",[[status objectForKey:@"user"]objectForKey:@"screen_name"]];; UIImage *imageFromCache = [self.imageCache objectForKey:imageUrlString]; if (imageFromCache) { statusCell.imageCellTL.image = imageFromCache; [statusCell.imageCellTL setFrame: ...]; // set your frame accordingly } else { statusCell.imageCellTL.image = [UIImage imageNamed:@"Placeholder"]; [statusCell.imageCellTL setFrame:CGRectZero]; // not sure if you need this line, but you had it in your original code snippet, so I include it here [self.imageOperationQueue addOperationWithBlock:^{ NSURL *imageurl = [NSURL URLWithString:imageUrlString]; UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageurl]]; if (img != nil) { // update cache [self.imageCache setObject:img forKey:imageUrlString]; // now update UI in main queue [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // see if the cell is still visible ... it's possible the user has scrolled the cell so it's no longer visible, but the cell has been reused for another indexPath TimeLineCell *updateCell = (TimeLineCell *)[tableView cellForRowAtIndexPath:indexPath]; // if so, update the image if (updateCell) { [updateCell.imageCellTL setFrame:...]; // I don't know what you want to set this to, but make sure to set it appropriately for your cell; usually I don't mess with the frame. [updateCell.imageCellTL setImage:img]; } }]; } }]; }
No special handling of
UIApplicationDidReceiveMemoryWarningNotification
is needed because althoughNSCache
does not respond to this memory warning, it does automatically evict its objects as memory becomes low.
I haven't tested the above code, but hopefully you get the idea. This is the typical pattern. Your original code had a check for [ImagesUrl isEqualToString:@"<null>"]
, which I don't see how could ever be the case, but if you need some additional logic besides just my if (img != nil) ...
, then adjust that line accordingly.
回答2:
A good example from Ray Wenderlich : http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
It also having cancel function to cancel operation in case user press cancel button.
回答3:
Download async images in tableview using swift 3
class ViewController: UIViewController {
var queue = OperationQueue()
let imageURLs = ["https://amazingslider.com/wp-content/uploads/2012/12/dandelion.jpg", "https://media.treehugger.com/assets/images/2011/10/tiger-running-snow.jpg.600x315_q90_crop-smart.jpg", "https://www.w3schools.com/css/trolltunga.jpg", "https://www.w3schools.com/w3css/img_lights.jpg", "https://cdn.pixabay.com/photo/2015/04/28/20/55/aurora-borealis-744351_960_720.jpg", "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS2L0pETnybA5sld783iz1mgtOFS8vxBTjB4tYXeRtQWDxig3dc"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return imageURLs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: ImagesTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ImagesTableViewCell
var img: UIImage!
let operation = BlockOperation(block: {
img = Downloader.downloadImageWithURl(self.imageURLs[indexPath.row])
})
operation.completionBlock = {
DispatchQueue.main.async {
cell.imgView?.image = img
}
}
queue.addOperation(operation)
return cell
}
}
class Downloader {
class func downloadImageWithURl(_ url: String) -> UIImage! {
if let data = try? Data(contentsOf: URL(string: url)!) {
return UIImage(data: data)!
}
return nil
}
}
来源:https://stackoverflow.com/questions/14444041/nsoperationqueue-background-download-images