NSOperation

本文介绍了iOS开发中多线程的实现方法,重点讲解了NSOperation及其子类NSInvocationOperation和NSBlockOperation的使用技巧,并探讨了如何利用NSOperationQueue进行高效的多线程任务调度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

2016年03月29日10:42:36更新

感谢@皮特尔 的提醒


在iOS开发中,谈到多线程,大家第一时间想到的一定是GCD。GCD固然是一套强大的多线程解决方案,能够解决绝大多数的多线程问题,但是他易于上手难于精通且到处是坑的特点也注定了想熟练使用它有一定的难度。而且很多人嘴上天天挂着GCD,实际上对它的实际应用也不甚了解。
再者说,在现在的主流开发模式下,能用到多线程的绝大多数就是网络数据请求和网络图片加载,这两点上AFNetwork+SDWebImage已经能满足几乎所有的需求。而剩下的一小部分,简单好用的NSOperation无疑是比GCD更有优势的。
因此,如果你还是坚持『GCD大法好』,那看到这里就不必再看了。如果你想试一试更简单的方法,那就随我来吧。


什么是NSOperation?

和GCD一样,NSOperation也是苹果提供给我们的一套多线程解决方案。实际上它也是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。
NSOperation是一个抽象基类,基本没有什么实际使用价值。我们使用最多的是系统封装好的NSInvocationOperationNSBlockOperation
不过NSOperation一些通用的方法你要知道

NSOperation * operation = [[NSOperation alloc]init];
//开始执行
[operation start];
//取消执行
[operation cancel];
//执行结束后调用的Block
[operation setCompletionBlock:^{
    NSLog(@"执行结束");
}];
使用NSInvocationOperation

NSInvocationOperation的使用方式和给Button添加事件比较相似,需要一个对象和一个Selector。使用方法非常简单。
我们先来写一个方法

- (void)testNSOperation
{
NSLog(@"我在第%@个线程",[NSThread currentThread]);
}

然后调用它

//创建
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];
//执行
[invo start];

得到这样的执行结果


执行结果

我们可以看到NSInvocationOperation其实是同步执行的,因此单独使用的话,这个东西也没有什么卵用,它需要配合我们后面介绍的NSOperationQueue去使用才能实现多线程调用,所以这里我们只需要记住有这么一个东西就行了。

