【iOS入门】UITableView加载图片

学习带图片的列表

官方 LazyTableImages demo  http://download.csdn.net/detail/jlyidianyuan/5726749

分析源码是学习的好方法。

 源码结构如上,不能运行,加红框内容。

项目结构

挨个看源文件

/*
 Copyright (C) 2017 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 Application delegate for the LazyTableImages sample.
  It also downloads in the background the "Top Paid iPhone Apps" RSS feed using NSURLSession/NSURLSessionDataTask.
 */

#import "LazyTableAppDelegate.h"
#import "RootViewController.h"
#import "ParseOperation.h"
#import "AppRecord.h"


// the http URL used for fetching the top iOS paid apps on the App Store
static NSString *const TopPaidAppsFeed =
    @"http://phobos.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/toppaidapplications/limit=75/xml";


@interface LazyTableAppDelegate ()

// the queue to run our "ParseOperation" NSOperationQueue解析队q列,类似java线程池
@property (nonatomic, strong) NSOperationQueue *queue;

// the NSOperation driving the parsing of the RSS feed 解析类操作
@property (nonatomic, strong) ParseOperation *parser;

@end


#pragma mark -

@implementation LazyTableAppDelegate

// The app delegate must implement the window @property
// from UIApplicationDelegate @protocol to use a main storyboard file.
//
@synthesize window;

// -------------------------------------------------------------------------------
//    application:didFinishLaunchingWithOptions:
// -------------------------------------------------------------------------------
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //实例化联网请求
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:TopPaidAppsFeed]];
    
    // create an session data task to obtain and the XML feed
    // 使用9.0以后的联网操作类,之前可能使用NSURLConnection
    NSURLSessionDataTask *sessionTask =
        [[NSURLSession sharedSession] dataTaskWithRequest:request
                                        completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        // 获取联网状态代码  200 ,300,400,500等
        // in case we want to know the response status code
        //NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];
        
        if (error != nil)//如果有错误
        {
            [[NSOperationQueue mainQueue] addOperationWithBlock: ^{
                [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                
                if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
                {
                    // if you get error NSURLErrorAppTransportSecurityRequiresSecureConnection (-1022),
                    // then your Info.plist has not been properly configured to match the target server.
                    //错误码含意查询:https://www.meiwen.com.cn/subject/jjjdnttx.html
                    //在工程的 info.plist 文件中添加 https允许 
                    abort();
                }
                else
                {
                    //其它错误交给handleError处理
                    [self handleError:error];
                }
            }];
        }
        else
        {
            //没错误往下走
            // create the queue to run our ParseOperation 初始化解析队列
            self.queue = [[NSOperationQueue alloc] init];
            
            // create an ParseOperation (NSOperation subclass) to parse the RSS feed data so that the UI is not blocked 初始化解析操作类
            _parser = [[ParseOperation alloc] initWithData:data];
            //__weak 弱引用,这里使用弱引用,防止线程引用对象造成内存泄漏。要回收LazyTableAppDelegate?真如此,程序已经结束。没必要了。
            __weak LazyTableAppDelegate *weakSelf = self;
            //添加解析错误时回调的block ,block 类似java interface 或者理解为内部类。好比java OnClickLister.
            self.parser.errorHandler = ^(NSError *parseError) {
                //dispatch_async GCD 方式在主线程上操作的方法。相关学习:线程如何操作主线程的3种方法.
                dispatch_async(dispatch_get_main_queue(), ^{
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    //扔给本类的handleError方法处理。
                    [weakSelf handleError:parseError];
                });
            };
            
            // referencing parser from within its completionBlock would create a retain cycle
            __weak ParseOperation *weakParser = self.parser;
            
            //解析完成返回处理
            self.parser.completionBlock = ^(void) {
                // The completion block may execute on any thread.  Because operations
                // involving the UI are about to be performed, make sure they execute on the main thread.
                //结果需要在主线程更新
                dispatch_async(dispatch_get_main_queue(), ^{
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    if (weakParser.appRecordList != nil)
                    {
                        //如果返回的解析集合不为空
                        RootViewController *rootViewController =
                            (RootViewController *)[(UINavigationController *)weakSelf.window.rootViewController topViewController];
                        //RootViewController : UITableViewController 解析数据给到tableview
                        rootViewController.entries = weakParser.appRecordList;
                        
                        // tell our table view to reload its data, now that parsing has completed 更新tableview
                        [rootViewController.tableView reloadData];
                    }
                });
                
                // we are finished with the queue and our ParseOperation
                weakSelf.queue = nil;
            };
            
            [self.queue addOperation:self.parser]; // this will start the "ParseOperation"
        }
    }];
    
    [sessionTask resume];

    // show in the status bar that network activity is starting
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    
    return YES;
}

