Guys at the session did great talk about NSOperation and I recommend to watch
it. I wasn’t familiar with subclassing NSOperation class - I used the class
sometimes, but it was NSBlockOperation for scheduling few blocks to run one
after another in some order using NSOperationQueue. During the session I was
thinking: Hey, why didn’t I even subclass NSOperation in my code and why didn’t
I create any task using NSOperation? So, I played with the Quotes
today and created few tasks based on NSOperation.
The idea was similar to idea of presenters - there is some backend which app
communicates with, and the app fetch data from it, parse it and updates UI.
Simple. I browsed sample code and it was a bit too complex for what I needed
so I wrote it completely from nothing but you can see some similarity.
I decided to integrate app with parse.com because I haven’t opportunity
to use it before. There is a database that contains Quote objects and all of
them can be downloaded by sending GET request to /classes/Quote endpoint.
Here is the flow of the long running task - user is entering main view, fetch
request is added to queue with QoS set to User Initiated so task are pretty
important for the system. Data is received, and next dependent task is running,
that is, parsing. Data is parsed, changes are saved into persistent store and
table is ready to be reloaded to present new data.
NSOperation, GCD and QoS
NSOperation and NSOperationQueue were in the passed different mechanisms
than Grand Central Dispatch but with iOS 4 it have been rewrote on top of GCD.
The GCD is good for concurrent execution of a code that don’t need to be
scheduled. There is no ready mechanism for cancelling or suspending blocks
that are in progress.
NSOperation is higher level mechanism that gives ability to schedule, cancel
and suspend. It also allows to add dependency among operations. So, If you want
to do some longer running operations like downloading and parsing data, this is
probably mechanism you want to use.
NSOperationQueue can perform one or more concurrent operations at the same
time. It is specified by maxConcurrentOperationCount property. It also has qualityOfService property that indicates to the system the nature and importance
of work. Downloading and parsing is initiated by the user when entering the app
and user wants to see results as soon as possible so UserInitiated QoS is good
choice. In Foundation > NSObjCRuntime you can find description of all of them.
As mentioned before the long running operation of updating content of the app
has been decoupled to three small tasks: download data, parse data, notify about
end of the long running operation. To implement this we need to create DownloadOperation, ParseQoutesOperation and GetAllQuotesOperation which contains these operations - also, the last operation will be added to the queue in QuoteListViewController which displays all quotes.
DownloadOperation is small and easy to be reused with any request that end up
with some downloaded JSON. It takes request, creates connection, gets results
data and tries to convert it to dictionary. Because there is asynchronous work
to do it uses semaphore to not finish itself too fast.
At the end of its work the download operation calls delegate method. This is
needed to pass data between two operations. Next operation that takes this data
is ParseQuotesOperation. It also takes NSManagedObjectContext to work with
Quote objects. This operation also perform some asynchronous work like saving
contexts so it also needs additional semaphore to work correctly.
The last thing to do is configuration of NSOperationQueue in view controller
and implementation of method that starts downloading process.
I like separation of tasks and dependency among operations. Tasks can be reused easily in the future in other places. Because all tasks are operations the view controller needed only few lines to support downloading additional data.
Update 13 Jul 2015 17:15
I played with these described operations and changed it a bit. DownloadOperation changed to NetworkOperation which can send POST/GET or whatever request you
want instead of being able only to download data. Start parsing requires
passing downloaded response so I decided to parse it outside of the
NetworkOperation - it could be done better I think, but let’s keep it as is
for now - will update it when better/cleaner solution comes up. I’ve also
updated Quotes project and added UpdateReadCountOperation which
increments counter of readings selected quote.