本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。
本文相关目录:
==================== 所属文集:4.0 多线程 ====================
4.1 多线程基础->1.0 进程 & 线程
······················ 2.0 多线程简介
4.2 pthread
4.3 NSThread->1.0 创建线程
····················· 2.0 线程属性
····················· 3.0 线程状态/线程生命周期
····················· 4.0 多线程安全隐患
····················· 5.0 线程间通讯和常用方法
4.4 GCD->1.0 GCD简介和使用
·············· 2.0 线程间的通信
·············· 3.0 其他用法
·············· 4.0 GCD 的定时器事件
4.5 NSOperation->1.0 NSOperation简介
························ 2.0 NSOperationQueue
························ 3.0 线程间通信
························ 4.0 自定义NSOperation
4.6 RunLoop - 运行循环
===================== 所属文集:4.0 多线程 =====================
1.0 自定义NSOperation
自定义NSOperation的步骤很简单:
- 重写- (void)main方法,在里面实现想执行的任务
重写- (void)main方法的注意点:
- 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
- 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应
代码示例:
ViewController.m
#import "TDOperation.h"
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建自定义 TDGOperation
TDOperation *op = [[TDOperation alloc] init];
// 3.把操作(任务)添加到队列中,并自动调用 start 方法
[queue addOperation:op];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
TDOperation.h(继承自:NSOperation)
#import
@interface TDOperation : NSOperation
@end
TDOperation.m
#import "TDOperation.h"
@implementation TDOperation
//需要执行的任务
- (void)main {
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
}
@end
运行结果:
自定义NSOperation[1567:84075] download1 -0-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download1 -1-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download1 -2-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -0-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -1-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -2-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -0-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -1-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -2-- {number = 2, name = (null)}
2.0 自定义NSOperation队列的取消操作
代码示例:
ViewController.m
#import "TDOperation.h"
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //队列
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
queue.maxConcurrentOperationCount = 2;
// 3.添加操作 - 自定义 NSOperation
[queue addOperation:[[TDOperation alloc] init]];
self.queue = queue;
}
#pragma mark - 取消队列的所有操作
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)
[self.queue cancelAllOperations];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
TDOperation.h
#import
@interface TDOperation : NSOperation
@end
TDOperation.m
#import "TDOperation.h"
@implementation TDOperation
//需要执行的任务
- (void)main {
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
for (NSInteger i = 0; i < 1000; i++) {
NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
}
// 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
if (self.isCancelled)
return;
}
@end
3.0 多图下载
沙盒结构:
Documents
Library
- Caches
- Preference
tmp
自定义NSOperation下载图片思路 – 有沙盒缓存
代码示例:
ViewController.m
#import "TDApp.h"
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSArray *apps; //所有数据
@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
@property(nonatomic, strong) NSOperationQueue *queue; //队列对象
@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
}
#pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 重用标识
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
TDApp *app = self.apps[indexPath.row];
#pragma mark - app 名称
cell.textLabel.text = app.name;
#pragma mark - 下载量
cell.detailTextLabel.text = app.download;
#pragma mark - 图片
// 1.先从内存缓存中取出图片
UIImage *image = self.imageCache[app.icon];
// 2.判断内存中是否有图片
if (image) {
// 2.1 内存中有图片,直接设置图片
cell.imageView.image = image;
} else {
// 2.2 内存中没有图片,将图片文件数据写入沙盒中
//(1)获得Library/Caches文件夹
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(
NSCachesDirectory, NSUserDomainMask, YES) firstObject];
//(2)获得文件名
NSString *filename = [app.icon lastPathComponent];
//(3)计算出文件的全路径
NSString *file = [cachesPath stringByAppendingPathComponent:filename];
//(4)加载沙盒的文件数据
NSData *data = [NSData dataWithContentsOfFile:file];
// 2.3 判断沙盒中是否有图片
if (data) {
// 有图片,直接利用沙盒中图片,设置图片
UIImage *image = [UIImage imageWithData:data];
cell.imageView.image = image;
// 并将图片存到字典中
self.imageCache[app.icon] = image;
} else {
// 没有图片,先设置一个占位图
cell.imageView.image = [UIImage imageNamed:@"placeholder"];
// 取出图片,并判断这张图片是否有下载操作
NSOperation *operation = self.operations[app.icon];
if (operation == nil) {
// 如果这张图片暂时没有下载操作,则需要创建一个下载操作
// 下载图片是耗时操作,放到子线程
operation = [NSBlockOperation blockOperationWithBlock:^{
// 下载图片
NSData *data =
[NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
// 如果数据下载失败
if (data == nil) {
// 下载失败,移除操作
[self.operations removeObjectForKey:app.icon];
return;
}
// 下载成功,将图片放在 image 中
UIImage *image = [UIImage imageWithData:data];
// 存到字典中
self.imageCache[app.icon] = image;
//回到主线程显示图片
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[tableView reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationNone];
}];
// 将图片文件数据写入沙盒中
[data writeToFile:file atomically:YES];
// 下载完毕,移除操作
[self.operations removeObjectForKey:app.icon];
}];
// 添加到队列中(队列的操作不需要移除,会自动移除)
[self.queue addOperation:operation];
// 并将图片存到字典中
self.operations[app.icon] = operation;
}
}
}
return cell;
}
#pragma mark - 数据懒加载
- (NSArray *)apps {
if (!_apps) {
NSArray *dictArray =
[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:@"apps.plist"
ofType:nil]];
NSMutableArray *appArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
[appArray addObject:[TDApp appWithDict:dict]];
}
_apps = appArray;
}
return _apps;
}
#pragma mark - 懒加载
- (NSMutableDictionary *)imageCache {
if (!_imageCache) {
_imageCache = [NSMutableDictionary dictionary];
}
return _imageCache;
}
#pragma mark - 懒加载
- (NSOperationQueue *)queue {
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
#pragma mark - 懒加载
- (NSMutableDictionary *)operations {
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
TDApp.h
#import
@interface TDApp : NSObject
@property(nonatomic, strong) NSString *icon; // 图片
@property(nonatomic, strong) NSString *download; //下载量
@property(nonatomic, strong) NSString *name; // 名字
+ (instancetype)appWithDict:(NSDictionary *)dict;
@end
TDApp.m
#import "TDApp.h"
@implementation TDApp
+ (instancetype)appWithDict:(NSDictionary *)dict {
TDApp *app = [[self alloc] init];
[app setValuesForKeysWithDictionary:dict];
return app;
}
@end
4.0 多图下载 - SDWebImage
SDWebImage:
- iOS中著名的网络图片处理框架
- 包含的功能:图片下载、图片缓存、下载进度监听、gif处理等等
- 框架地址:https://github.com/rs/SDWebImage
- SDWebImage的图片缓存周期是:1周
代码示例:
ViewController.m
#import "TDApp.h"
#import "UIImageView+WebCache.h"
#import "ViewController.h"
@interface ViewController ()
@property(nonatomic, strong) NSArray *apps; //所有数据
@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
@property(nonatomic, strong) NSOperationQueue *queue; //队列对象
@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
}
#pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 重用标识
static NSString *ID = @"app";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
TDApp *app = self.apps[indexPath.row];
#pragma mark - app 名称
cell.textLabel.text = app.name;
#pragma mark - 下载量
cell.detailTextLabel.text = app.download;
#pragma mark - 图片
// expectedSize: 图片的总字节数 receivedSize: 已经接收的图片字节数
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
placeholderImage:[UIImage imageNamed:@"placeholder"]
options:0 // 0 表示什么都不做
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
NSLog(@"下载进度:%f", 1.0 * receivedSize / expectedSize);
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,
NSURL *imageURL) {
NSLog(@"下载完图片");
}];
return cell;
}
#pragma mark - 数据懒加载
- (NSArray *)apps {
if (!_apps) {
NSArray *dictArray =
[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:@"apps.plist"
ofType:nil]];
NSMutableArray *appArray = [NSMutableArray array];
for (NSDictionary *dict in dictArray) {
[appArray addObject:[TDApp appWithDict:dict]];
}
_apps = appArray;
}
return _apps;
}
#pragma mark - 懒加载
- (NSMutableDictionary *)imageCache {
if (!_imageCache) {
_imageCache = [NSMutableDictionary dictionary];
}
return _imageCache;
}
#pragma mark - 懒加载
- (NSOperationQueue *)queue {
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
#pragma mark - 懒加载
- (NSMutableDictionary *)operations {
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
#pragma mark - 设置控制器的内存警告
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
self.imageCache = nil;
self.operations = nil;
[self.queue cancelAllOperations];
}
@end
TDApp.h
#import
@interface TDApp : NSObject
@property(nonatomic, strong) NSString *icon; // 图片
@property(nonatomic, strong) NSString *download; //下载量
@property(nonatomic, strong) NSString *name; // 名字
+ (instancetype)appWithDict:(NSDictionary *)dict;
@end
TDApp.m
#import "TDApp.h"
@implementation TDApp
+ (instancetype)appWithDict:(NSDictionary *)dict {
TDApp *app = [[self alloc] init];
[app setValuesForKeysWithDictionary:dict];
return app;
}
@end
5.0【区别】GCD & NSOperationQueue 队列类型的创建方式
GCD 队列类型的创建方式:
(1)并发队列:手动创建、全局
(2)串行队列:手动创建、主队列
NSOperationQueue的队列类型的创建方法:
(1)主队列:[NSOperationQueue mainQueue]
- 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行
(2)其他队列(同时包含了串行、并发功能):[NSOperationQueue alloc]init]
- 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行
本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_4.0_multithreading.git
作者:蓝田(Loto)
出处:
如果你觉得本篇文章对你有所帮助,请点击文章末尾下方“喜欢”
如有疑问,请通过以下方式交流:
① 评论区回复
② 微信(加好友请注明“+称呼”)
③发送邮件
至 [email protected]
本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。