iOS基础 - NSOperation | NSOperationQueue
▶ NSOperation
NSOperation 在 MVC 中属于 M 层,是用来封装单个任务相关代码和数据的抽象类。它不能够直接使用,必须使用其子类。使用⼦类有三种方式
A - NSInvocationOperation:封装了执行操作的 target 和要执行的 action
B - NSBlockOperation :封装了要执行的代码块
C - 自定义其子类
注:使用 Operation 的目的就是为了让开发人员不再关心线程!NSOperation 本身并无主、子线程之分,可在任意线程中使用,通常和 NSOperationQueue 搭配使用。队列用来管理一组操作对象的执行,它会根据需要自动为操作开辟合适数量的线程以完成任务的并发执行
NSInvocationOperation 调⽤ start ⽅法来执⾏任务,默认同步执行
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 // 封装要执行的任务 5 NSInvocationOperation *operationA = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOperationA:) object:@"operationA"]; 6 // 执行操作 7 [operationA start]; 8 } 9 10 - (void)testOperationA:(NSString *)name{ 11 // 操作对象默认在当前线程中执行(这里是主线程),只有添加到队列中时才会开启新的线程 12 NSLog(@"%@",[NSThread currentThread]); // <_NSMainThread: 0x600003ff8480>{number = 1, name = main} 13 }
NSBlockOperation 是否会开启线程,关键是看所封装操作数
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{ 5 NSLog(@"%@",[NSThread currentThread]);// 当前线程:<_NSMainThread: 0x60000331c000>{number = 1, name = main} 6 }]; 7 [operationB start]; 8 }
只要封装的操作数 > 1,就会异步执行
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{ 5 NSLog(@"%@",[NSThread currentThread]); // 当前线程:<_NSMainThread: 0x6000027ec540>{number = 1, name = main} 6 }]; 7 8 9 // 以下会开辟新线程,且 block 之间是并发执行的 10 // 会开辟新的线程 11 [operationC addExecutionBlock:^{ 12 NSLog(@"添加1 = %@",[NSThread currentThread]); // 添加1 = <NSThread: 0x6000027a1b80>{number = 4, name = (null)} 13 }]; 14 15 // 会开辟新的线程 16 [operationC addExecutionBlock:^{ 17 NSLog(@"添加2 = %@",[NSThread currentThread]); // 添加2 = <NSThread: 0x6000027b0cc0>{number = 6, name = (null)} 18 }]; 19 [operationC start]; 20 21 }
▶ NSOperationQueue
我们知道 NSInvocationOperation 默认在当前线程中执行;如果将操作添加到队列中系统就会自动异步执行:系统自动把队列中的操作对象取出,将其封装到一条新的线程中执行。下面就把操作对象添加到 NSOperationQueue 中
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 // 搞 3 个操作对象 5 NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOperation1) object:nil]; 6 NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOperation2) object:nil]; 7 NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{ 8 NSLog(@"operation3-1--%@",[NSThread currentThread]); 9 }]; 10 [operation3 addExecutionBlock:^{ 11 NSLog(@"operation3-2--%@",[NSThread currentThread]); 12 }]; 13 14 // 将操作对象添加进队列 15 NSOperationQueue *operationsQueue = [NSOperationQueue new]; 16 // [operationsQueue setSuspended:YES]; // 暂停队列 17 [operationsQueue addOperation:operation1]; 18 [operationsQueue addOperation:operation2]; 19 [operationsQueue addOperation:operation3]; 20 21 operation2.queuePriority = NSOperationQueuePriorityHigh; // 优先级 22 // [operation3 cancel]; // 取消单个操作 23 // [operationsQueue cancelAllOperations]; // 取消全部操作 24 }
日志信息
上面的代码一共有四个任务:operation1 和 operation2 各有一个任务;operation3 有两个任务;也就意味着开启了 4条线程
注:如果操作、队列两者配合使用的话,那么就应当禁止操作的开启,否则程序 crash
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 NSInvocationOperation *operationD = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOperationD) object:nil]; 5 [operationD start];// 不应开启 6 7 // 把操作对象添加进队列 8 NSOperationQueue *queueD = [[NSOperationQueue alloc] init]; 9 [queueD setMaxConcurrentOperationCount:1]; 10 [queueD addOperation:operationD];// crash 11 } 12 13 - (void)testOperationD{ 14 NSLog(@"operationD--%@",[NSThread currentThread]); 15 }
▶ 常用API
并发数:同时执⾏的任务数。最大并发数:同一时间最多只能执行的任务的个数。如果没有设置最大并发数,那么并发的个数是由系统内存和 CPU 决定的,最大并发数建议不要乱写(5 以内),一般以 2~3 为宜
- (NSInteger)maxConcurrentOperationCount; - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
取消操作:取消队列中的所有操作;也可以调用 NSOperation 的 - (void)cancel ⽅法取消单个操作
// NSOperation - (void)cancel; // NSOperationQueue - (void)cancelAllOperations;
暂停和恢复队列:其适用场合,比如在 tableview 界面里开启线程下载网络时会对 UI 会有所影响使用户体验变差,那么这种情况就可以设置当用户操作 UI(如滚动屏幕)时暂停队列而不是取消队列,在停止滚动时恢复队列
- (void)setSuspended:(BOOL)b;
- (BOOL)isSuspended;
操作优先级:NSOperation 在 queue 中的优先级。注:优先级高的任务只能保证被调用的几率会更大,而不是一定就优先执行
- (NSOperationQueuePriority)queuePriority; - (void)setQueuePriority:(NSOperationQueuePriority)p; // 优先级 NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8
操作监听:可以监听一个操作的结束
- (void (^)(void))completionBlock; - (void)setCompletionBlock:(void (^)(void))block;
等待 block 代码块执行完毕后,执行 结束!输出语句
1 NSBlockOperation *operationE = [NSBlockOperation blockOperationWithBlock:^{ 2 for (int i = 0; i<5; i++) { 3 [NSThread sleepForTimeInterval:1.5]; 4 NSLog(@"%d",i); 5 } 6 }]; 7 8 // block 执行完毕后调用 9 operationE.completionBlock = ^{ 10 NSLog(@"结束!"); 11 }; 12 13 NSOperationQueue *queueE = [[NSOperationQueue alloc]init]; 14 [queueE addOperation:operationE];
▶ 操作依赖
NSOperation 之间可以设置依赖来保证执行顺序,⽐如一定要让 操作A 执行完后才能执行 操作B。当然,我们完全可以在不同 queue 的 NSOperation 之间创建依赖关系,这时需要注意循环依赖问题(A 依赖于 B 且 B 又依赖于 A)注:依赖一定要在操作添加进队列之前进行设置。
使用依赖关系使线程有序执行
1 - (void)viewDidLoad { 2 [super viewDidLoad]; 3 4 // 操作依赖 5 NSInvocationOperation *operationK = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOperationK) object:nil]; 6 NSInvocationOperation *operationL = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(testOperationL) object:nil]; 7 NSBlockOperation *operationO = [NSBlockOperation blockOperationWithBlock:^{ 8 for (int i=0; i<3; i++) { 9 NSLog(@"operationO---1---%@",[NSThread currentThread]); 10 } 11 }]; 12 [operationO addExecutionBlock:^{ 13 for (int i=0; i<3; i++) { 14 NSLog(@"operationO---2---%@",[NSThread currentThread]); 15 } 16 }]; 17 18 // 监听结束 19 operationO.completionBlock = ^{ 20 NSLog(@"operationO---结束"); 21 }; 22 operationL.completionBlock = ^{ 23 NSLog(@"operationL---结束"); 24 }; 25 operationK.completionBlock = ^{ 26 NSLog(@"operationK---结束"); 27 }; 28 29 // 依次执行 operationO operationL operationK 30 [operationK addDependency:operationL]; 31 [operationL addDependency:operationO]; 32 33 NSOperationQueue *operationsQueueQ = [[NSOperationQueue alloc] init]; 34 [operationsQueueQ addOperation:operationK]; 35 [operationsQueueQ addOperation:operationL]; 36 [operationsQueueQ addOperation:operationO]; 37 38 } 39 40 -(void)testOperationK{ 41 NSLog(@"--testOperationK--%@",[NSThread currentThread]); 42 } 43 44 -(void)testOperationL{ 45 NSLog(@"--testOperationL--%@",[NSThread currentThread]); 46 }
日志信息
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)