使用NSBlockOperation
  • 终于到了我们今天的第一个重点
    NSBlockOperation也是NSOperation的子类,支持并发的实行一个或多个block,使用起来简单又方便
    执行以下代码
    NSBlockOperation * blockOperation = [[NSBlockOperation 
    blockOperationWithBlock:^{
       NSLog(@"1在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
      NSLog(@"2在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
      NSLog(@"3在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
      NSLog(@"4在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
      NSLog(@"5在第%@个线程",[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
      NSLog(@"6在第%@个线程",[NSThread currentThread]);
    }];
    这里我们多执行两次并比较结果

第一次执行的结果

第二次执行的结果

第三次执行的结果
  • 通过三次不同结果的比较,我们可以看到,NSBlockOperation确实实现了多线程。但是我们可以看到,它并非是将所有的block都放到放到了子线程中。通过上面的打印记录我们可以发现,它会优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,但最大并发数为4(包括主线程在内)。如果block数量大于了4,那么剩下的Block就会等待某个线程空闲下来之后被分配到该线程,且依然是优先分配到主线程。
  • 另外,同一个block中的代码是同步执行的

为了证明以上猜想,我们为它增加更多block,并给每条block添加两行代码。

 NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"1在第%@个线程",[NSThread currentThread]);
    NSLog(@"1haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"2在第%@个线程",[NSThread currentThread]);
    NSLog(@"2haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"3在第%@个线程",[NSThread currentThread]);
    NSLog(@"3haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"4在第%@个线程",[NSThread currentThread]);
    NSLog(@"4haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"5在第%@个线程",[NSThread currentThread]);
    NSLog(@"5haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"6在第%@个线程",[NSThread currentThread]);
    NSLog(@"6haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"7在第%@个线程",[NSThread currentThread]);
    NSLog(@"7haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"8在第%@个线程",[NSThread currentThread]);
    NSLog(@"8haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"9在第%@个线程",[NSThread currentThread]);
    NSLog(@"9haha");
}];
[blockOperation addExecutionBlock:^{
    NSLog(@"10在第%@个线程",[NSThread currentThread]);
    NSLog(@"10haha");
}];

[blockOperation start];

然后我们看一下执行结果


执行结果

]

  • 我们可以看到,最大并发数为4,使用同一个线程的block一定是等待前一个block的代码全部执行结束后才执行,且同步执行。

关于最大并发数
在刚才的结果中我们看到最大并发数为4,但这个值并不是一个固定值。4是我在模拟器上运行的结果,而如果我使用真机来跑的话,最大并发数始终为2。因此,具体的最大并发数和运行环境也是有关系的。我们不必纠结于这个数字

所以NSBlockOperation也不是一个理想的多线程解决方案,尽管我们可以在第一个block中创建UI,在其他Block做数据处理等操作,但还是感觉哪里不舒服。
别着急,我们继续往下看

自定义NSOperation

是的,你没看错,NSOperation是可以自定义的。如果NSInvocationOperationNSBlockOperation无法满足你的需求,你可以选择自定义一个NSOperation。
经过上面的分析,我们发现,系统提供的两种NSOperation是一定满足不了我们的需求的。
那我们是不是需要自定义一个NSOperation呢?
答案是,不需要。
自定义NSOperation并不难,但是依然要写不少代码,这违背了我们简单实现多线程的初衷。况且,接下来我会介绍我们今天真正的主角--NSOperationQueue。所以,我打算直接跳过这一个环节。
如果确实有同学需要的话,可以私信我。。。 如果很多人需要的话。。 我会额外写一篇。。。
(读者:你TM不讲还这么多废话(╯‵□′)╯︵┻━┻)

NSOPerationQueue
简单使用

终于轮到我们今天的主角了。
顾名思义,NSOperationQueue就是执行NSOperation的队列,我们可以将一个或多个NSOperation对象放到队列中去执行。
比如我们上面介绍过的NSInvocationOperation,我们来将它放到队列中来

//依然调用上面的那个方法
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil];

NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperation:invo];

看一下执行结果


执行结果

现在它已经被放到子线程中执行了

我们把刚才写的NSBlockOperation也加到这个Queue中来

 ...原来的代码
//[blockOperation start];
[queue addOperation:blockOperation];

然后我们再来看执行情况


执行结果


我们看到,NSInvocationOperation 和 NSBlockOperation是异步执行的,NSBlockOperation中的每一个Block也是异步执行且都在子线程中执行,每一个Block内部也依然是同步执行。

是不是简单好用又强大?

放入队列中的NSOperation对象不需要调用start方法,NSOPerationQueue会在『合适』的时机去自动调用

更简单的使用方式

除了上述的将NSOperation添加到队列中的使用方法外,NSOperationQueue提供了一个更加简单的方法,只需以下两行代码就能实现多线程调用

   NSOperationQueue * queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
    //这里是你想做的操作
}];

你可以同时添加一个或这个多个Block来实现你的操作
怎么样,是不是简单的要死?
(原来这篇文章只需要看这两句就行了是嘛?

资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “STC单片机电压测量”是一个以STC系列单片机为基础的电压检测应用案例,它涵盖了硬件电路设计、软件编程以及数据处理等核心知识点。STC单片机凭借其低功耗、高性价比和丰富的I/O接口,在电子工程领域得到了广泛应用。 STC是Specialized Technology Corporation的缩写,该公司的单片机基于8051内核,具备内部振荡器、高速运算能力、ISP(在系统编程)和IAP(在应用编程)功能,非常适合用于各种嵌入式控制系统。 在源代码方面,“浅雪”风格的代码通常简洁易懂,非常适合初学者学习。其中,“main.c”文件是程序的入口,包含了电压测量的核心逻辑;“STARTUP.A51”是启动代码,负责初始化单片机的硬件环境;“电压测量_uvopt.bak”和“电压测量_uvproj.bak”可能是Keil编译器的配置文件备份,用于设置编译选项和项目配置。 对于3S锂电池电压测量,3S锂电池由三节锂离子电池串联而成,标称电压为11.1V。测量时需要考虑电池的串联特性,通过分压电路将高电压转换为单片机可接受的范围,并实时监控,防止过充或过放,以确保电池的安全和寿命。 在电压测量电路设计中,“电压测量.lnp”文件可能包含电路布局信息,而“.hex”文件是编译后的机器码,用于烧录到单片机中。电路中通常会使用ADC(模拟数字转换器)将模拟电压信号转换为数字信号供单片机处理。 在软件编程方面,“StringData.h”文件可能包含程序中使用的字符串常量和数据结构定义。处理电压数据时,可能涉及浮点数运算,需要了解STC单片机对浮点数的支持情况,以及如何高效地存储和显示电压值。 用户界面方面,“电压测量.uvgui.kidd”可能是用户界面的配置文件,用于显示测量结果。在嵌入式系统中,用
资源下载链接为: https://pan.quark.cn/s/abbae039bf2a 在 Android 开发中,Fragment 是界面的一个模块化组件,可用于在 Activity 中灵活地添加、删除或替换。将 ListView 集成到 Fragment 中,能够实现数据的动态加载与列表形式展示,对于构建复杂且交互丰富的界面非常有帮助。本文将详细介绍如何在 Fragment 中使用 ListView。 首先,需要在 Fragment 的布局文件中添加 ListView 的 XML 定义。一个基本的 ListView 元素代码如下: 接着,创建适配器来填充 ListView 的数据。通常会使用 BaseAdapter 的子类,如 ArrayAdapter 或自定义适配器。例如,创建一个简单的 MyListAdapter,继承自 ArrayAdapter,并在构造函数中传入数据集: 在 Fragment 的 onCreateView 或 onActivityCreated 方法中,实例化 ListView 和适配器,并将适配器设置到 ListView 上: 为了提升用户体验,可以为 ListView 设置点击事件监听器: 性能优化也是关键。设置 ListView 的 android:cacheColorHint 属性可提升滚动流畅度。在 getView 方法中复用 convertView,可减少视图创建,提升性能。对于复杂需求,如异步加载数据,可使用 LoaderManager 和 CursorLoader,这能更好地管理数据加载,避免内存泄漏,支持数据变更时自动刷新。 总结来说,Fragment 中的 ListView 使用涉及布局设计、适配器创建与定制、数据绑定及事件监听。掌握这些步骤,可构建功能强大的应用。实际开发中,还需优化 ListView 性能,确保应用流畅运
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值