单例模式的作用
ARC中,单例模式的实现
在.m中保留一个全局的static的实例
static id _instance;
重写allocWithZone:方法,在这里创建唯一的实例(注意线程安全)
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
- (id)copyWithZone:(struct _NSZone *)zone
{
return _instance;
}
#if __has_feature(objc_arc)
//ARC
#else
//MRC
#endif
NSOperation的作用
NSOperation和NSOperationQueue实现多线程的具体步骤
NSOperation的子类
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
使用NSOperation子类的方式有3种
NSInvocationOperation
// 1.将操作封装到Operation中
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 2.执行封装的操作
// 如果直接执行NSInvocationOperation中的操作, 那么默认会在主线程中执行
[op1 start];
// 1.封装操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1- %@", [NSThread currentThread]);
}];
// 2.添加操作
[op1 addExecutionBlock:^{
NSLog(@"2- %@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"3- %@", [NSThread currentThread]);
}];
// 2.执行操作
// 如果只封装了一个操作, 那么默认会在主线程中执行
// 如果封装了多个操作, 那么除了第一个操作以外, 其它的操作会在子线程中执行
[op1 start];
@implementation XMOperation
- (void)main
{
NSLog(@"%s, %@", __func__,[NSThread currentThread]);
}
NSOperationQueue的作用
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
添加操作到NSOperationQueue中
- (void)addOperation:(NSOperation *)op; - (void)addOperationWithBlock:(void (^)(void))block;
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.封装任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 3.将任务添加到队列中
// 只要将一个任务添加到alloc/init的队列中, 那么队列内部会自动调用start
// 只要将一个任务添加到alloc/init的队列中, 就会开启一个新的线程执行队列
[queue addOperation:op1];
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.封装任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1 = %@", [NSThread currentThread]);
}];
// 3.将任务添加到队列中
[queue addOperation:op1];
另一种简洁写法:
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.将任务添加到队列中
// addOperationWithBlock方法会做两件事情
// 1.根据传入的block, 创建一个NSBlockOperation对象
// 2.将内部创建好的NSBlockOperation对象, 添加到队列中
[queue addOperationWithBlock:^{
NSLog(@"1 = %@", [NSThread currentThread]);
}];
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.封装任务
XMGOperation *op1 = [[XMGOperation alloc] init];
// 3.将任务添加到队列中
[queue addOperation:op1];
什么是并发数:同时执行的任务数
// 如果是YES, 代表需要暂停
// 如果是NO ,代表恢复执行
self.queue.suspended = YES;
self.queue.suspended = !self.queue.suspended;
// 内部会调用所有任务的cancel方法
[self.queue cancelAllOperations];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 开启子线程下载图片
[queue addOperationWithBlock:^{
NSString *urlStr = @"http://...";
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlStr];
NSData *data = [NSData dataWithContentsOfURL:url];
//生成下载好的图片
UIImage *image = [UIIMage imageWithData:data]
// 回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
[operationB addDependency:operationA]; // 操作B依赖于操作A
op1.completionBlock = ^{
NSLog(@"第一张图片下载完毕");
};
op2.completionBlock = ^{
NSLog(@"第二张图片下载完毕");
};
图片合成案例代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
// queue.maxConcurrentOperationCount = 1;
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 1.开启一个线程下载第一张图片
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://www.ibayue.com/images/fileup/201411/20141103150824.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 2.生成下载好的图片
UIImage *image = [UIImage imageWithData:data];
image1 = image;
}];
// 2.开启一个线程下载第二长图片
NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSURL *url = [NSURL URLWithString:@"http://i0.letvimg.com/lc02_yunzhuanma/201503/07/00/48/170acdff2830753f89957742b0e8f5b8_25431356/thumb/2_400_225.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
// 2.生成下载好的图片
UIImage *image = [UIImage imageWithData:data];
image2 = image;
}];
// 3.开启一个线程合成图片
NSOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
[image1 drawInRect:CGRectMake(0, 0, 100, 200)];
[image2 drawInRect:CGRectMake(100, 0, 100, 200)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// 4.回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"回到主线程更新UI");
self.imageView.image = newImage;
}];
}];
// 监听任务是否执行完毕
op1.completionBlock = ^{
NSLog(@"第一张图片下载完毕");
};
op2.completionBlock = ^{
NSLog(@"第二张图片下载完毕");
};
// 添加依赖
// 只要添加了依赖, 那么就会等依赖的任务执行完毕, 才会执行当前任务
// 注意:
// 1.添加依赖, 不能添加循环依赖
// 2.NSOperation可以跨队列添加依赖
[op3 addDependency:op1];
[op3 addDependency:op2];
// 将任务添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue2 addOperation:op3];
}
#import "XMGApp.h"
@implementation XMGApp
- (instancetype)initWithDict:(NSDictionary *)dict{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
+ (instancetype)appWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
#pragma mark - lazy
- (NSArray *)apps
{
if (!_apps) {
// 1.从plist中加载数组
NSString *path = [[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil];
NSArray *arr = [NSArray arrayWithContentsOfFile:path];
// 2.定义数组保存转换好的模型
NSMutableArray *models = [NSMutableArray arrayWithCapacity:arr.count];
// 3.遍历数组中所有的字典, 将字典转换为模型
for (NSDictionary *dict in arr) {
XMGApp *app = [XMGApp appWithDict:dict];
[models addObject:app];
}
_apps = [models copy];
}
return _apps;
}
- (NSMutableDictionary *)imageCaches
{
if (!_imageCaches) {
_imageCaches = [NSMutableDictionary dictionary];
}
return _imageCaches;
}
#pragma mark - UITableViewDatasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// NSLog(@"%s", __func__);
// 1.获取cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
// 2.设置数据
XMGApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download];
// 设置图片
/* 存在的问题: 1.在主线程中下载图片, 可能会阻塞主线程 2.重复下载 */
// 1.先从内存缓存中获取, 如果没有才去下载
UIImage *image = self.imageCaches[app.icon];
if (image == nil) {
// 2.再从磁盘缓存中获取, 如果没有才去下载
NSString *filePath = [app.icon cacheDir];
NSData *data = [NSData dataWithContentsOfFile:filePath];
if (data == nil) {
NSLog(@"下载图片");
// 内存缓存中没有值, 需要下载
NSURL *url = [NSURL URLWithString:app.icon];
data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 将下载好的图片缓存到内存缓存中
self.imageCaches[app.icon] = image;
// 将下载好的图片写入到磁盘
[data writeToFile:filePath atomically:YES];
// 更新UI
cell.imageView.image = image;
}else
{
NSLog(@"使用磁盘缓存");
NSData *data = [NSData dataWithContentsOfFile:filePath];
UIImage *image = [UIImage imageWithData:data];
// 将下载好的图片缓存到内存缓存中
self.imageCaches[app.icon] = image;
// 更新UI
cell.imageView.image = image;
}
}else
{
NSLog(@"使用内存缓存");
// 更新UI
cell.imageView.image = image;
}
// 3.返回cell
return cell;
}
Documents
Caches
Preferences
tmp
封装获取文件路径方法
- (NSString *)cacheDir
{
// 1.获取cache目录
NSString *dir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 2.生成绝对路径
return [dir stringByAppendingPathComponent:[self lastPathComponent]];
}
- (NSString *)documentDir {
NSString *dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
return [dir stringByAppendingPathComponent:[self lastPathComponent]];
}
- (NSString *)tmpDir {
NSString *dir = NSTemporaryDirectory();
return [dir stringByAppendingPathComponent:[self lastPathComponent]];
}
优化版本:
#pragma mark - UITableViewDatasource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// NSLog(@"%s", __func__);
// 1.获取cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
// 2.设置数据
XMGApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download];
cell.imageView.image = [UIImage imageNamed:@"abc"]; //开始没有图片,搞占位图片
// 设置图片
/* 存在的问题: 1.在主线程中下载图片, 可能会阻塞主线程 2.重复下载 */
// 1.先从内存缓存中获取, 如果没有才去下载
UIImage *image = self.imageCaches[app.icon];
if (image == nil) {
// 2.再从磁盘缓存中获取, 如果没有才去下载
NSString *filePath = [app.icon cacheDir];
__block NSData *data = [NSData dataWithContentsOfFile:filePath];
if (data == nil) {
NSLog(@"下载图片");
/* 存在的问题: 1.重复设置 2.重复下载 */
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 3.判断当前图片是否有任务正在下载
NSBlockOperation *op = self.operations[app.icon];
if (op == nil) {
// 没有对应的下载任务
op = [NSBlockOperation blockOperationWithBlock:^{
// 开启子线程下载
// 内存缓存中没有值, 需要下载
NSURL *url = [NSURL URLWithString:app.icon];
data = [NSData dataWithContentsOfURL:url];
if (data == nil) {
// 如果下载失败, 应该将当前图片对应的下载任务从缓存中移除 \以便于下次可以再次尝试下载
[self.operations removeObjectForKey:app.icon];
return;
}
UIImage *image = [UIImage imageWithData:data];
// 将下载好的图片缓存到内存缓存中
self.imageCaches[app.icon] = image;
// 将下载好的图片写入到磁盘
[data writeToFile:filePath atomically:YES];
// 回到主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新UI");
// 刷新指定的行,网速慢的情况下防止图片跳来跳去
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
// 从缓存中将当前图片对应的下载任务移除
[self.operations removeObjectForKey:app.icon];
}];
}];
// 先将下载任务保存到缓存中
self.operations[app.icon] = op;
// 将任务添加到队列中
[queue addOperation:op];
}
}else
{
NSLog(@"使用磁盘缓存");
NSData *data = [NSData dataWithContentsOfFile:filePath];
UIImage *image = [UIImage imageWithData:data];
// 将下载好的图片缓存到内存缓存中
self.imageCaches[app.icon] = image;
// 更新UI
cell.imageView.image = image;
}
}else
{
NSLog(@"使用内存缓存");
// 更新UI
cell.imageView.image = image;
}
// 3.返回cell
return cell;
}
逻辑1 - 从来没下载过
1.查看内存缓存是否有图片
2.查看磁盘缓存是否有图片
3.查看时候有任务正在下载当前图片
4.开启任务下载图片
5.写入磁盘
6.缓存到内存
7.移除下载操作
8.显示图片
逻辑2 - 已经下载过
1.查看内存缓存是否有图片
2.查看磁盘缓存是否有图片
3.使用磁盘缓存
4.将图片缓存到内存中
5.更新UI
逻辑3 - 已经下载过, 并且不是重新启动
1.查看内存缓存是否有图片
2.更新UI
SDWebImageManager
SDWebImage常见面试题
默认缓存时间多少
缓存的地址
cleanDisk如何清理过期图片
clearDisk如何清理磁盘
SDWebImage如何播放图片
SDWebImage如何判断图片类型
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.rowHeight = 150;
// 直接下载一张图片
/* 第1个参数: 需要下载图片的URL 第2个参数: 下载的配置信息(例如是否需要缓存等等) 第3个参数: 下载过程中的回调 第4个参数: 下载完成后的回调 */
NSURL *url = [NSURL URLWithString:@"http://ia.topit.me/a/f9/0a/1101078939e960af9ao.jpg"];
[[SDWebImageManager sharedManager] downloadImageWithURL:url options:kNilOptions progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// receivedSize : 已经接受到的数据大小
// expectedSize : 需要下载的图片的总大小
NSLog(@"正在下载 %zd %zd", receivedSize, expectedSize);
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// image : 下载好的图片
// error: 错误信息
// cacheType: 缓存的类型
// finished: 是否下载完成
// imageURL: 被下载的图片的地址
NSLog(@"下载成功 %@", image);
}];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// 1.获取cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"app"];
// 2.设置数据
XMGApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = [NSString stringWithFormat:@"下载:%@", app.download];
// 在老版本的SDWebImage中, 以下方法是没有sd_前缀的
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon] placeholderImage:[UIImage imageNamed:@"abc"]];
// 3.返回cell
return cell;
}