0

I'm creating my NSOperation subclass with an initializer containing a block:

let concurrentOperation = ABOConcurrentOperation {[weak weakOp = concurrentOperation] in
    ...
}

Unfortunately, this doesn't work as I always get the error message Variable used within its own initial value which makes sense to me, but... how can I achieve having the concurrentOperation as weak reference inside?

swalkner
  • 16,679
  • 31
  • 123
  • 210
  • http://stackoverflow.com/a/30523666/3141234 – Alexander Jul 07 '16 at 21:04
  • it seems as if I do get a retain cycle as the operation doesn't get released - see my comment on the answer of @AMomchilov – swalkner Jul 07 '16 at 21:14
  • In retrospect, the issue is that you're apparently trying to reference the operation in the closure. The way to resolve this is to have the operation, itself, pass a reference to itself as a parameter. See the revised discussion in my answer below. – Rob Jul 07 '16 at 21:53

2 Answers2

1

If you need a reference to the operation in the block, you should pass it as a parameter to the closure and then you don't need a weak reference. It will automatically resolve the reference when the closure finishes. For example, consider the following:

let queue = NSOperationQueue()

let concurrentOperation = ABOConcurrentOperation() { operation in
    print("\(NSDate()): starting operation")
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * Int64(NSEC_PER_SEC)), dispatch_get_main_queue()) {
        print("\(NSDate()): finishing operation")
        operation.completeOperation()
    }
}

queue.addOperation(concurrentOperation)

And, I defined that closure to be a standard closure:

private var block: ((AsynchronousOperation) -> ())?    // FYI, I use optional in case the caller accidentally introduces a strong reference cycle, I can resolve that when the operation completes.

If you have your subclass that prints something in deinit:

/// a subclass that will just confirm that `deinit` is called

class ABOConcurrentOperation: AsynchronousBlockOperation {
    deinit {
        print("deinit")
    }
}

You'll see that's what happens:

2016-07-07 21:20:54 +0000: starting operation
2016-07-07 21:21:01 +0000: finishing operation
deinit

For your reference, this is the sample AsynchronousOperation class used above:

/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
///   necessary and then ensuring that `completeOperation()` is called; or
///   override `cancel` method, calling `super.cancel()` and then cleaning-up
///   and ensuring `completeOperation()` is called.

public class AsynchronousOperation : NSOperation {

    override public var asynchronous: Bool { return true }

    private let stateLock = NSLock()

    private var _executing: Bool = false
    override private(set) public var executing: Bool {
        get {
            return stateLock.withCriticalScope { _executing }
        }
        set {
            willChangeValueForKey("isExecuting")
            stateLock.withCriticalScope { _executing = newValue }
            didChangeValueForKey("isExecuting")
        }
    }

    private var _finished: Bool = false
    override private(set) public var finished: Bool {
        get {
            return stateLock.withCriticalScope { _finished }
        }
        set {
            willChangeValueForKey("isFinished")
            stateLock.withCriticalScope { _finished = newValue }
            didChangeValueForKey("isFinished")
        }
    }

    /// Complete the operation
    ///
    /// This will result in the appropriate KVN of isFinished and isExecuting

    public func completeOperation() {
        if executing {
            executing = false
        }

        if !finished {
            finished = true
        }
    }

    override public func start() {
        if cancelled {
            finished = true
            return
        }

        executing = true

        main()
    }

    override public func main() {
        fatalError("subclasses must override `main`")
    }
}

/// Asynchronous Operation base class
///
/// This class lets you perform asynchronous block operation. Make sure that the
/// the provided `block` calls `completeOperation`, or else this operation will 
/// never finish.

public class AsynchronousBlockOperation : AsynchronousOperation {

    private var block: ((AsynchronousOperation) -> ())?

    init(block: (AsynchronousOperation) -> ()) {
        self.block = block
        super.init()
    }

    override public func main() {
        block?(self)
    }

    override public func completeOperation() {
        block = nil

        super.completeOperation()
    }

}

extension NSLock {

    /// Perform closure within lock.
    ///
    /// An extension to `NSLock` to simplify executing critical code.
    ///
    /// - parameter block: The closure to be performed.

    func withCriticalScope<T>(@noescape block: Void -> T) -> T {
        lock()
        let value = block()
        unlock()
        return value
    }
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
0

The code (as you've written it) brings up a "chicken or the egg" scenario. Try this:

var concurrentOperation: ((foo) -> bar)! //insert correct type annocation here

concurrentOperation = ABOConcurrentOperation {
    //use concurrentOperation here
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • but then the operation doesn't get released if I just use `concurrentOperation` there... – swalkner Jul 07 '16 at 21:13
  • @swalkner Not necessarily. If the closure ends without calling itself again, ARC should clean up the reference. Do test it, however. – Alexander Jul 07 '16 at 21:32