How to write a proper singleton BOOL function - ios

I am using ASIHTTPRequest to check if user logged in, and try to return a boolean value when login successfull
Problem: When I request the boolean it always returns 0 then few seconds later ASIHTTPRequest finishes its request and update the boolean value.
What I want: get the boolean value after all requests finishes. I think proper way would be write a boolean function and retrieve the result of asihhtp requests?
in singleton:
#interface CloudConnection : NSObject
{
BOOL isUserLoggedIN;
}
#property BOOL isUserLoggedIN;
+ (CloudConnection *)sharedInstance;
#end
+ (CloudConnection *)sharedInstance
{
static CloudConnection *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[CloudConnection alloc] init];
// Do any other initialisation stuff here
});
return sharedInstance;
}
- (id)init {
if (self = [super init]) {
//send login request
[self sendLoginRequest];
}
return self;
}
-(void) sendLoginRequest{ .....}
- (void)requestFinished:(ASIHTTPRequest *)request
{ else if (request.responseStatusCode == 202) {
//parse json data
NSLog(#"Login Succesfull");
_isUserLoggedIN=YES;
}
}
- (void)requestFailed:(ASIHTTPRequest *)request{}
In a VC:
CloudConnection *sharedInstance=[CloudConnection sharedInstance];
NSLog(#"is logged in init %hhd",sharedInstance.isUserLoggedIN);
[self performSelector:#selector( checkLoginAfterFiveSeconds) withObject:nil afterDelay:5.0];
-(void) checkLoginAfterFiveSeconds
{
CloudConnection *sharedInstance=[CloudConnection sharedInstance];
NSLog(#"is logged in init %hhd",sharedInstance.isUserLoggedIN);
}
NSLOG:
is logged in init 0
Login Succesfull`
is logged in init 1 //after 5 secs

well if you do what you propose, its gonna block the calling thread. and you never want a thread waiting for network traffic especially not the main / ui thread
make it a void function and make it call a completionHandler or ... send a NSNotification once the result can be calculated directly! :)

Yes you are right :)
In your request completion block call this method:
[self loginResult:result];
-(void)loginResult:(BOOL)result
{
if(result == TRUE)
{
NSLog(#"Login successfully now call any method or do what ever you want");
}
else
{
NSLog(#"Login unsuccessfull");
}
}

Related

Resetting NSOperation

My network requests need to happen in a FIFO serial fashion. If one fails due to a network issue (i.e. offline, timeout) I need the failed request to retry before continuing the queue. How can I accomplish this?
When the operation fails, I set executing and finished both to - assuming that would work as a 'reset' for the NSOperation. But the queue never resumes.
Is there something I'm missing?
You could build a network operation that performs retries, and only sets isFinished when the network completion block is called, and it determines that no retry is needed:
class NetworkOperationWithRetry: AsynchronousOperation {
var session: NSURLSession
var request: NSURLRequest
var task: NSURLSessionTask?
var networkCompletionHandler: ((NSData?, NSURLResponse?, NSError?) -> ())?
init(session: NSURLSession = NSURLSession.sharedSession(), request: NSURLRequest, networkCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> ()) {
self.session = session
self.request = request
self.networkCompletionHandler = networkCompletionHandler
}
override func main() {
attemptRequest()
}
func attemptRequest() {
print("attempting \(request.URL!.lastPathComponent)")
task = session.dataTaskWithRequest(request) { data, response, error in
if error?.domain == NSURLErrorDomain && error?.code == NSURLErrorNotConnectedToInternet {
print("will retry \(self.request.URL!.lastPathComponent)")
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * Int64(NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
self.attemptRequest()
}
return
}
print("finished \(self.request.URL!.lastPathComponent)")
self.networkCompletionHandler?(data, response, error)
self.networkCompletionHandler = nil
self.completeOperation()
}
task?.resume()
}
override func cancel() {
task?.cancel()
super.cancel()
}
}
And you could call it like so:
let queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1 // I wouldn't generally do this, but just to illustrate that it's honoring operation queue dependencies/concurrency settings
let requests = urlStrings.map { NSURLRequest(URL: NSURL(string: $0)!) }
requests.forEach { request in
queue.addOperation(NetworkOperationWithRetry(request: request) { data, response, error in
// do something with the `data`, `response`, and `error`
})
}
Now, that's only retrying on NSURLErrorNotConnectedToInternet, but you could change that logic to be whatever you want. Likewise, I'd probably be inclined to add some "max retries" logic. Also, if the lack of Internet connectivity is really the issue, rather than retrying until Internet connectivity is achieved, I'd probably be inclined to set the operation's isReady notification tied into Reachability.
By the way, the above uses the following AsynchronousOperation:
/// 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`")
}
}
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
}
}
I used this custom class to manage NSOperation. I fiddled a bit with the code from GCDWebServer and came out with this stub. Most of it is self explanatory.
#interface SROperation : NSOperation <NSCopying>
///-------------------------------
/// #name error reporting
///-------------------------------
/**
The error, if any, that occurred in the lifecycle of the request.
*/
#property (nonatomic, strong) NSError *error;
///-----------------------------------------
/// #name the operation properties
///-----------------------------------------
/**
a dictionary.
*/
#property (nonatomic, strong) NSDictionary*aDictionary;
///----------------------------------
/// #name Pausing / Resuming Requests
///----------------------------------
+(instancetype)operationWithDictionary:(NSDictionary*)dict;
/**
Pauses the execution of the request operation.
A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect.
*/
- (void)pause;
/**
Whether the request operation is currently paused.
#return `YES` if the operation is currently paused, otherwise `NO`.
*/
#property (NS_NONATOMIC_IOSONLY, getter=isPaused, readonly) BOOL paused;
/**
Resumes the execution of the paused request operation.
Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request.
*/
- (void)resume;
#end
SROperation.m
#import "SROperation.h"
typedef NS_ENUM(NSInteger, SROperationState) {
SROperationPausedState = -1,
SROperationReadyState = 1,
SROperationExecutingState = 2,
SROperationFinishedState = 3,
};
static NSString * const kSROperationLockName = #"your.application.bundle.id.operationlock"
static inline BOOL SRStateTransitionIsValid(SROperationState fromState, SROperationState toState, BOOL isCancelled) {
switch (fromState) {
case SROperationReadyState:
switch (toState) {
case SROperationPausedState:
case SROperationExecutingState:
return YES;
case SROperationFinishedState:
return isCancelled;
default:
return NO;
}
case SROperationExecutingState:
switch (toState) {
case SROperationPausedState:
case SROperationFinishedState:
return YES;
default:
return NO;
}
case SROperationFinishedState:
return NO;
case SROperationPausedState:
return toState == SROperationReadyState;
default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
switch (toState) {
case SROperationPausedState:
case SROperationReadyState:
case SROperationExecutingState:
case SROperationFinishedState:
return YES;
default:
return NO;
}
}
#pragma clang diagnostic pop
}
}
static inline NSString * SRKeyPathFromSROperationState(SROperationState state) {
switch (state) {
case SROperationReadyState:
return #"isReady";
case SROperationExecutingState:
return #"isExecuting";
case SROperationFinishedState:
return #"isFinished";
case SROperationPausedState:
return #"isPaused";
default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
return #"state";
#pragma clang diagnostic pop
}
}
}
#interface SROperation ()
#property (readwrite, nonatomic, assign) SROperationState state;
#property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
#end
#implementation SROperation
+(instancetype)operationWithDictionary:(NSDictionary*)dict {
return [[self alloc] initWithDictionary:dict];
}
-(instancetype)initWithDictionary:(NSDictionary*)aDictionary {
self = [self init];
if (self)
{
self.aDictionary = aDictionary;
self.queuePriority = NSOperationQueuePriorityVeryHigh;
}
return self;
}
-(instancetype)init
{
self = [super init];
if(self) {
_state = SROperationReadyState;
_lock = [[NSRecursiveLock alloc] init];
_lock.name = kSROperationLockName;
}
return self;
}
#pragma mark -
- (void)setState:(SROperationState)state {
if (!SRStateTransitionIsValid(self.state, state, [self isCancelled])) {
return;
}
[self.lock lock];
NSString *oldStateKey = SRKeyPathFromSROperationState(self.state);
NSString *newStateKey = SRKeyPathFromSROperationState(state);
[self willChangeValueForKey:newStateKey];
[self willChangeValueForKey:oldStateKey];
_state = state;
[self didChangeValueForKey:oldStateKey];
[self didChangeValueForKey:newStateKey];
[self.lock unlock];
}
- (void)pause {
if ([self isPaused] || [self isFinished] || [self isCancelled]) {
return;
}
[self.lock lock];
if ([self isExecuting]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:SROperationDidFinishNotification object:self userInfo:nil];
});
}
self.state = SROperationPausedState;
[self.lock unlock];
}
-(void)operationDidPause
{
}
- (BOOL)isPaused {
return self.state == SROperationPausedState;
}
- (void)resume {
if (![self isPaused]) {
return;
}
[self.lock lock];
self.state = SROperationReadyState;
[self start];
[self.lock unlock];
}
- (BOOL)isReady {
return self.state == SROperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
return self.state == SROperationExecutingState;
}
- (BOOL)isFinished {
return self.state == SROperationFinishedState;
}
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self cancelConnection];
} else if ([self isReady]) {
self.state = SROperationExecutingState;
[self operationDidStart];
}
[self.lock unlock];
}
- (void)operationDidStart {
[self.lock lock];
if (![self isCancelled]) {
// YOUR ACTUAL OPERATION CODE
// finish
self.state = SROperationFinishedState;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SROperationDidFinishNotification object:nil];
});
}
[self.lock unlock];
}
- (void)cancel {
[self.lock lock];
if (![self isFinished] && ![self isCancelled]) {
[super cancel];
if ([self isExecuting]) {
}
}
[self.lock unlock];
}
#pragma mark - NSObject
- (NSString *)description {
[self.lock lock];
NSString *description = [NSString stringWithFormat:#"<%#: %p, state: %#, cancelled: %#>", NSStringFromClass([self class]), self, SRKeyPathFromSROperationState(self.state), ([self isCancelled] ? #"YES" : #"NO")];
[self.lock unlock];
return description;
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
SROperation *operation = [(SROperation *)[[self class] allocWithZone:zone] init];
// copy more properties
return operation;
}
}