// -------------------------------------------------------------------------------
//    handleError:error
//  Reports any error with an alert which was received from connection or loading failures.
// -------------------------------------------------------------------------------
- (void)handleError:(NSError *)error
{
    NSString *errorMessage = [error localizedDescription];

    // alert user that our current record was deleted, and then we leave this view controller
    //
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Cannot Show Top Paid Apps", @"")
                                                                   message:errorMessage
                                                            preferredStyle:UIAlertControllerStyleActionSheet];
    UIAlertAction *OKAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"")
                                                       style:UIAlertActionStyleDefault
                                                     handler:^(UIAlertAction *action) {
                                                         // dissmissal of alert completed
                                                     }];
    
    [alert addAction:OKAction];
    [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}

@end
/*
 Copyright (C) 2017 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 Controller for the main table view of the LazyTable sample.
  This table view controller works off the AppDelege's data model.
  produce a three-stage lazy load:
  1. No data (i.e. an empty table)
  2. Text-only data from the model's RSS feed
  3. Images loaded over the network asynchronously
  
  This process allows for asynchronous loading of the table to keep the UI responsive.
  Stage 3 is managed by the AppRecord corresponding to each row/cell.
  
  Images are scaled to the desired height.
  If rapid scrolling is in progress, downloads do not begin until scrolling has ended.
 */

#import "RootViewController.h"
#import "AppRecord.h"
#import "IconDownloader.h"

#define kCustomRowCount 7

static NSString *CellIdentifier = @"LazyTableCell";
static NSString *PlaceholderCellIdentifier = @"PlaceholderCell";


#pragma mark -

@interface RootViewController () <UIScrollViewDelegate>

// the set of IconDownloader objects for each app
@property (nonatomic, strong) NSMutableDictionary *imageDownloadsInProgress;

@end


#pragma mark -

@implementation RootViewController

// -------------------------------------------------------------------------------
//    viewDidLoad
// -------------------------------------------------------------------------------
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _imageDownloadsInProgress = [NSMutableDictionary dictionary];
}

// -------------------------------------------------------------------------------
//    terminateAllDownloads
// -------------------------------------------------------------------------------
- (void)terminateAllDownloads
{
    //停止下载
    // terminate all pending download connections
    NSArray *allDownloads = [self.imageDownloadsInProgress allValues];
    //数组的makeObjectsPerformSelector:SEL方法来减少自己写循环代码.让数组中每个对象都执行 cancelDownload 方法
    [allDownloads makeObjectsPerformSelector:@selector(cancelDownload)];
    //从字典移除记录。
    [self.imageDownloadsInProgress removeAllObjects];
}

// -------------------------------------------------------------------------------
//    dealloc
//  If this view controller is going away, we need to cancel all outstanding downloads.
// -------------------------------------------------------------------------------
- (void)dealloc
{
    // terminate all pending download connections
    [self terminateAllDownloads];
}

// -------------------------------------------------------------------------------
//    didReceiveMemoryWarning
// -------------------------------------------------------------------------------
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
    // terminate all pending download connections
    [self terminateAllDownloads];
}


#pragma mark - UITableViewDataSource

