iOS Concurrency with GCD and Operations - Part 7: | Ray Wenderlich

Nope. Still getting an error on self.state = .finished occasionally.

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘*** -_referenceData64 only defined for abstract class. Define -[NSTemporaryObjectID_default _referenceData64]!’

I seems to be specifically complaining about the didChangeValue(forKey: state.keyPath) in state’s didSet.

Ok. I don’t think it’s an Operation issue. I think it’s in the completionBlock where I was trying to do some CoreData stuff.
Instead I made a BlockOperation that does what I need and made it have a dependency of the AsyncOperation and that seemed to clear it up.
So, probably most / all of my added code is pointless
 :frowning:

I wish I could actually watch this tutorial :frowning:

This and the next one wont run, just a loading icon forever


Hi I purchased a subscription (19.95/month) specifically for the GDC tutorials. However they are stuck

https://videos.raywenderlich.com/courses/55-ios-concurrency-with-gcd-and-operations/lessons/7
https://videos.raywenderlich.com/courses/55-ios-concurrency-with-gcd-and-operations/lessons/8

Never loads or runs. If you could address this or please refund my subscription.

hi, I’ll ping the web guys about this.

Hi @solarstorm50: I have our internal Engineering team looking into this for you. I’ll let you know as soon as we find a solution for you!

Hi, Part 7 and Part 8 are also not loading for me. It’s been over a day. Can you please provide us with an update ?

Hi everybody,

Sorry for the problems you’ve been experiencing with this course. This was due to a CDN issue with our provider. They’re working on a fix for it, which should be deployed by the end of the week.

In the meantime, I’ve just deployed a version of the site that should allow you to view videos again. It uses a fallback video player, which doesn’t have quite the same functionality that you’re used to, but it will at least allow you to watch videos again.

Once again, apologies for the the problems

Sam

AsyncOperation code didn’t work for me - completionBlocks were never called. This is what fixed it (iOS 11, Swift 4):

  class AsyncOperation: Operation {
  ...

   enum State: String
   {
        case ready = "Ready"
        case executing = "Executing"
        case finished = "Finished"
    
       fileprivate var keyPath: String {
           return "is" + rawValue
        }
   }

  ...
  }

odd, it still works for me, in Xcode 9.1

Hi @Audrey! Thank you for the great video course, I just love it.

Could you please help me to understand following situation with AsyncOperation:

In current implementation we have property state and it’s not thread save.
So set it .Finished state for example in completion handler of urlSession is a unsafe, right?.

Supposed solution:

  1. Wrap state read/write in serial private queue (guess should solve the issue).

@remark: What is about willChangeValue, didChangeValue, aren’t the supposed to be called in thread where operation was created/started?

Proximate thread save state prop implementation (please correct me if I wrong):

let isolationQueue = DispatchQueue(label: "\(Bundle.main.bundleIdentifier!).AsyncOperation", attributes: .concurrent )

public var _state: State = State.Ready {
    willSet {
        willChangeValue(forKey: newValue.keyPath)
        willChangeValue(forKey: _state.keyPath)
    }
    didSet {
        didChangeValue(forKey: oldValue.keyPath)
        didChangeValue(forKey: _state.keyPath)
    }
}

public var state: State {
    set {
        isolationQueue.sync(flags: .barrier, execute: {
            self._state = newValue
        })
    }
    get {
        var tmpState: State = .Finished
        isolationQueue.sync {
            tmpState = self._state
        }
        
        return tmpState
    }
}

Thanks in advance for any help! Any code snippet would be much appreciated!

1 Like

hi Dmitry: excellent question!

your isolationQueue code is very similar to the ThreadSafetyChallenge code in part 11 of this course, but I call isolationQueue.async when setting the value.

Curiously, TSAN doesn’t have any problem with AsyncOperation, as it’s written in TiltShiftDecompressed. But the main thread checker caught me accessing imageView from a non-main thread — in filter.completionBlock

for the KVO methods, I found this answer in SO: ios - Operation State Not Thread Safe Swift - Stack Overflow

it explains why you don’t call willChange and didChange inside the lock, as it would cause deadlock:

It’s important to note that the KVO notifications are NOT called from inside the lock. If they were, the app would deadlock, because in the middle of calling the didChangeValueForKey() method, the observers try to access properties like “isReady” or “isFinished”. Since those methods also acquire the lock, then we’d be stuck waiting on our own lock. It’s the classic definition of deadlock.

