iOS并发编程(上)-- NSOperation queue

YOLOv8 轴承缺陷检测 使用YOLOv8进行训练、评估和可视化预测结果 包含1440张图片的轴承缺陷检测数据集 YOLO格式或XML格式 本文介绍了一个基于YOLOv8的轴承缺陷检测项目。数据集包含1440张图片,标注了4类缺陷(凹槽、凹陷、卡伤、划痕),按8:1:1比例划分为训练集(1152张)、验证集(144张)和测试集(144张)。标注文件支持YOLO格式和XML格式,可直接用于模型训练。项目实现了数据准备、模型训练、评估及可视化预测结果的全流程,使用YOLOv8n预训练模型进行50个epoch的训练,并提供了数据格式转换(XML转YOLO)的代码示例。最终模型可自动检测轴承表面缺陷,并保存预测结果。 阅读详情
并发是多件事情同时发生的一个概念。
在过去,在应用中引入并发就需要创建一个或者多个额外的线程。不幸的是,写线程代码是一个挑战。线程是低层次工具必须手动的管理。给定应用可选的线程数会基于当前系统负载和硬件动态的改变。实现一个正确的线程方案变得额外的困难,很难达到。并且,线程同步机制的使用会增加软件的复杂度和风险而且还不能保证提升性能。

iOS和OS X都提供了一个比传统基于线程更加异步的方式给多任务的执行。不用直接创建线程,应用只需要指定任务并且让系统执行他们。开发者获取了更加简单和高效的编程模型。

尽管线程已经使用了多年并且会继续发挥作用,他们不再解决一般问题的多任务了。使用线程时,创建一个可衡量的解决方案在你的肩上。你需要决定创建多少个线程并且随着系统条件的变化调整线程数量。另外一个问题是你的程序承担大多数线程创建和维护的开销。

为了替换直接依赖线程,OS X 和iOS采用了一个异步设计方式来解决并发问题。异步函数引入操作系统多年了并且经常使用来执行花长时间运行的任务,比如从磁盘读取数据。当调用时,一个异步函数在后台做这些任务并且在完成后返回。典型的,这个工作涉及到一个后台线程,在线程上开始这个工作,完成以后发送一个通知给调用方。过去,如果你希望做的事情的异步函数不存在,你必须写你自己的异步函数然后创建你的线程。现在IOS和OS X提供了技术允许你来执行任何异步任务不需要自己管理线程。

其中一个技术执行异步任务的是Grand Central Dispatch(GCD).这个技术把你通常需要写在你应用中的线程管理代码交给了系统层面。所有你需要做的就是定义你希望执行的任务把他们添加到一个合适的dispatch queue上。GCD创建需要的线程来并且将你的任务放在这些线程上执行。因为线程管理是系统的一部分了,GCD提供一个全部的方法来管理任务的执行,提供比传统线程更好的效率。

Operation Queues是Objective-C对象类似于dispatch queues的作用。你定义你的任务然后添加任务到operation queue,它处理任务执行的调度。像GCD一样,operation queues处理线程管理的所有事情,确保你的任务在系统上执行得又快又有效率。

Dispatch Queues
Dispatch Queues是一个基于C的机制来执行用户任务。一个Dispatch Queue执行任务可以是串行的也可以是并发的。但是总是先进先出的。一个串行的Dispatch Queue一次只运行一个任务,直到任务完成后再从队列头上取出下一个任务开始执行。相对而言,一个并发执行的Dispatch Queue启动它能启动的尽可能多的任务,不需要等待。

Dispatch Queues有以下优点:
他们提供直接而简单的编程接口
他们提供自动和完全的线程池管理
他们提供协作组件的速度。
他们更加内存高效(因为线程栈不在应用的内存中,而在内核的内存中?)
他们不会导致内核负载过高
他们使用dispatch queue异步处理任务,不会让queue死锁
他们在竞争中保持平和。
串行dispatch queue提供了一个比锁和其他同步机制更加高效的选择

你提交给dispatch queue的任务必须封装在一个函数或者一个block 对象中。block对象是在ios4中的类似于函数指针但有有额外优点的c语言特性。与其定义blocks在他们自己的词法作用域,你一般定义blocks在他们能访问的一个函数或者方法内部。blocks可以移出他们原始的范围然后复制到堆上,这就是当你将他们提交到dispatch queue上发生的。所有这些语义使得使得少量代码就可以实现非常动态的任务成为可能。

Dispatch queues是GCD技术的一部分和C运行时的一部分。

Dispatch Sources
Dispatch Sources是基于C的机制处理指定类型的系统异步事件。一个dispatch source封装包括特定类型的系统事件的信息然后提交给一个指定的block对象或者函数给dispatch queue当事件发生时。你可以使用dispatch sources来监视以下系统事件
定时器(Timers)
信号处理(Signal handlers)
描述符相关的事件(Descriptor-related events)
进程相关事件(Process-related events)
端口事件(Mach port events)
你触发的用户事件(Custom events that you trigger)

Dispache sources是GCD技术的一部分。

Operation Queues
一个Operation Queue是一个Cocoa等价的并发dispatch queue,它是通过NSOperationQueue类来实现。
dispache queue执行任务是先进先出的,operation queue会考虑其他因素来决定任务的执行顺序。这些因素中的主要因素是给定任务依赖于其他任务的完成。你配置依赖关系当定义你的任务时,然后使用他们来创建负责的执行顺序。