// -------------------------------------------------------------------------------
//    tableView:numberOfRowsInSection:
//  Customize the number of rows in the table view.
// -------------------------------------------------------------------------------
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSUInteger count = self.entries.count;
    
    // if there's no data yet, return enough rows to fill the screen
    if (count == 0)
    {
        return kCustomRowCount;
    }
    return count;
}

// -------------------------------------------------------------------------------
//    tableView:cellForRowAtIndexPath:
// -------------------------------------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = nil;
    
    NSUInteger nodeCount = self.entries.count;
    
    if (nodeCount == 0 && indexPath.row == 0)
    {
        // add a placeholder cell while waiting on table data 在storyboard中定义的加载中...
        cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier forIndexPath:indexPath];
    }
    else
    {
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

        // Leave cells empty if there's no data yet
        if (nodeCount > 0)
        {
            // Set up the cell representing the app
            AppRecord *appRecord = (self.entries)[indexPath.row];
            
            cell.textLabel.text = appRecord.appName;
            cell.detailTextLabel.text = appRecord.artist;
            
            // Only load cached images; defer new downloads until scrolling ends
            //这里注释得很明白,只加载cached(已缓存的)图片。
            //【defer】 英[dɪˈfɜː(r)] ,美[dɪˈfɜːr] ,v.推迟; 延缓; 展期;
            if (!appRecord.appIcon)//如果为空
            {
                if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
                {
                    //如果没有拖动,也没在惯性滑动。开始下载。
                    [self startIconDownload:appRecord forIndexPath:indexPath];
                }
                // if a download is deferred or in progress, return a placeholder image
                //如果正在下载中给一个默认图。
                cell.imageView.image = [UIImage imageNamed:@"Placeholder.png"];                
            }
            else
            {
               // (self.entries)[indexPath.row]; 直接使用
               cell.imageView.image = appRecord.appIcon;
            }
        }
    }
    
    return cell;
}


#pragma mark - Table cell image support

// -------------------------------------------------------------------------------
//    startIconDownload:forIndexPath: 第N个Cell的下载图片资源
// -------------------------------------------------------------------------------
- (void)startIconDownload:(AppRecord *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
    IconDownloader *iconDownloader = (self.imageDownloadsInProgress)[indexPath];
    if (iconDownloader == nil) 
    {
        iconDownloader = [[IconDownloader alloc] init];
        iconDownloader.appRecord = appRecord;
        [iconDownloader setCompletionHandler:^{
            
            //图片加载完成时回调本段代码
            
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            
            // Display the newly loaded image
            cell.imageView.image = appRecord.appIcon;
            
            // Remove the IconDownloader from the in progress list.
            // This will result in it being deallocated. 从字典中把下载对象移除。之前是有记录要下载的。
            [self.imageDownloadsInProgress removeObjectForKey:indexPath];
            
        }];
        //在开始下载后,把下载对象记录到字典管理。
        (self.imageDownloadsInProgress)[indexPath] = iconDownloader;
        [iconDownloader startDownload];  
    }
}

// -------------------------------------------------------------------------------
//    loadImagesForOnscreenRows
//  This method is used in case the user scrolled into a set of cells that don't
//  have their app icons yet.
// -------------------------------------------------------------------------------
- (void)loadImagesForOnscreenRows
{
    if (self.entries.count > 0)
    {
        //获取tableview 当前可见的编号。indexPathsForVisibleRows
        NSArray *visiblePaths = [self.tableView indexPathsForVisibleRows];
        for (NSIndexPath *indexPath in visiblePaths)
        {
            AppRecord *appRecord = (self.entries)[indexPath.row];
            
            if (!appRecord.appIcon)
            // Avoid the app icon download if the app already has an icon
            // 如果没有图片 开始下载图片
            {
                [self startIconDownload:appRecord forIndexPath:indexPath];
            }
        }
    }
}


#pragma mark - UIScrollViewDelegate

// -------------------------------------------------------------------------------
//    scrollViewDidEndDragging:willDecelerate:
//  Load images for all onscreen rows when scrolling is finished.
// tableview 滚动结束事件回调。停下来时,加载当前屏的图片。
// -------------------------------------------------------------------------------
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate)
    {
        [self loadImagesForOnscreenRows];
    }
}

