I added a observer to my collection, and observe the count on it
[[[JHTaskSave defaults] tasks] addObserver:self forKeyPath:#"count" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
JHTaskSave is a singleton object and tasks is a JHTaskCollection KVC compliant, when I add an object to my collection:
[[[JHTaskSave defaults] tasks] addTask:newTask]
The count of tasks changes but the observeValueForKeyPath is not called, I don't understand why
Here is my collection class:
#interface JHTaskCollection : NSObject <NSFastEnumeration>
{
NSMutableArray *_tasks;
}
#property (nonatomic) NSUInteger count;
- (id)taskAtIndex:(NSUInteger)index;
- (void)addTask:(JHTask *)task;
- (void)removeTask:(JHTask *)task;
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index;
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index;
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes;
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes;
#end
#implementation JHTaskCollection
- (id)init
{
if(self = [super init]) {
_tasks = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
return [_tasks countByEnumeratingWithState:state objects:stackbuf count:len];
}
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes
{
return [_tasks objectsAtIndexes:indexes];
}
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index
{
[_tasks insertObject:key atIndex:index];
}
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index
{
[_tasks removeObjectAtIndex:index];
}
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes
{
[_tasks removeObjectsAtIndexes:indexes];
}
- (JHTask *)taskAtIndex:(NSUInteger)index
{
return [_tasks objectAtIndex:index];
}
- (NSUInteger)count
{
return _tasks.count;
}
- (void)addTask:(JHTask *)task
{
[_tasks addObject:task];
}
- (void)removeTask:(JHTask *)task
{
[_tasks removeObject:task];
}
#end
Based on the code you posted, if you want count to be Key-Value Observable, you need to send willChangeValueForKey and didChangeValueForKey notifications any time you mutate the collection in a way that changes the count. Using your code:
#implementation JHTaskCollection
- (id)init
{
if(self = [super init]) {
_tasks = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained *)stackbuf count:(NSUInteger)len
{
return [_tasks countByEnumeratingWithState:state objects:stackbuf count:len];
}
- (NSArray *)taskAtIndexes:(NSIndexSet *)indexes
{
return [_tasks objectsAtIndexes:indexes];
}
- (void)insertObject:(id)key inTasksAtIndex:(NSUInteger)index
{
[self willChangeValueForKey: #"count"];
[_tasks insertObject:key atIndex:index];
[self didChangeValueForKey: #"count"];
}
- (void)removeObjectFromTasksAtIndex:(NSUInteger)index
{
[self willChangeValueForKey: #"count"];
[_tasks removeObjectAtIndex:index];
[self didChangeValueForKey: #"count"];
}
- (void)removeTaskAtIndexes:(NSIndexSet *)indexes
{
[self willChangeValueForKey: #"count"];
[_tasks removeObjectsAtIndexes:indexes];
[self didChangeValueForKey: #"count"];
}
- (JHTask *)taskAtIndex:(NSUInteger)index
{
return [_tasks objectAtIndex:index];
}
- (NSUInteger)count
{
return _tasks.count;
}
- (void)addTask:(JHTask *)task
{
[self willChangeValueForKey: #"count"];
[_tasks addObject:task];
[self didChangeValueForKey: #"count"];
}
- (void)removeTask:(JHTask *)task
{
[self willChangeValueForKey: #"count"];
[_tasks removeObject:task];
[self didChangeValueForKey: #"count"];
}
#end
There's a couple of reasons why the notification for count isn't firing, both having to do with Key-Value Coding.
The first reason is because the count property is not updated when calling addTask:. There isn't a direct link between the count property and the count of the _tasks array. Any code manipulating the array has to be wrapped between -willChangeValueForKey: and -didChangeValueForKey: calls. However, the KVO protocol provides the ability to set dependent keys. Since the count property is affected by the array, you can set a dependency between these two keys by using
+ (NSSet*) keyPathsForValuesAffectingCount {
return [NSSet setWithObject:#"tasks"];
}
Or the more generic + keyPathsForValuesAffectingValueForKey:(NSString *)key. Implementing either will fire notifications for count when tasks is modified.
The second reason why this won't work, is because you're not updating the tasks array in a Key-Value compliant way as defined in the Key-Value Coding Programming Guide. Basically, objects in ordered collections have to be added with either
-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes:
Or be wrapped with the will|didChangeValueForKey: calls.
In your example, if you provide the following implementation after setting the key dependency, you should observe a change.
- (void)addTask:(JHTask *)task
{
[self insertObject:task inTasksAtIndex:[_tasks count]];
}
-(NSInteger) count is a method, and as such, you cannot use KVO to monitor it - you could use AOP though I would think this is not the direction you would like to go.
Perhaps instead, you could directly monitor the collection, rather than it's count, though I'm not sure if you can do this with KVO (I don't think you can) - you would need to add an addObject: method and do your magic in there?
Edit:
You could use 'method swizzling', as laid out in this blog, though is it worth the added obscurity?
Related
In my project im trying to work via MVVM,
so in VM in .h file
#property (nonatomic, strong) NSArray *cities;
in .m file
- (NSArray *)cities {
return [[GPCity allObjects] valueForKey:#"name"];
}
GPCity is a RLMObject subclass
How to bind this via ReactiveCocoa (i mean see all cities updates/adds/removes) ?
Something like:
RAC(self, cities) = [[GPCity allObjects] map:(GPCity *)city {return city.name;}];
You can wrap Realm change notifications in a RAC signal:
#interface RLMResults (RACSupport)
- (RACSignal *)gp_signal;
#end
#implementation RLMResults (RACSupport)
- (RACSignal *)gp_signal {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
id token = [self.realm addNotificationBlock:^(NSString *notification, RLMRealm *realm) {
if (notification == RLMRealmDidChangeNotification) {
[subscriber sendNext:self];
}
}];
return [RACDisposable disposableWithBlock:^{
[self.realm removeNotification:token];
}];
}];
}
#end
and then do:
RAC(self, cities) = [[[RLMObject allObjects] gp_signal]
map:^(RLMResults<GPCity *> *cities) { return [cities valueForKey:#"name"]; }];
This will unfortunately update the signal after every write transaction, and not just ones which modify cities. Once Realm 0.98 is released with support for per-RLMResults notifications, you'll be able to do the following, which will only update when a GPCity object is updated:
#interface RLMResults (RACSupport)
- (RACSignal *)gp_signal;
#end
#implementation RLMResults (RACSupport)
- (RACSignal *)gp_signal {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
id token = [self addNotificationBlock:^(RLMResults *results, NSError *error) {
if (error) {
[subscriber sendError:error];
}
else {
[subscriber sendNext:results];
}
}];
return [RACDisposable disposableWithBlock:^{
[token stop];
}];
}];
}
#end
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.
So, I have all multipeer connectivity related code in the main thread. I have a MCSession, MCNearbyServiceAdvertiser, and a MCNearbyServiceBrowser. These are all created with peerID, and I make sure only one sends an invitation.
The session becomes connected. My problems is that, two clients take around 20-30 seconds to connect. This is unacceptable. The clients are on good Wifi, and bluetooth. I want the browsing, invitation handler, and connection to take place within 1 second. Does anyone have any idea what is slowing things down?
Code is exactly as provided here and I also implemented certificateHandler(YES)
#interface SessionController () // Class extension
#property (nonatomic, strong) MCPeerID *peerID;
#property (nonatomic, strong) MCSession *session;
#property (nonatomic, strong) MCNearbyServiceAdvertiser *serviceAdvertiser;
#property (nonatomic, strong) MCNearbyServiceBrowser *serviceBrowser;
// Connected peers are stored in the MCSession
// Manually track connecting and disconnected peers
#property (nonatomic, strong) NSMutableOrderedSet *connectingPeersOrderedSet;
#property (nonatomic, strong) NSMutableOrderedSet *disconnectedPeersOrderedSet;
#end
#implementation SessionController
static NSString * const kMCSessionServiceType = #"mcsessionp2p";
#pragma mark - Initializer
- (instancetype)init
{
self = [super init];
if (self)
{
_peerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];
_connectingPeersOrderedSet = [[NSMutableOrderedSet alloc] init];
_disconnectedPeersOrderedSet = [[NSMutableOrderedSet alloc] init];
NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
// Register for notifications
[defaultCenter addObserver:self
selector:#selector(startServices)
name:UIApplicationWillEnterForegroundNotification
object:nil];
[defaultCenter addObserver:self
selector:#selector(stopServices)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[self startServices];
_displayName = self.session.myPeerID.displayName;
}
return self;
}
#pragma mark - Memory management
- (void)dealloc
{
// Unregister for notifications on deallocation.
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Nil out delegates
_session.delegate = nil;
_serviceAdvertiser.delegate = nil;
_serviceBrowser.delegate = nil;
}
#pragma mark - Override property accessors
- (NSArray *)connectedPeers
{
return self.session.connectedPeers;
}
- (NSArray *)connectingPeers
{
return [self.connectingPeersOrderedSet array];
}
- (NSArray *)disconnectedPeers
{
return [self.disconnectedPeersOrderedSet array];
}
#pragma mark - Private methods
- (void)setupSession
{
// Create the session that peers will be invited/join into.
_session = [[MCSession alloc] initWithPeer:self.peerID];
self.session.delegate = self;
// Create the service advertiser
_serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:self.peerID
discoveryInfo:nil
serviceType:kMCSessionServiceType];
self.serviceAdvertiser.delegate = self;
// Create the service browser
_serviceBrowser = [[MCNearbyServiceBrowser alloc] initWithPeer:self.peerID
serviceType:kMCSessionServiceType];
self.serviceBrowser.delegate = self;
}
- (void)teardownSession
{
[self.session disconnect];
[self.connectingPeersOrderedSet removeAllObjects];
[self.disconnectedPeersOrderedSet removeAllObjects];
}
- (void)startServices
{
[self setupSession];
[self.serviceAdvertiser startAdvertisingPeer];
[self.serviceBrowser startBrowsingForPeers];
}
- (void)stopServices
{
[self.serviceBrowser stopBrowsingForPeers];
[self.serviceAdvertiser stopAdvertisingPeer];
[self teardownSession];
}
- (void)updateDelegate
{
[self.delegate sessionDidChangeState];
}
- (NSString *)stringForPeerConnectionState:(MCSessionState)state
{
switch (state) {
case MCSessionStateConnected:
return #"Connected";
case MCSessionStateConnecting:
return #"Connecting";
case MCSessionStateNotConnected:
return #"Not Connected";
}
}
#pragma mark - MCSessionDelegate protocol conformance
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state
{
NSLog(#"Peer [%#] changed state to %#", peerID.displayName, [self stringForPeerConnectionState:state]);
switch (state)
{
case MCSessionStateConnecting:
{
[self.connectingPeersOrderedSet addObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
break;
}
case MCSessionStateConnected:
{
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
break;
}
case MCSessionStateNotConnected:
{
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet addObject:peerID];
break;
}
}
[self updateDelegate];
}
- (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID
{
// Decode the incoming data to a UTF8 encoded string
NSString *receivedMessage = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"didReceiveData %# from %#", receivedMessage, peerID.displayName);
}
- (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress
{
NSLog(#"didStartReceivingResourceWithName [%#] from %# with progress [%#]", resourceName, peerID.displayName, progress);
}
- (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error
{
NSLog(#"didFinishReceivingResourceWithName [%#] from %#", resourceName, peerID.displayName);
// If error is not nil something went wrong
if (error)
{
NSLog(#"Error [%#] receiving resource from %# ", [error localizedDescription], peerID.displayName);
}
else
{
// No error so this is a completed transfer. The resources is located in a temporary location and should be copied to a permenant location immediately.
// Write to documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *copyPath = [NSString stringWithFormat:#"%#/%#", [paths firstObject], resourceName];
if (![[NSFileManager defaultManager] copyItemAtPath:[localURL path] toPath:copyPath error:nil])
{
NSLog(#"Error copying resource to documents directory");
}
else
{
// Get a URL for the path we just copied the resource to
NSURL *url = [NSURL fileURLWithPath:copyPath];
NSLog(#"url = %#", url);
}
}
}
// Streaming API not utilized in this sample code
- (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID
{
NSLog(#"didReceiveStream %# from %#", streamName, peerID.displayName);
}
#pragma mark - MCNearbyServiceBrowserDelegate protocol conformance
// Found a nearby advertising peer
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info
{
NSString *remotePeerName = peerID.displayName;
NSLog(#"Browser found %#", remotePeerName);
MCPeerID *myPeerID = self.session.myPeerID;
BOOL shouldInvite = ([myPeerID.displayName compare:remotePeerName] == NSOrderedDescending);
if (shouldInvite)
{
NSLog(#"Inviting %#", remotePeerName);
[browser invitePeer:peerID toSession:self.session withContext:nil timeout:30.0];
}
else
{
NSLog(#"Not inviting %#", remotePeerName);
}
[self updateDelegate];
}
- (void)browser:(MCNearbyServiceBrowser *)browser lostPeer:(MCPeerID *)peerID
{
NSLog(#"lostPeer %#", peerID.displayName);
[self.connectingPeersOrderedSet removeObject:peerID];
[self.disconnectedPeersOrderedSet addObject:peerID];
[self updateDelegate];
}
- (void)browser:(MCNearbyServiceBrowser *)browser didNotStartBrowsingForPeers:(NSError *)error
{
NSLog(#"didNotStartBrowsingForPeers: %#", error);
}
#pragma mark - MCNearbyServiceAdvertiserDelegate protocol conformance
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler
{
NSLog(#"didReceiveInvitationFromPeer %#", peerID.displayName);
invitationHandler(YES, self.session);
[self.connectingPeersOrderedSet addObject:peerID];
[self.disconnectedPeersOrderedSet removeObject:peerID];
[self updateDelegate];
}
- (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didNotStartAdvertisingPeer:(NSError *)error
{
NSLog(#"didNotStartAdvertisingForPeers: %#", error);
}
#end
I've noticed this issue to and i can't seem to figure out whats happening here, it appears when debugging that there is a huge delay between the notification for state changing being fired, perhaps something to do with the view itself.
Update: Well i think i figured the issues out after a bit of reading, the response is pretty much instant now, in the notification in the view i have pushed the process back to the main thread like so:
-(void)peerDidChangeStateWithNotification:(NSNotification *)notification{
MCPeerID *peerID = [[notification userInfo] objectForKey:#"peerID"];
NSString *peerDisplayName = peerID.displayName;
MCSessionState state = [[[notification userInfo] objectForKey:#"state"] intValue];
if (state != MCSessionStateConnecting) {
if (state == MCSessionStateConnected) {
// add the user
[arrConnectedDevices addObject:peerDisplayName];
}
else if (state == MCSessionStateNotConnected){
// do we have connections
if ([arrConnectedDevices count] > 0) {
int indexOfPeer = [arrConnectedDevices indexOfObject:peerDisplayName];
[arrConnectedDevices removeObjectAtIndex:indexOfPeer];
}
}
}
// push to main queue for speedy response
dispatch_async(dispatch_get_main_queue(), ^(void) {
[collView reloadData];
BOOL peersExist = ([[appDelegate.mcManager.session connectedPeers] count] == 0);
NSLog(#"PEER COUNT IS %lu",(unsigned long)[[appDelegate.mcManager.session connectedPeers] count]);
[disconnectButton setEnabled:!peersExist];
if ([disconnectButton isEnabled]) {
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(51/255.0) green:(202/255.0) blue:(168/255.0) alpha:1.0]];
}
else{
[disconnectButton setBackgroundColor:[UIColor colorWithRed:(107/255.0) green:(107/255.0) blue:(107/255.0) alpha:1.0]];
}
});
}
Hope this helps anyone who ran into the issues.
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...
I have a problem. In my program I need to cancel operations (subclass NSOperation) in queue after clicking on button. But when I calling [queue cancelAllOperations] nothing happens. The queue continue executing. All operations must be concurrent. What am I doing wrong?
#interface SearchOperation : NSOperation
{
TrueSearcherViewController *viewController;
SearchYouTube *you_search;
SearchGoogle *goo_search;
BOOL semafore;
}
- (id)initYoutubeTaskWithData:(SearchYouTube *) sy;
- (id)initGoogleTaskWithData:(SearchGoogle *) sg;
- (void) beginYoutubeSearch:(SearchYouTube *) sy;
- (void) beginGoogleSearch:(SearchGoogle *) sg;
#end
#import "SearchOperation.h"
#implementation SearchOperation
- (void) start
{
if (semafore == YES)
{
[self beginYoutubeSearch:you_search];
}
else
{
[self beginGoogleSearch:goo_search];
}
}
- (id)initYoutubeTaskWithData:(SearchYouTube *) sy
{
if (self = [super init])
{
you_search = sy;
semafore = YES;
}
return self;
}
- (id)initGoogleTaskWithData:(SearchGoogle *) sg
{
if (self = [super init])
{
goo_search = sg;
semafore = NO;
}
return self;
}
- (void) beginYoutubeSearch:(SearchYouTube *) sy
{
[sy runSearch];
}
- (void) beginGoogleSearch:(SearchGoogle *) sg
{
[sg runSearch];
}
- (void) dealloc
{
[super dealloc];
}
#end
You need to check in your code if your operation is cancelled to interrupt it with :
if(self.isCancelled){
return;
}