你提交给operation queue的任务必须是NSOperation类的实例。一个operation 对象是一个Objective C的对象封装了你需要执行的工作和你执行时需要的数据。因为NSOperation类本质是一个抽象类,你需要定义自己的子类来执行你的任务。然而,Foundation 框架提供了一些具体的子类你能够创建和使用来执行任务。

Operation Object产生key-value observing KVO通知,这是一个有用的方法来监视你任务的完成情况。尽管operation queues 总是执行operations 并发的,需要时你可以使用依赖关系来确保他们串行的执行。

异步设计技术
在你开始重新设计你的代码来支持并发时,你需要问问你自己是否真的需要。并发能够提高你代码的响应能力来确保主线程空闲的响应用户事件。并发能提高你代码的执行效率,更多的核心执行更多的任务同一时间。然后,它也增加了代码复杂度难以编写和调试。

因为它增加了复杂度,并发不是一个你可以随意加给你的程序特性。正确的增加并发需要仔细的思考你程序执行的任务和执行这些任务需要的数据结构。正确的完成了以后,你可以发现你的代码比之前更加慢并且更少的响应用户了。因此,在开始你的设计时设定一些目标然后思考一些你需要采用的方法是值得的。

每一个应用有不同的需求和不同的需要执行的任务。一个文档不可能完整的告诉你如何设计你的应用和它的任务。然而,以下部分尝试提供一些引导来帮助你在设计过程中做出正确的选择。

定义你的应用程序的预期的行为
在思考给你的程序添加并发之前,你必须先定义你期待你的应用的正确的行为。明白你的应用的预期行为可以为你随后验证你设计提供方法。同时给你了一些主意关于你引入并发带来的预期的执行效果的收益。


什么时候使用线程
尽管operation queues和dispatch queues是执行并发任务的优先选择,但是它们不是万能药。依赖于你的应用,可能有一些任务你需要创建自定义的线程。如果你创建用户线程,你必须努力创建尽可能少的线程并且你必须只有在特殊的任务不能通过另外的方法来实现时使用这些线程。
线程仍然是一种实现实时运行代码的很好的方式。Dispatch queues让每个任务尽可能的快但是它们并不遵守实时运行的约束。如果你需要你的代码运行更加精准的行为,线程仍然是一个更好的选择。
使用线程编程,你必须谨慎的使用线程并且只有在绝对必须的时候才使用。

Operations Queues
Cocoa operations是一个面向对象的方式来封装你需要异步执行的任务。Operations设计的即可以联合一个operation queue使用也可以单独使用。因为它们是基于Objective C,operations是使用在iOS和OS X中基于Cocoa的应用程序中最常见的。

关于Operation Objects
一个operation对象是一个NSOperation类的实例,你用来封装你希望你的应用程序执行的任务。NSOperation类本身是一个抽象基类必须进行子类化来做一些有用的工作。尽管是抽象的,这个类提供了一些重要的基础概述来最小化你的子类需要做的工作。另外,基础框架提供了两个具体化的子类你可以使用。


所有的operation对象支持下面的关键特性:
1. 支持建立与其他operation对象的依赖关系。这些依赖关系阻止一个operation运行直到所有的它依赖的operations完成运行。
2. 支持一个可选的完成block,在operation的主任务完成后执行。
3. 支持通过KVO通知来监视你的operations的执行状态的变化
4. 支持优先级的operations并且因此影响它们的执行顺序
5. 支持退出语法来允许你停止一个正在运行的operation。

operations的设计可以帮助你提供你的应用的并发水平。Operations同样是一个组织和封装你的程序行为到简单的分离块的方式。替代运行一些代码在你程序的主线程,你可以将它们提交一个或者多个operation对象到一个queue,然后让相应的工作在一个或者多个分离的线程上异步的执行。

并发和非并发的Operations
尽管你通常执行operations是将它们添加到一个operation queue中,这并不是必须的。可以通过调用它的start方法来手动的执行一个operation对象,但是这样做不能保证这个operation与其他的代码并行运行。在start方法调用后,operation类的isConcurrent方法告诉你一个operation是同步运行还是异步运行的。这个方法默认是返回NO,意味着operation在它的调用线程中是同步运行的。

如果你希望实现一个并发的operation,它在调用线程中异步运行,你必须写一些额外的代码来启动operation异步的。比如,你可能分裂一个单独的线程,调用一个异步的系统函数,或者做一些确保start方法启动任务并且在任务完成之前立即返回,在所有的可能中。

大部分开发者不需要实现一个并发的operation对象。如果你通常将你的operations添加到一个operation queue中,你不需要实现并发operations。当你提交一个非并发的operation给一个operation queue时,queue本身会创建线程来运行你的operation。这样,添加一个非并发的operation到一个operation queue上仍然达到了异步执行你的operation对象的代码的效果了。定义并发的operations只有在必要的情况下才需要:在不添加它到一个operation queue,你需要异步地执行你的operation。

创建一个NSInvocationOperation对象
NSInvocationOperation类是NSOperation地一个具体地子类,运行时,调用你指定的对象和selector。使用这个类来避免为你程序中每个任务都定义大量的用户operation对象。特别是如果你修改一个已经存在的程序和已经存在的对象和方法需要执行必须的任务。当你希望调用的方法依赖环境能够变化时你可以使用它。比如,你可以使用一个invocation operation来执行一个基于用户输入动态选择的selector。