1 Like

Thank you for the good answer with necessary links. I’ve updated implementation accordingly.

Yeah, that’s really interesting, I didn’t found any issues with TSAN as well.

Curiously, TSAN doesn’t have any problem with AsyncOperation, as it’s written in TiltShiftDecompressed.

Yes, thank you for the note, isolationQueue.async might be better here. In my case I applied @jrg.developer solution from course “iOS Design Patterns”, challenge “Multicast Closure Delegate”.

your isolationQueue code is very similar to the ThreadSafetyChallenge code in part 11 of this course, but I call isolationQueue.async when setting the value.

Hi Audrey,

Thanks for the excellent tutorial. I found controversial answers on stackoverflow regarding when to use GCD and OperationQueue. What is your opinion? Is it good enough if I just master one of them? Thanks!

Hi @mike!

CGD use-case:

  • Filter array of data source items and then render update table view with the result.

Example code snippet:

DispatchQueue.global(qos: .userInitiated).async {
    let filteredDataSource = dataSource.filter { $0 % 2 == 0 }

    DispatchQueue.main.async {
        print("Reload table view with new datasource \(filteredDataSource)")
    }
}

NSOperation use-case:

Theory:

  • NSOperation is build on top of the DispatchWorkItem, so it’s high-level work item that has built-in state machine (started / executing / finished / cancelled).
  • NSOperationQueue is build on top of DispatchQueue and can control dispatched operations (e.g. cancelling all operations).

Real-life challenge:
You have to perform synchronization: download and parse the data to be available locally in DB on the mobile device.

  • Download and parse Ingredient categories
  • Download and parse Ingredients
  • Download and parse Dish categories
  • Download and parse Dishes

All of these operations should be performed in the strict order (as written).

So in this case you can create operations:

  • Download{Element}Operation // DownloadDishOperation, DownloadIngredientOperation 

  • Parse{Element}Operation //ParseDishOperation, ParseIngredientOperation 

    


Then you can group them to more high-level group operations like:

  • DownloadAndParse{Element}

For every sync you have to have internet connection, right? So you can create InternetReachabilityOperation that will be the first in your operation list.

So in our case I will have the operation list:

  • InternetReachabilityOperation (will start first)
  • DownloadAndParseElement1 (depends on InternetReachabilityOperation)
  • DownloadAndParseElement2 (depends on DownloadAndParseElement1)
  • DownloadAndParseElement3 (depends on DownloadAndParseElement2)
    


Let’s assume InternetReachabilityOperation reported an error (no internet connection). So all other operations won’t start until dependencies are resolved (finished successfully). Synced ended because of no internet connection, no further checks in code


Would you like to show user an alert how much data will be loaded so he has enough internet traffic? Create another SyncTraficUsageOperation and insert between InternetReachabilityOperation and DownloadAndParseElement1.

So this kinda logic is easy to support / fix when it’s incapsulated in operations. Once again: you don’t have to use operations if you see simple use-case with GCD. Make your logic as simple as possible, so other can easily understand it.

More about CGD:

More about advanced NSOperations:

Best
Dzmitry.

1 Like

Hi Dzmitry,

Thank you for your answer. So I will be focusing on GCD first and may put more time on OperationQueue later.

1 Like

Ms @audrey, TSAN also complains about setting the state sometimes. Seems the state is not thread-safe. Could you please update the code of your tutorial? @altairs99’s solution looks good.

Ooops, doesnt work for me. crashed in isolationQueue.sync: Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

Gave up, I decided using this AsyncOperation instead:

1 Like

Hi @crafttang. My posted snippet of code for AsyncOperation was just an idea how to make it thread-safe. Unfortunately it doesn’t work and causes bad access instruction error. Instead of isolationQueue you can use NSLock to create a critical section, like:

private var _state = State.initialized

private let stateLock = NSLock()

private var state: State {
    get {
        return stateLock.withCriticalScope {
            _state
        }
    }
    
    set(newState) {
        willChangeValue(forKey: "state")

        stateLock.withCriticalScope {
            guard _state != .finished else {
                return
            }
            
            _state = newState
        }
        
        didChangeValue(forKey: "state")
    }
}

If you’d like to learn more about the operations, please visit Advanced NSOperations topic (link in my answer to mike above). Over there you can find complete source code example with much more details.

Thanks a lot, @altairs99.