// -------------------------------------------------------------------------------
//    scrollViewDidEndDecelerating:scrollView
//  When scrolling stops, proceed to load the app icons that are on screen.
// -------------------------------------------------------------------------------
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    //惯性滑动结束时,开始加载图片,同上。
    [self loadImagesForOnscreenRows];
}

@end
/*
 Copyright (C) 2017 Apple Inc. All Rights Reserved.
 See LICENSE.txt for this sample’s licensing information
 
 Abstract:
 Helper object for managing the downloading of a particular app's icon.
  It uses NSURLSession/NSURLSessionDataTask to download the app's icon in the background if it does not
  yet exist and works in conjunction with the RootViewController to manage which apps need their icon.
 */

#import "IconDownloader.h"
#import "AppRecord.h"

#define kAppIconSize 48


@interface IconDownloader ()

@property (nonatomic, strong) NSURLSessionDataTask *sessionTask;

@end


#pragma mark -

@implementation IconDownloader

// -------------------------------------------------------------------------------
//    startDownload
// -------------------------------------------------------------------------------
- (void)startDownload
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:self.appRecord.imageURLString]];

    // create an session data task to obtain and download the app icon
    _sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:request
                                                   completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        // 这里使用NSURLSessionDataTask 进行数据加载。@NSURLSession @NSURLSessionDataTask 属一个知识体系,可以系统学习。
        //_sessionTask 声明为了属性,目的是可以进行控制。
        
        // in case we want to know the response status code
        //NSInteger HTTPStatusCode = [(NSHTTPURLResponse *)response statusCode];

        if (error != nil)
        {
            if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
            {
                // if you get error NSURLErrorAppTransportSecurityRequiresSecureConnection (-1022),
                // then your Info.plist has not been properly configured to match the target server.
                //
                abort();
            }
        }
        
        //以上代码好像是规范格式。
        
        
                                                            
        [[NSOperationQueue mainQueue] addOperationWithBlock: ^{
            
            // Set appIcon and clear temporary data/image
            UIImage *image = [[UIImage alloc] initWithData:data];
            
            if (image.size.width != kAppIconSize || image.size.height != kAppIconSize)
            {
                //对图片进行裁剪操作。
                CGSize itemSize = CGSizeMake(kAppIconSize, kAppIconSize);
                UIGraphicsBeginImageContextWithOptions(itemSize, NO, 0.0f);
                CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
                [image drawInRect:imageRect];
                self.appRecord.appIcon = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
            }
            else
            {
                self.appRecord.appIcon = image;
            }
            
            //不如何image给到了appRecord数据模型。
            
            // call our completion handler to tell our client that our icon is ready for display
            //通知加载完成
            if (self.completionHandler != nil)
            {
                self.completionHandler();
            }
        }];
    }];
    
    //开启task
    [self.sessionTask resume];
}

// -------------------------------------------------------------------------------
//    cancelDownload
// -------------------------------------------------------------------------------
- (void)cancelDownload
{
    [self.sessionTask cancel];
    _sessionTask = nil;
}

@end

代码比较简单

1.appdelegate 去下载数据并解析。并更新tableview

//RootViewController : UITableViewController 解析数据给到tableview

rootViewController.entries = weakParser.appRecordList;

2.使用 model给tableview展示。

@property (nonatomic, strong) NSString *appName;

@property (nonatomic, strong) UIImage *appIcon;

@property (nonatomic, strong) NSString *artist;

@property (nonatomic, strong) NSString *imageURLString;

@property (nonatomic, strong) NSString *appURLString;

appIcon开始时为空,当展示在屏幕时,起线程去加载。并记录在

self.imageDownloadsInProgress

第一个下载对应一个线程对象。

3.这个代码  UIImage 会不数增加,数量够多,内存肯定溢出。

所以需要图片的加载策略。图片的三级缓存可系统独立学习。

猜你喜欢

转载自www.cnblogs.com/mamamia/p/12273766.html