创建一个invocation operation的过程是直接的。你创建和初始化一个新的实例,传递需要的对象和selector来执行初始化方法。例子如下:
- (NSOperation *)taskWithData:(id)data
{
    NSInvocationOperation *invocationOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod:) object:data];
    return invocationOp;
}
- (void)myTaskMethod:(id)data
{
    NSString *inputString = data;
    //the actual work you want to do
}

创建一个NSBlockOperation对象
NSBlockOperation类是NSOperation的一个具体的子类,充当一个或者多个block对象的包装器。这个类为已经使用operation queue并且不希望创建dispatch queue的应用程序提供了一个面向对象的包装。你可以使用block operations来获取operation依赖性,KVO通知,和其他的一些特性你不能从dispatch queue获取。

当你创建一个block operation,在初始化时你通常添加至少一个block。你可以根据需要随后添加更多的block。当需要执行一个NSBlockOperation对象时,对象提交它所有的block到一个默认优先级,并发的dispatch queue。block对象等待所有block完成执行。当最后一个block完成执行,operation对象标记它自己为完成。这样,你可以使用一个block operation来跟踪一组执行的blocks,就像你使用一个线程join多个线程并且合并它们的结果。不同之处是block operation它自己运行在一个分离的线程上,在等待block operation完成时你的应用的其他线程可以继续执行工作。
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Beginning operation.\n");
        [self myTastInBlockOp];
    }];
完成block operation对象的创建后,你可以使用addExecutionBlock:方法来添加更多的block。如果你需要串行的执行block,你需要将它们提交到需要的dispatch queue。这里的意思就是不要使用NSBlockOperation,而是使用GCD?

定义一个自定义的Operation Object
如果block operation和invocation opetation对象都不能满足你的需求,你可以直接子类化NSOperation并且添加你需要的任何行为。NSOperation类提供了一个基本的子类化的线路。operation类提供了大量的基础来处理大部分的依赖于的KVO通知的工作。然而,当你需要支持已经存在的基础来而确保你的operations的行为正确。大量的额外的工作你需要做的依赖于你是实现一个并发和还是非并发的operation。

定义一个非并发的operation比一个并发的opertion简单多了。一个非并发地operation,所有你需要做地就是执行你的主要地任务并且正确响应退出事件;已经存在的类的基础为你所了所有其他的事。定义一个并发的operation,你必须用你自己的代码替换已经存在的基础。下面的部分为你展示如何实现这两种对象。

执行主任务
每个operation对象必须至少实现以下的方法:
1.一个用户自定义的初始化方法
2.main方法
你需要一个自定义的初始化方法将你的operation对象放入一个已知的状态并且用一个自定义的main方法来执行你的任务。你可以根据需要实现额外的方法,当然,就像如下:
1.自定义方法你打算从main方法中调用的。
2.accessor方法来设定和获取操作结果
3.NSCoding协议的方法来允许你序列化和反序列化opertion对象。

下面的代码展现了一个自定义的NSOperation子类的模版。
@interface MyNonConcurrentOperation : NSOperation
@property id (strong) myData;
-(id)initWithData:(id)data;
@end
@implementation MyNonConcurrentOperation
- (id)initWithData:(id)data {
    if (self = [super init])
        myData = data;
    return self;
}
-(void)main {
    @try {
        // Do some work on myData and report the results.
    }
    @catch(...) {
        // Do not rethrow exceptions.
    } }
@end
如何实现一个NSoperation子类的更详细的例子,请看NSOperationSample

响应退出事件
一个operation开始执行之后,它会继续执行它的任务直到它结束或者你的代码显示的退出operation。退出可能会在任何时间段发生,甚至在一个operation开始执行前。尽管NSOpertion类提供给客户端一个方法来退出一个operation,认识到退出事件必要的时候采用。当一个operation被立即的结束了,可能就没有方法来回收已经被分配的资源了。结果是,operation对象希望检查退出事件并且平静的退出当它们发生在operation的中间时。

为了在你的operaion对象中支持退出,所有你必须要做的是在你自定义的代码中间隔地调用对象的isCancelled方法,在isCancelled返回YES时立即返回。支持退出无论如何在你的operation都是很重要的,无论是你直接子类化NSOperation还是使用已经具体化的子类之一。isCancelled方法本身是非常轻量级的并且可以被频繁的被调用不会造成任何重要的性能影响。当设计你operation对象时,你必须考虑调用isCancelled方法在你代码的以下的几个地方:
1.立即调用在你执行任何实质性的工作前
2.在循环的每次迭代中至少调用一次,或者每次迭代时间很长的化多调用几次。
3.你代码的任何地方可能相对来说比较容易中止operation。

下面提供了一个简单的示例在operation对象的main方法中如何响应退出事件,isCancelled方法在每一次循环中都被调用,允许快速退出在任务开始和定期的重复时。
- (void)main {
    @try {
        BOOL isDone = NO;
        while (![self isCancelled] && !isDone) {
            // Do some work and set isDone to YES when finished
        } }
    @catch(...) {
        // Do not rethrow exceptions.
    }
}

