Default value of maxConcurrentOperationCount for NSOperationQueue
From the documentation,
The maximum number of concurrent operations set explicitly on the
receiver using the setMaxConcurrentOperationCount: method. If no value
has been explicitly set, this method returns
NSOperationQueueDefaultMaxConcurrentOperationCount by default.
So it is NSOperationQueueDefaultMaxConcurrentOperationCount
. If this is set, it will choose an appropriate value based on the number of available processors and other relevant factors.
This is how it is defined:
enum {
NSOperationQueueDefaultMaxConcurrentOperationCount = -1
};
NSOperationQueueDefaultMaxConcurrentOperationCount: The
default maximum number of operations is determined dynamically by the
NSOperationQueue object based on current system conditions.
NSOperation and NSOperationQueue with maxConcurrentOperationCount = 1
Based on how I read Apple's documentation, the concurrent
property in NSOperation is readonly, and tells us if the operation will run asynchronously or not. If you plan to start operations manually, you need to make the your NSOperation return YES for asynchronous
in order to avoid blocking the thread you are starting your operations from. The concurrent
property is just used to monitor the state of the operation if you are running them manually
If you are adding your NSOperation
to an NSOperationQueue
, the queue will ignore the value of the asynchronous
property, and run operations according to the maxConcurrentOperationCount
.
So, to answer your question: If you run all your operations manually, and set asynchronous
to YES
, the number of operations running in parallel will depend on how big the delay is between each time you call start
on your operations, and how long it will take to finish them. If you add them to a queue, your queue will run the operations one by one, as a serial queue.
NSOperationQueue: a sequence of NSOperation's with dependencies VS (maxConcurrentOperationCount == 1)?
To answer your questions:
- It's safe to combine this particular sequence of operations with the dependencies you have given with
maxConcurrentOperations = 1
. - The queue will run
op2
,op3
andop1
orop2
,op1
,op3
if you reverse the dependency order ofop1
andop2
.
Theres nothing tricky in the dependency chain you've specified and NSOperationQueue
can take care of things automatically. You can only really get into trouble if you specify a circular dependency (e.g op3
depends on op1
), or you have an operation that isn't added to the queue, and so can't get executed to satisfy a dependency.
Apple has this to say about cancellation in the NSOperationQueue class reference:
Canceling an operation causes the operation to ignore any dependencies it may have. This behavior makes it possible for the queue to execute the operation’s start method as soon as possible. The start method, in turn, moves the operation to the finished state so that it can be removed from the queue.
All NSOperation subclasses should handle cancellation correctly by first checking to see if it has been cancelled and then immediately finish the operation without performing any actions. If this isn't done then it's a bug, and operations may execute even though they have been cancelled.
(Interestingly this also applies for NSBlockOperation, which I didn't realise. You explicitly need to check self.isCancelled
in the block).
I used CodeRunner on the App Store to try this all out and modified your program slightly. It's reproduced below.
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1"); }];
NSOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op2"); }];
NSOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op3"); }];
[op3 addDependency:op2];
[op2 addDependency:op1];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperations:@[op1, op2, op3] waitUntilFinished:YES];
}
}
For a NSBlockOperation
to refer to itself you need to do this, which is a bit disgusting but looks better in a NSOperation
subclass as you can refer to self
.
__block NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"op1 cancelled=%d", op1.cancelled); }];
Setting maxConcurrentOperationCount in swift
If you check the header file for for NSOperationQueue
:
var maxConcurrentOperationCount: Int
you'll see that maxConcurrentOperationCount
is a property, not a method, so you'll need to use:
self.operationQueue = NSOperationQueue()
self.operationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount
OperationQueue with custom `maxConcurrentOperationCount` does not pick up / execute all operations in the queue, after finishing first operation
The issue is that the methods are not KVC/KVO compliant. As the Operation
documentation says:
The
NSOperation
class is key-value coding (KVC) and key-value observing (KVO) compliant for several of its properties.…
If you provide custom implementations for any of the preceding properties, your implementations must maintain KVC and KVO compliance.
Constraints on the degree of concurrency (e.g., both maxConcurrentOperationCount
and addDependency(_:)
) rely upon KVO to know when the prior operation is complete. If you fail to perform the required KVO notifications, the queue will not know when subsequent operations may proceed.
See the latter part of Trying to Understand Asynchronous Operation Subclass for an example implementation.
FWIW, here is an asynchronous operation implementation:
public class AsynchronousOperation: Operation {
@Atomic @objc private dynamic var state: OperationState = .ready
// MARK: - Various `Operation` properties
open override var isReady: Bool { state == .ready && super.isReady }
public final override var isExecuting: Bool { state == .executing }
public final override var isFinished: Bool { state == .finished }
public final override var isAsynchronous: Bool { true }
// KVO for dependent properties
open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
if [#keyPath(isReady), #keyPath(isFinished), #keyPath(isExecuting)].contains(key) {
return [#keyPath(state)]
}
return super.keyPathsForValuesAffectingValue(forKey: key)
}
// Start
public final override func start() {
if isCancelled {
state = .finished
return
}
state = .executing
main()
}
/// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception.
open override func main() {
fatalError("Subclasses must implement `main`.")
}
/// Call this function to finish an operation that is currently executing
public final func finish() {
if !isFinished { state = .finished }
}
}
private extension AsynchronousOperation {
/// State for this operation.
@objc enum OperationState: Int {
case ready
case executing
case finished
}
}
With the following:
@propertyWrapper
public class Atomic<T> {
private var _wrappedValue: T
private var lock = NSLock()
public var wrappedValue: T {
get { lock.synchronized { _wrappedValue } }
set { lock.synchronized { _wrappedValue = newValue } }
}
public init(wrappedValue: T) {
_wrappedValue = wrappedValue
}
}
extension NSLocking {
func synchronized<T>(block: () throws -> T) rethrows -> T {
lock()
defer { unlock() }
return try block()
}
}
With the above, I abstract the asynchronous Operation
code into something I can subclass and inherit the asynchronous behaviors. E.g., here is an operation that performs the same asyncAfter
as your example (but with some extra OSLog
signposts so I can visually see the operations in Instruments):
import os.log
private let log = OSLog(subsystem: "Op", category: .pointsOfInterest)
class MyOperation: AsynchronousOperation {
var value: Int
init(value: Int) {
self.value = value
super.init()
}
override func main() {
let id = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "Operation", signpostID: id, "%d", value)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
finish()
os_signpost(.end, log: log, name: "Operation", signpostID: id, "%d", value)
}
}
}
Then ...
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
for i in 0..<5 {
queue.addOperation(MyOperation(value: i))
}
... yields a timeline of the operations like so:
Related Topics
Why Must a Protocol Operator Be Implemented as a Global Function
How to Group Array of Objects by Date in Swift
iOS Swift 3:Convert "Yyyy-Mm-Dd'T'Hh:Mm:Ssz" Format String to Date Object
The 'Pods' Target Has Transitive Dependencies That Include Static Binaries' When Installing Gcm
Gamecenter iOS 9 Gamecenter Gklocalplayerlistener Methods Not Called
iOS 9 Cloudkit: Query Does Not Return Anything While Connected to Cellular Network
Afnetworking 2.2.0 Upload Image on Server Issues
<Googlemaps/Googlemaps.H> File Not Found Google Maps Sdk for iOS
Using iOS 6 Theme for iOS 7 App
Codesign Returned Errsecinternalcomponent in High Sierra
How to Permanently Allow Usage of Camera on Trusted Websites with iOS - Safari
How to Make a Mkannotationview Touch Sensitive
Universal Link Broken in iOS 11.2
iOS Simulator Is Not Launching
How Get the List of Paired Bluetooth Devices in Swift
Warning in Custom Map Annotations Iphone
How to Get Vcf Data with Contact Images Using Cncontactvcardserialization Datawithcontacts: Method