send a second request after the first is completed

I have a method (requestData) that can be called several times in my ViewController but the first time the ViewController is loaded (in ViewDidLoad method) I need to call it two times BUT the second request should be sent only after the first request has completed:
- (void)viewDidLoad {
[super viewDidLoad];
dataForPlot = 1;
[self requestData: dataForPlot];
dataForPlot = 2;
[self requestData: dataForPlot];
}
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
}
else if (forPlot == 2) {
...
}
}
}
I know I probably need to use blocks but, even if I've tried to read some tutorials, I don't know how.
Anyone could help me ?
Thanks, Corrado
Here is what I've implemented following Duncan suggestion:
typedef void(^myCompletion)(BOOL);
- (void)viewDidLoad {
[super viewDidLoad];
[self requestData:^(BOOL finished) { // first request
if(finished) {
NSLog(#"send second request");
[self requestData: ^(BOOL finished) {}]; // second request
}
}];
- (void) requestData: (myCompletion) compblock {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
...
NSLog(#"request completed");
compblock(YES);
}
}
Don't call the second request until the first completes:
- (void) requestData: (int) forPlot {
...
[urlRequest startWithCompletion:^(URLRequest *request, NSData *data, NSError *error, BOOL success) {
if (success) {
if (forPlot == 1) {
...
dataForPlot = 2;
[self requestData: dataForPlot];
}
else if (forPlot == 2) {
...
}
}
}
Refactor your requestData method to take a completion block.
In your requestData method, When the url request completes, invoke the completion block.
In viewDidLoad, use a completion block that calls requestData a second time, this time with an empty completion block.
In your other calls to the requestData method, pass in a nil completion block (or whatever other action you need to trigger when the request finishes)

OCMock unit test error

I use OCMock to test out singleton methods. I get "no such method exists in the mocked class." error for testSingleton method and infinite loop (the screenshot, the spinning indicator) for testSingletonWithBlock method
EDIT:
download sample project here
https://drive.google.com/file/d/0B-iP0P7UfFj0LVFpWWpPb3RDZFU/edit?usp=sharing
Here is my implementation
manager:
#implementation Manager
+ (Manager *)sharedManager {
static Manager *instance;
dispatch_once_t predicate;
dispatch_once(&predicate, ^{
instance = [Manager new];
});
return instance;
}
- (int)getOne {
return 1;
}
- (void)success:(BOOL)success completion:(void(^)(void))completion failure:(void(^)(void))failure {
success ? completion() : failure();
}
view controller:
- (void)manager_printOne {
int num = [[Manager sharedManager] getOne];
NSLog(#"number is: %d", num);
}
- (void)manager_success:(BOOL)success completion:(void(^)(void))completion failure:(void(^)(void))failure {
[[Manager sharedManager] success:success completion:completion failure:failure];
}
test view controller:
#interface coreDataTestTests : XCTestCase
#property (nonatomic, strong) id mockManager;
#property (nonatomic, strong) ViewController *viewController;
#end
#implementation coreDataTestTests
- (void)setUp
{
[super setUp];
self.viewController = [ViewController new];
//for singleton
self.mockManager = [Manager createNiceMockManager];
}
- (void)tearDown
{
[super tearDown];
self.viewController = nil;
//Note: singleton need both, retain counts = 2
self.mockManager = nil;
[Manager releaseInstance];
}
- (void)testSingleton {
NSLog(#"testSingleton");
OCMStub([self.mockManager getOne]).andReturn(2);
[self.viewController manager_printOne];
}
- (void)testSingletonWithBlock {
NSLog(#"testSingletonWithBlock");
[[[[self.mockHelper stub] ignoringNonObjectArgs] andDo:^(NSInvocation *invocation) {
void(^block)(void);
[invocation getArgument:&block atIndex:3];
block();
}] success:0 completion:[OCMArg any] failure:[OCMArg any]];
[self.viewController manager_success:NO completion:^{
NSLog(#"completion");
} failure:^{
NSLog(#"failure");
}];
}
#end
manager category for unit test:
static Manager *mockManager = nil;
#implementation Manager
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
+ (Manager *)sharedManager {
if (mockManager) {
return mockManager;
}
return invokeSupersequentNoParameters();
}
#pragma clang diagnostic pop
+(id)createMockManager {
mockManager = [OCMockObject mockForClass:[Manager class]];
return mockManager;
}
+(id)createNiceMockManager {
mockManager = [OCMockObject niceMockForClass:[Manager class]];
return mockManager;
}
+(void)releaseInstance {
mockManager = nil;
}
Rather than creating a category, you could just stub sharedManager and return a nice mock.
- (void)setUp
{
[super setUp];
self.viewController = [ViewController new];
//for singleton
id classMockManager = OCClassMock([Manager class]);
OCMStub([classMockManager sharedManager]).andReturn(classMockManager);
self.mockManager = classMockManager;
}
I don't have an environment up to test this just this moment, but this strategy should work. Note that this is OCMock3 syntax. See http://ocmock.org/reference/#mocking-class-methods
In your description above you write "manager category for unit test", but the implementation that follows is not a category, it's an actual implementation of the Manager class, i.e. the code reads
#implementation Manager
and not
#implementation Manager(unitTests)
It seems that the test code uses this second implementation of Manager and that implementation does not have a getOne method. So the mock is right to complain; the implementation of Manager it sees does not have the method and hence it can't stub it.
I believe you can fix your test by making the implementation a category. As far as I know it is possible to override a class method (sharedManager in your case) in a category, but it's a bit dicey to do so. The approach described by Ben Flynn is better.

Hold NSOperationQueue until previous operation completes

I want to perform few operations and need to start the next operation only upon completion of the previous one. The operation I'm adding will send async call to the server and receive data. I want to start the next operation only after the first call to the server finish receiving data from the server. How to do that?
{....
PhotoDownloader *pd = [[PhotoDownloader alloc] init];
[GetGlobalOperationQueue addOperation:pd];
}
Inside the PhotoDownloader I will allocate the required parameters and call a Global function which handles all the request
[GlobalCommunicationUtil sendServerReq:reqObj withResponseHandler:self];
Inside the sendServerReq method I will construct the URL request and send it to the server and this call is a "sendAsynchronousRequest" call. The PhotoDownloader will have the CommunicationUtil's delegate methods.
There are two parts to this question:
You asked:
How do I make one operation not start until the previous operation finishes?
To do this, you could, theoretically, simply make a serial queue (which is fine if you want to make all operations wait until the prior one finishes). With an NSOperationQueue, you achieve that simply by setting maxConcurrentOperationCount to 1.
Or, a little more flexible, you could establish dependencies between operations where dependencies are needed, but otherwise enjoy concurrency. For example, if you wanted to make two network requests dependent upon the completion of a third, you could do something like:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 4; // generally with network requests, you don't want to exceed 4 or 5 concurrent operations;
// it doesn't matter too much here, since there are only 3 operations, but don't
// try to run more than 4 or 5 network requests at the same time
NSOperation *operation1 = [[NetworkOperation alloc] initWithRequest:request1 completionHandler:^(NSData *data, NSError *error) {
[self doSomethingWithData:data fromRequest:request1 error:error];
}];
NSOperation *operation2 = [[NetworkOperation alloc] initWithRequest:request2 completionHandler:^(NSData *data, NSError *error) {
[self doSomethingWithData:data fromRequest:request2 error:error];
}];
NSOperation *operation3 = [[NetworkOperation alloc] initWithRequest:request3 completionHandler:^(NSData *data, NSError *error) {
[self doSomethingWithData:data fromRequest:request3 error:error];
}];
[operation2 addDependency:operation1]; // don't start operation2 or 3 until operation1 is done
[operation3 addDependency:operation1];
[queue addOperation:operation1]; // now add all three to the queue
[queue addOperation:operation2];
[queue addOperation:operation3];
You asked:
How do I ensure that an operation will not complete until the asynchronous network request it issued has finished as well?
Again, there are different approaches here. Sometimes you can avail yourself with semaphores to make asynchronous process synchronous. But, much better is to use a concurrent NSOperation subclass.
An "asynchronous" NSOperation is simply one that will not complete until it issues a isFinished notification (thereby allowing any asynchronous tasks it initiates to finish). And an NSOperation class specifies itself as an asynchronous operation simply by returning YES in its isAsynchronous implementation. Thus, an abstract class implementation of an asynchronous operation might look like:
// AsynchronousOperation.h
#import Foundation;
#interface AsynchronousOperation : NSOperation
/**
Complete the asynchronous operation.
If you create an asynchronous operation, you _must_ call this for all paths of execution
or else the operation will not terminate (and dependent operations and/or available
concurrent threads for the operation queue (`maxConcurrentOperationCount`) will be blocked.
*/
- (void)completeOperation;
#end
and
//
// AsynchronousOperation.m
//
#import "AsynchronousOperation.h"
#interface AsynchronousOperation ()
#property (getter = isFinished, readwrite) BOOL finished;
#property (getter = isExecuting, readwrite) BOOL executing;
#end
#implementation AsynchronousOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (instancetype)init {
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start {
if (self.isCancelled) {
if (!self.isFinished) self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
- (void)completeOperation {
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
}
#pragma mark - NSOperation methods
- (BOOL)isAsynchronous {
return YES;
}
- (BOOL)isExecuting {
#synchronized(self) { return _executing; }
}
- (BOOL)isFinished {
#synchronized(self) { return _finished; }
}
- (void)setExecuting:(BOOL)executing {
[self willChangeValueForKey:#"isExecuting"];
#synchronized(self) { _executing = executing; }
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished {
[self willChangeValueForKey:#"isFinished"];
#synchronized(self) { _finished = finished; }
[self didChangeValueForKey:#"isFinished"];
}
#end
Now that we have that abstract, asynchronous NSOperation subclass, we can use it in our concrete NetworkOperation class:
#import "AsynchronousOperation.h"
NS_ASSUME_NONNULL_BEGIN
typedef void(^NetworkOperationCompletionBlock)(NSData * _Nullable data, NSError * _Nullable error);
#interface NetworkOperation : AsynchronousOperation
#property (nullable, nonatomic, copy) NetworkOperationCompletionBlock networkOperationCompletionBlock;
#property (nonatomic, copy) NSURLRequest *request;
- (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(NetworkOperationCompletionBlock)completionHandler;
#end
NS_ASSUME_NONNULL_END
and
// NetworkOperation.m
#import "NetworkOperation.h"
#interface NetworkOperation ()
#property (nonatomic, weak) NSURLSessionTask *task;
#end
#implementation NetworkOperation
- (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(NetworkOperationCompletionBlock)completionHandler {
self = [self init];
if (self) {
self.request = request;
self.networkOperationCompletionBlock = completionHandler;
}
return self;
}
- (void)main {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task = [session dataTaskWithRequest:self.request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (self.networkOperationCompletionBlock) {
self.networkOperationCompletionBlock(data, error);
self.networkOperationCompletionBlock = nil;
}
[self completeOperation];
}];
[task resume];
self.task = task;
}
- (void)cancel {
[super cancel];
[self.task cancel];
}
#end
Now, in this example, I'm using block-based implementation of these asynchronous network requests, but the idea works equally well in delegate-based connections/sessions, too. (The only hassle is that NSURLSession specifies its task-related delegate methods to be part of the session, not the network task.)
Clearly the implementation of your own NetworkOperation class may differ wildly (use delegate patterns or completion block patterns, etc.), but hopefully this illustrates the idea of a concurrent operation. For more information, see the Operation Queues chapter of the Concurrency Programming Guide, notably the section titled "Configuring Operations for Concurrent Execution".
A swift version for an asynchronous operation (which was not very obvious):
final class NetworkOperation: Operation {
lazy var session: NSURLSession = {
return NSURLSession.sharedSession()
}()
private var _finished = false {
willSet {
willChangeValue(forKey: "isFinished")
}
didSet {
didChangeValue(forKey: "isFinished")
}
}
private var _executing = false {
willSet {
willChangeValue(forKey: "isExecuting")
}
didSet {
didChangeValue(forKey: "isExecuting")
}
}
override var isAsynchronous: Bool {
return true
}
override var isFinished: Bool {
return _finished
}
override var isExecuting: Bool {
return _executing
}
override func start() {
_executing = true
execute()
}
func execute() {
task = session.downloadTaskWithURL(NSURL(string: "yourURL")!) {
(url, response, error) in
if error == nil {
// Notify the response by means of a closure or what you prefer
// Remember to run in the main thread since NSURLSession runs its
// task on background by default
} else {
// Notify the failure by means of a closure or what you prefer
// Remember to run in the main thread since NSURLSession runs its
// task on background by default
}
// Remember to tell the operation queue that the execution has completed
self.finish()
}
}
func finish() {
//Async task complete and hence the operation is complete
_executing = false
_finished = true
}
}
To serialize the operations:
let operationQueue = OperationQueue()
let operation1 = NetworkOperation()
let operation2 = NetworkOperation()
operation2.addDependency(operation1)
operationQueue.addOperations([operation1, operation2], waitUntilFinished: false)
Is it manadatory to use NSOperationQueue?
This behaviour is very easy to implement with serial queues
https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
Assuming you have a class to manage the operations, you would create a serial dispatch queue in your init method with
queue = dispatch_queue_create("com.example.MyQueue", NULL);
And you would have a method to enqueue request, something like this
- (void) enqueueRequest:(NSURL *)requestURL
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(queue, ^{ /* get data from requestURL */ }) });
}
This way, only one request is active at one time, even though each request will be executed in a separated background thread, and several requests will be enqueued until the active request finishes.

Keeping an NSOperation in a queue even after it completes

Typically once the main method of the an NSOperation is completed, the op is marked completed and it is removed from the queue. However, my op makes networking calls, and I want to handle retries. How do I keep an NSOperation in an NSOperationQueue until I explicitly say it's ok to remove it?
I can't find the original source for the work I did on my current project.
I have subclassed NSOperation and do this...
Add private properties in the .m...
#property (nonatomic) BOOL executing;
#property (nonatomic) BOOL finished;
#property (nonatomic) BOOL completed;
Init the operation...
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
_completed = NO;
}
return self;
}
Add the functions to return the properties...
- (BOOL)isExecuting { return self.executing; }
- (BOOL)isFinished { return self.finished; }
- (BOOL)isCompleted { return self.completed; }
- (BOOL)isConcurrent { return YES; }
In the "start" function (this is the bit that the operationQueue calls...
- (void)start
{
if ([self isCancelled]) {
[self willChangeValueForKey:#"isFinished"];
self.finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
}
Then in the main put your working code...
- (void)main
{
#try {
//this is where your loop would go with your counter and stuff
//when you want the operationQueue to be notified that the work
//is done just call...
[self completeOperation];
}
#catch (NSException *exception) {
NSLog(#"Exception! %#", exception);
[self completeOperation];
}
}
Write the code for completeOperation...
- (void)completeOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
That's it.
As long as you have these then the operation will work.
You can add as many other functions and properties as you wish.
In fact, I have actually subclassed this class as I have a function that does all the work for different types of object (it's an upload thing). I have defined a function...
- (void)uploadData
{
//subclass this method.
}
Then all I have in the subclasses is a custom "uploadData" method.
I find this really useful as it gives you fine grain control on when to finish the operation etc...

Resources