尽管上面的示例没有包含清除代码,你自己的代码必须确保释放任务你自己申请的资源。

配置operation成为并发执行
operation对象执行默认是同步的,它们通过调用start方法在线程上执行自己的任务。因为operation queues为非并发的operations提供线程,所以,大部分operations仍然是以异步的方式运行。然而,如果你打算手动执行operations并且仍然希望它们异步运行,你必须采取一些合适的措施来确保这样。你可以通过定义你的operations对象成一个并发的operations。


本部分的剩余部分展示了一个示例实现MyOperation类,这个类阐述了实现一个并发的operation基础的代码。MyOperation类简单的在一个它自己创建的分离的线程上执行它自己的main方法。main方法执行的实际的工作是不相关的。这个例子的目的是阐述了基本的构建你需要提供来定义一个并发operation。
下面展示了MyOperation的接口和部分实现。isConcurrent,isExecuting,isFinished方法的实现相对来说很直接。isConcurrent方法必须返回YES来指明它是一个并发的operation。isExecuting和isFinished方法简单的返回存储在实例变量中的值。

@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@end

@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}
- (BOOL)isConcurrent {
    return YES;
}
- (BOOL)isExecuting {
    return executing;
}
- (BOOL)isFinished {
    return finished;
}
下面展示MyOperation类的start方法。这个方法的实现是最小化的仅仅为了阐明你必须执行的任务。在这样的情况下,这个方法简单的启动一个新线程并且配置它来调用main方法。这个方法同时更新executing成员变量并且为键路径为isExecuting产生KVO通知来反映这个值的改变。这些工作完成后,简单的返回,留下新分离的线程去执行实际的工作。
- (void)start {
    // Always check for cancellation before launching the task.
    if ([self isCancelled])
    {
        // Must move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return; }
    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    executing = YES;
    [self didChangeValueForKey:@"isExecuting"];
}

下面接着展示MyOperation剩下的部分。我们看见main方法是新线程的入口点。它执行operation对象关联的工作并且当工作完成后调用自定义的completeOperation方法。这个completeOperation方法这时为isExecuting和isFinished键路径产生需要的KVO通知来反应operation状态的改变。
- (void)main {
    @try {
        // Do the main work of the operation here.
        [self completeOperation];
    }
    @catch(...) {
        // Do not rethrow exceptions.
    } }
- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    executing = NO;
    finished = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

即使一个operation退出了,你必须总是通知KVO的观察者你的operation现在完成了它的工作。当一个operation对象依赖于其他的operation对象完成时,这个operation监视这些对象的isFinished键路径。没有成功的产生一个完成通知可能因此阻止你应用中的其他operations的执行。(Failing to generate a finish notification can therefore prevent the execution of other operations in your application.
 )
维持KVO遵从性(Compliance)
operation类为下面这些键路径KVO遵从:
isCancelled;
isConcurrent
isExecuting
isFinished
isReady
dependencies
queuePriority
completionBlock

如果你重写start方法或者为NSOperation对象做一些重要的自定义,你必须确保你自定义的对象为这些键路径仍然是KVO遵从性的。当重写start方法时,键路径中你最需要关注的是isExecuting和isFinished。在重新实现main方式时,它们是键路径中最容易受到影响的。

键路径(key path)
如果你希望实现支持依赖其他operation对象时,你可以重新isReady方法并且强制它返回NO直到你自定义的依赖被满足。(如果你实现自定义的依赖,如果你仍然支持NSOperation类提供的默认的依赖管理系统,确保在你的isReady方法中调用super)当你的operation对象准备就绪的状态改变时,为isReady键路径产生KVO通知来报告这些改变。除非你重些addDependency:或者removeDependency:方法,你不用担心为dependencied键路径产生KVO通知。

尽管你可以为NSOperation的其他key path产生KVO通知,你没有必要去做这些。如果你需要退出一个operation,你可以简单的调用已经存在的cancel方法来退出。类似的,你很少需要修改operation对象中的queue优先级说明。最后,除非你的operation能够动态的改变它的并发状态,你不需要为isConcurrent键路径提供KVO通知。

自定义operation对象的执行行为
operation对象的配置发生在你已经创建了它们之后但是在将它们添加到queue之前。本部分描述的配置的类别可以应用在所有的operation对象中,无论你使用已经存在的子类还是自定义NSOperation。

配置相互依赖
依赖你串行执行operation对象的是一种方式。一个operation依赖其他的operations不能开始执行直到所有它依赖的operations已经完成执行。因此,你可以使用依赖性来创建简单的两个operation对象间一对一的依赖关系或者构建复杂的对象依赖关系。

建立两个operation对象的依赖关系,你可以使用NSOperation的addDependency:方法。这个方法创建了一个单向的依赖从当前的operation对象到你用参数指定的目标operation对象。这个依赖意味着当前的对象不能开始执行直到目标对象完成执行。依赖关系在同一个queue中operations是没有被限制的。(Dependencies are also not limited to operations in the same queue.)operation对象管理它们自己的依赖关系并且它完美的接受在operations中创建依赖关系和添加它们到不同的queues中。有一件事件是不能接受的,就是在operations中创建依赖循环。这样做是一个程序错误组织operations运行。

当一个operation所有的依赖关系都已经完成执行,一个operation对象通常变成准备执行(如果你自定义了isReady方法,operation准备就绪就取决于你自己设定的条件)如果一个operation对象在queue中,这个queue可能开始执行这个operation在任何时间内。如果你打算执行operation手动的,取决于你调用operation的start方法。
重要:你必须总是在运行你的operations或者添加它们到一个operation queue之前配置依赖关系。依赖关系添加以后可能不会阻止一个给定的operation对象执行。

依赖关系依赖于每一个operation对象在对象状态更改时发送合适的KVO通知。如果你自定义自己的operation对象的行为,你可能需要从你自定义的代码中产生正确的KVO通知来避免产生依赖性的问题。

改变一个operation的执行优先级
把operation添加到一个queue中,执行顺序的首要决定因素是queue operation的准备就绪和它们相对的优先级。准备就绪取决于一个operation和其他operations的依赖关系,但是优先级是一个operation对象自身的属性。默认的,所有的新的operation对象有一个“normal”优先级,但是你可以添加或者降低这个优先级通过调用对象的setQueuePriority:方法。

优先级层面应用仅仅是operations在同一个operation queue中。如果你的应用程序有多个operation queues,每个它拥有的operations的优先级独立于其他的queues。因此,在不同的queue中,低优先级的operations可能比高优先级的operations先执行。

优先级层面不能代替依赖性。优先级决定一个operation queue开始执行的顺序只有在这些operations现在准备好执行。比如,如果一个queue包含一个高优先级和一个低优先级operation,并且两者都准备好执行,queue首先执行高优先级的operation。然而,如果高优先级的operation没有准备好但是低优先级的准备好了,queue首先执行低优先级的。如果你希望阻止一个operation开始执行直到另外一个operation完成执行,你必须使用依赖性。

更改基本的线程优先级

设置一个完成block
一个operation可以在它的主任务结束执行时执行一个完成block。你可以使用一个完成block做任何你认为不是主任务部分的工作。比如,你可能使用这个block通知感兴趣的客户端你的operation已经执行完了。一个并发的operation对象可能使用这个block来产生它最后的KVO通知。
使用NSOperation的setCompletionBlock:方法来设置一个完成block。你传递给这个方法的block必须是没有参数和没有返回值的。

为operation对象决定一个合适的范围
尽管可以添加一个任意大数量的operations到一个operation queue中,这样做通常是不实际的。就像其他的对象,NSOperation类的实例消耗内存并且它们的执行有真实的开销。如果每个你的operation对象处理少量的工作,你创建了数万个这样的operations,你可能发现你花费了太多的时间调度operations而不是做实际的工作。如果你的应用程序已经受到了内存约束,你可能发现数万的operation对象在内存中甚至降低你的执行效率。

高效的使用operations的关键是站到一个合适的平衡:在你需要做的工作的总和与保持处理器忙碌之间找到平衡。尝试确保你的operations处理适量的工作。比如,如果你的应用创建100个operation对象用100个不同的值来执行同样的任务,考虑每次创建10个operation对象来处理10个值。

你必须避免一次添加大量的operations到一个queue中,或者避免连续的添加operation对象到一个queue中的速度超过它们被处理的速度。一批批的创建这样的对象而不是用operation对象淹没queue。当一批完成了执行,使用一个完成block来告诉你的应用创建一个新的一批。当你有大量工作要做时,你希望保持queue充满足够的operations来让处理器保持忙碌,但是你又不希望一次创建过多的operations导致你的程序耗尽内存。

当然,你创建的operation对象的数量,你在每一个operation上执行的工作的总量,是易变的和完全的依赖于你的应用。你必须经常使用如instruments和Shark这样的工具来找到一个效率和速度之间合适的平衡。(performance overview)

执行operations
最后,你的应用需要执行operations来做关联的工作。在本部分,你可以学习几种方式来执行operations,你如何能在runtime时操作你的operations的执行。

添加operations到一个operation queue
目前为止,执行operations最简单的方式就是使用一个operation queue,是NSOperationQueue的一个实例。你的应用负责创建和保持任何它打算使用的operation queues。一个应用可以拥有任意数量的queues,但是在同一个时间点执行的operations有实际的限制。operation queue和系统限制并发的operation数量为一个值,这个值对于CPU的可用核心数和系统负载有关。因此,创建额外的queues并不意味着你可以执行额外的operations。

为了创建一个queue,你在你应用中为它像其他对象一样分配内存:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
添加operations到queue中,你可以使用addOperation:方法。在OS X v10.6和以后,你可以使用addOperations:waitUntilFinished:添加operations组,或者你可以使用addOperationWithBlock:方法添加block对象到queue中。每个方法都向队列中加了一个operation并且通知queue必须准备处理它们。在大部分情况下,operations在被添加到queue中不久后就会被执行,但是operation queue可能会因为一些原因延迟加入到队列的operations的执行。特别是,执行可能会被推迟如果加入队列的operation依赖于其他的operations并且还没有完成。执行可能会被推迟如果operation queue自己被暂停了或者它已经执行了它的最大数量的并发operations。如下实例展示了添加operations到一个queue中的基本的语法。
[aQueue addOperation:anOp]; // Add a single operation
[aQueue addOperations:anArrayOfOps waitUntilFinished:NO]; // Add multiple operations [aQueue addOperationWithBlock:^{
/* Do something. */
}];
重要:永远不要修改一个operation对象在它已经被添加到queue中以后。当在queue中等待时,operation可能在任何时候开始执行,所以改变它的依赖关系或者它包含的数据可能造成不利的影响。如果你希望直到operation的状态,你可以使用NSOperation类的方法来确定operation是否在运行,等待运行或者已经结束了。

