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âŠ
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.
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:
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!
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
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.
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.
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!
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).
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.
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.
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.