尽管NSOperationQueue类是为并发执行operations而设计的,它可以强制一个queue同一时间只运行一个operation。setMaxConcurrentOperationCount:方法让你可以配置operation queue最大的并发operation数量。把1传递给这个函数会导致queue一次只执行一个operation,尽管一次只执行一个operation,执行的顺序依然是基于其他因素的,比如每个operation的准备就绪和它们被赋予的优先级。然而,一个串行的operation queue的行为和一个串行的GCD dispatch queue不完全一样。如果你的operation对象执行顺序对你很重要的话,你必须在添加到operation queue之前使用依赖关系来建立执行顺序。

手动执行operations
尽管operation queues是运行operation对象最方便的方式,也可以运行operations不通过queue。如果你选择手动运行operation,然而,你的代码中有一些需要主要的地方。特别是,operation必须准备好运行并且你使用start方法来启动operation。

operation不能运行直到它的isReady方法返回YES。isReady方法已经被NSOperation类集成到依赖管理系统来提供operation依赖关系的状态。只有当它的依赖关系都清除了operation才能自由的开始执行。

当手动执行一个operation时,你需要使用start方法来启动运行,你使用这个方法,而不是main或者其他方法,因为start方法在实际执行你的自定义代码前执行一些安全检查。更详细的,默认的start方法产生operations需要正确处理它的依赖关系的KVO通知。这个方法正确的避免执行已经退出的operation并且在operation没有真正准备好运行时抛出一个异常。

如果你的应用定义一个并发的operation对象,你必须考虑在启动它之前优先调用operation的isConcurrent方法。当这个方法返回NO时,你的局部的代码可以决定是在当前线程中同步的执行operation还是首先创建一个分离的线程。实现这种类型的检查完全取决于你。

下面的示例展示了这种类型的简单检查。如果返回NO,你可以安排一个定时器稍后调用这个方法。你可以保持这样的调度一直到这个方法返回YES,可能会发生因为operation已经退出了。
- (BOOL)performOperation:(NSOperation *)anOp
{
    BOOL ranIt = NO;
    if ([anOp isReady] && ![anOp isCancelled])
    {
        if (![anOp isConcurrent])
        {
            [anOp start];
        }
        else
            [NSThread detachNewThreadSelector:@selector(start) toTarget:anOp withObject:nil];
        ranIt = YES;
    }
    else if([anOp isCancelled])
    {
        [self willChangeValueForKey:@"isFinished"];
        [self willChangeValueForKey:@"isExecuting"];
        _executing = NO;
        _finished = YES;
        [self didChangeValueForKey:@"isExecuting"];
        [self didChangeValueForKey:@"isFinished"];
        ranIt = YES;
    }
    return ranIt;
}

退出operations
一旦加入到operation queues,operation对象实际就是属于queue了并且不能退出。退出queue的唯一方法就是cancel operation。你可以通过调用cancel方法退出一个单独的operation对象或者你可以调用cancelAllOperations方法从queue种退出所有的operation对象。

你只有在确信不再需要这个operation时才应该退出它。发出一个退出命令将operation对象置为退出状态,会阻止operation的执行。因为一个退出的operation仍然可以被认为是完成的,依赖它的对象会收到一个正确的KVO通知来清除依赖关系。因此,响应一些重大事件时更加常见的做法是退出所有加入到queue中的operations,比如应用程序的退出或者用户发出了退出请求,而不是选择性的退出operations。

等待Operations的完成
为了达到最好的性能,你应该尽可能的把你的operations设计成异步的,当operation在执行时释放你的应用做更多的工作。如果创建operation的代码同时处理operation的结果,你可以使用NSOperation的waitUtilFinished方法来阻塞代码直到operation完成。通常来说,最好避免调用这个方法如果你能帮助它的话。阻塞当前线程可能是一个方便的解决方案,但是它引入了更多的串行到你的代码中并且限制了并发的数量。
important:你应该永远不要在你应用的主线程中等待一个operation。你只应该在分离的线程中或者另一个operation中做。阻塞你的主线程阻止你的应用响应用户事件和导致你的应用出现不响应的情况。
除了等待一个单一的operation完成,你也可以使用NSOperationQueue的waitUtilAllOperationAreFinished方法来等待队列中所有的operations的完成。当等待一个完整的队列的完成时,注意你应用程序的其他线程仍然可以添加operations到队列中,因此可以延长等待。

暂停和恢复Queues
如果你希望暂时停止operations的执行,你可以使用setSuspended:方法暂停对应的operation queue。暂停一个queue不会造成已经在运行的operations停止在它们的任务的中间。它仅仅阻止新的operations被安排执行。你可能暂停一个queue来响应用户请求暂停所有要进行的工作,因为期望的是用户可能最终希望恢复这个工作。

Qt c++ llama.cpp 运行 LLaMA-3-8B 中文对话机器人(多模态输入) 助手: 阅读详情

相关推荐

Cocos Creator3.5.2使用Spine切换皮肤

3、然后获取当前spine中是否有皮肤数据,如果spine连皮肤数据都没有,接着写可能会error,这里的 skins[skin],是判断皮肤列表中是否有需要更换的皮肤。2、因为spine往往意味着异步加载,换肤时也经常出现频繁异步的情况,所以下一步我们要可以判断当前spine组件里是否已经加载好了spine文件。换肤前隐藏好需要换肤的节点,避免穿帮;官网文档的换肤流程太简化,对于实际使用来说会有各种各样的问题。1、首先获取对应的spine组件。这里的skin自然是外部传入的需要换肤的皮肤名。

tc654321的博客 1817

iOS—NSOperation、NSOperationQueue简单了解

文章目录NSOperation、NSOperationQueue为什么要使用NSOperation、NSOperationQueueNSOperation、NSOperationQueue操作和操作队列NSOperation、NSOperationQueue使用步骤NSOperation、NSOperation基本使用创建操作使用子类NSInvocationOperation使用子类NSBlockOperation NSOperation、NSOperationQueue NSOperation、NSOpe

qq_45836906的博客 1072

独立站如何采集商品?外贸独立站采集教程

从这里Robert了解到还有国内有很多仿牌卖家、微商,都有类似的情况,他们出海的阻碍主要在于支付、物流、推广。8.插件“抓取任务”列表手动点同步,将商品同步到主商城后台商品管理列表(商品默认同步到下架列表中) 后台商品管理下架列表显示已同步商品。5.店铺信息修改完成后,可以开始采集商品,采集时弹窗中会自动识别当前平台及商品链接,分类和相册名称可以自定义 编辑修改。4. 打开采集目标站商品页面,点开谷歌浏览器扩展程序列表,找到Shopyy采集平台,打开配置页。打开弹窗中的“店铺管理”,添加店铺信息。

Jernnifer_mao的博客 1378

使用NSOperation以及NSOperationQueue

使用NSOperation以及NSOperationQueue   NSOperation vs. Grand Central Dispatch (GCD) 在Mac OS X v10.6和iOS4之前,NSOperation 与 NSOperationQueue 不同于GCD,他们使用了完全不同的机制。 从Mac OS X v10.6和iOS4开始,NSOperation 和 NSO...

weixin_33910434的博客 230

Hyper-V虚拟机 安装 银河麒麟V10桌面版

如何在windows 11 Hyper-V 虚拟机中 安装 银河麒麟V10桌面版。

m0_46756112的博客 3418

数据仓库ODS层详解- 功能、设计与最佳实践

数据仓库ODS层是大数据分析的基石,为企业决策提供可靠数据源。本文深入探讨ODS层设计原则、实施要点和最佳实践,涵盖金融、零售等行业应用。重点关注云环境下ODS层部署策略,以及实时数据集成、数据湖技术等创新趋势。文章还分析了数据体量增长、实时性需求等挑战,提供实用解决方案。whether助您构建高效、安全、可扩展的ODS层,为数字化转型奠定坚实基础。#数据仓库 #ODS层 #大数据分析 #云计算 #数据湖

一个8年大数据开发工程师的碎碎念 1万+

iOS内存优化——OperationQueue悄悄给你挖的坑

这篇文章是对以前我们的APP做过的一次内存优化的介绍,场景是在大批量(数千至上万)的小文件(JSON、图片)数据下载的情况,使用NSOperationQueue和dispatch_global_queue的一些注意点,有类似大批量数据的上传或下载需求的童鞋可以参考一下。 一、基本概念 1.队列。 使用场景 在iOS的开发中,我们经常会需要处理一些耗时操作,比如网络请求、文件读写、数据库增

wentianen的博客 2574

015、架构_配置文件_管理节点(proxymanager.ini)

该配置文件中的参数在后台配置,两种生效方式:*所有参数重启生效。*部分参数可以使用工具dbtool动态生效。动态生效命令为:dbtool-pm-load-config。配置文件“proxymanager.ini”用于设置元数据管理模块的信息。文件路径为“~/etc”。配置文件“proxymanager.ini”是普通的文本文件。默认值proxymanager-alarmconfutf8.txt。配置错误或不配置,PM无法连接MDS,无法服务。获取元数据失败超时处理时间。异常时请求启动对等回滚。

Oracle/MySQL 到 国产信创数据库|迁移 · 调优 · 信创替换 329

关于NSOperationQueue,一个容易让初学者误解的问题

凡是学习NSOperationQueue的人,都会遇到setMaxConcurrentOperationCount这个函数。在网上的许多博文中,都将setMaxConcurrentOperationCount解释为“设置线程池中的线程数”,我觉得这是一种以讹传讹的说法,相当不准确,容易误导初学者,至少我曾经就被误导过。实际上,NSOperationQueue 可以认为是线程池...

weixin_30642869的博客 276

YOLOv11官方代码库与预训练模型下载:介绍Ultralytics官方GitHub及模型权重(yolov11n.pt, yolov11s.pt等)

本文介绍了Ultralytics官方GitHub仓库和YOLOv11预训练模型。Ultralytics仓库是YOLO系列算法的官方实现,包含YOLOv11最新代码、预训练模型、文档和工具。仓库采用清晰的代码结构和活跃的更新策略,支持全流程开发。YOLOv11提供了5种不同规模的模型变体(YOLOv11n/s/m/l/x),在精度、速度和模型大小间权衡,适用于从移动设备到高精度检测等不同场景。各模型在COCO数据集上预训练,可直接使用或微调,满足多样化应用需求。

本博客聚焦 YOLOv11 全流程落地,涵盖架构优化、数据集处理、训练技巧与多场景部署,兼及国产数据库、Java 开发与 AI 模型应用,内容兼顾理论与工程实践,为开发者提供系统干货,助力高效提升技术能力。 5826

从零开始利用树莓派+扬声器,实现简单的蓝牙音箱,手机连接放歌

树莓派4B自带蓝牙和Wifi, 无需外接 USB dongle;蓝牙最常见的应用是近距离传输数据,比如蓝牙传文件,蓝牙音箱等。正好家里有个普通的usb供电的便携音箱;本文用树莓派蓝牙+普通音箱,实现简单的蓝牙音箱。怎样理解这三层:可以类比Linux网络层:ALSA 类似网络驱动框架A2DP 类似TCP/UDP层PulseAudio 类似HTTP层的服务器,类比Apache而蓝牙连接类似http连接和会话;

南七小僧的学海无涯 2221

iOS多线程编程三:Operation和OperationQueue

概述 本文是多线程的第三篇文章,主要讲解Operation和OperationQueue。看资料的时候发现了一篇特别好的教程,随性就翻译一下,权当自己做个总结。本文主要内容翻译自raywenderlich的operation and operationqueue tutorial in swift。原文链接点这里。 不管是在Mac还是iO...

weixin_33806509的博客 371

可控异常的契约化设计:在 ABAP 中用 CX_STATIC_CHECK 把可预期错误变成接口的一部分

文章摘要: 本文探讨了ABAP系统中异常处理的工程化设计原则,强调将可预期的业务异常作为接口契约的一部分。核心观点是:对于调用方能够合理处理的错误情况,应使用继承自CX_STATIC_CHECK的受检异常,通过方法签名强制调用方处理或显式转发。文章对比了ABAP三大异常根类的适用场景,提出"三问法"判断异常是否适合受检处理,并给出了包含上下文信息的异常类实现示例。通过Fiori文件上传案例,展示了如何在分层架构中合理传递和转换异常,最终在OData服务层将业务异常映射为标准错误响应。这种

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 3063

iOS并发编程指南

iOS Concurrency Programming Guide iOS 和 Mac OS 传统的并发编程模型是线程,不过线程模型伸缩性不强,而且编写正确的线程代码也不容易。Mac OS 和 iOS 采取 asynchronous design approach 来解决并发的问题。 引入的异步技术有两个: Grand Central Dispatch:系统管理线程,你不需要编写线程代码。只

程序人生 6630

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

iOS并发编程--GCD、操作队列、线程

现在iOS的多线程方案主要有以下这几种: 1. GCD(Grand Central Dispatch):使用dispatch queue(分派队列)执行tasks(任务),苹果公司推荐使用; 2. NSOperationQueue和NSOperation:使用operation queue(操作队列)执行operations(操作),苹果公司推荐使用; 3. NSThread:苹果公司封装的基

学海无涯苦作舟 1008

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

iOS并发编程

iOS 和 Mac OS 传统的并发编程模型是线程,不过线程模型伸缩性不强,而且编写正确的线程代码也不容易。Mac OS 和 iOS 采取 asynchronous design approach 来解决并发的问题。 下面扣丁学堂为大家讲解一下常见的名词和易混淆的名词的含义。。   串行 vs. 并发   从本质上来说,串行和并发的主要区别在于允许同时执行的任务数量。串行,指的是一次只能

yinranhuakai3的博客 378

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

基本概念和Dispatch Queue

iOS的三种多线程技术                     1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØNSOperation/NSOperationQueue 面向对象的线程技术 ØGCD —— Grand Central Dispatch

plpldog的博客 562

并发编程之Operation Queue

随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至 四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势,就要采用并发编程,提高CPU的利用率。 iOS中并发编程中主要有2种方式Operation Queue和GCD(Grand Central Dispatch)。下面就来先来说一下Operation...

aqokgik84582的博客 150

iOS学习笔记09—iOS并发编程

iOS学习笔记09—iOS并发编程 一、概述 Mac OS 和 iOS采取"异步设计方式"来解决并发编程的问题。包括Grand Central Dispatch(GCD)和Operation Queue。 1、Grand Central Dispatch(GCD): GCD是基于C的执行自定义任务机制。系统管理线程,你不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的d

jjunjoe的专栏 5318

iOS并发编程指南(1)

全文分四个部分: 1: Operation Queues  2: Dispatch Queues  3: Dispatch Sources  4: Migrating Away from Threads  下面正式开始!  iOS Concurrency Programming Guide  iOS 和 Mac OS 传统的并发编程模型是线程,不过线程模型伸缩性不

zaitianaoxiang的专栏 2004

GCD介绍: 基本概念和Dispatch…

目录(?)[+]   iOS的三种多线程技术                     1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØNSOperation/NSOperationQueue 面向对象的线程技术 ØGCD —— Grand Cen

ji5ji的专栏 392

关于GCD NSOperation

多线程

Love@YR 375
上一篇: iOS KVO
下一篇: 关于文章的声明
MakeCodeClear
博客等级 码龄14年 0粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值