I am trying to split up my programs flow using NSOperations. I am using the Parse framework to make a simple messaging app. I want to display some messages and then delete them. The display messages shouldn't be called unless the delete operations is finished so I want to try using an NSQueue and add a displayMessages operation to it and then a deleteMessages operation (named MyOperation below). I understand that concurrent operations means they will only execute one after another in a queue fashion. Below is my code for the delete method. Is there a way to manually tell the operation it is finished i.e. setting isFinished or isExecuting??
// MyOperation.h
#interface MyOperation : NSOperation {
#property (strong, nonatomic) NSMutableArray *toDelete;
}
#end
And the implementation:
// MyOperation.m
#implementation MyOperation
- (id)initWithArray:(NSMutableArray *)array
{
self = [super init];
if (self == nil)
return nil;
_toDelete=array;
}
- (void)main {
if ([self isCancelled]) {
NSLog(#"** operation cancelled **");
}
//how do I get main to finish execution ONLY after deleteAllInBackground has finished?
[PFObject deleteAllInBackground:self.toDelete];
if ([self isCancelled]) {
NSLog(#"** operation cancelled **");
}
NSLog(#"Operation finished");
}
#end
right now this code above won't solve my problem. It will queue up the ops but this one will finish even though the deleteAllInBackground is still running. Would really appreciate some help here! thanks
other possible solution:
-(void)receivedMessage
{
NSLog(#"push!");
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
[self displayMessages];
dispatch_async(dispatch_get_main_queue(), ^(void){
if([self.toDelete count]>0) {
[PFObject deleteAllInBackground:self.toDelete];
}
});
});
}
I would suggest you to use dispatch_async like below;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
dispatch_async(dispatch_get_main_queue(), ^(void){
// Display messages
});
// Delete messages here
});
If you have to use NSOperationQueue then I would suggest you to use KVO to get notification for task completion; When you setup your queue, do this:
[self.deleteQueue addObserver:self forKeyPath:#"delete-operations" options:0 context:NULL];
Then do this in your observeValueForKeyPath:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
if (object == self.deleteQueue && [keyPath isEqualToString:#"delete-operations"]) {
if ([self.queue.operations count] == 0) {
// Delete operation done
// Display messages here
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
[EDIT]
-(void)receivedMessage {
#synchronized(self) {
NSLog(#"push!");
dispatch_async(dispatch_get_main_queue(), ^(void) {
[self displayMessages];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
if([self.toDelete count]>0) {
// this deletion doesn't matter if background or on main thread as it's already in background queue
[PFObject deleteAllInBackground:self.toDelete];
}
});
});
}
}
Use deleteAll instead of deleteAllInBackground if you want to block the current thread until the deletion is complete.
Related
I am trying to write some code to scan for bluetooth devices for a while, and then return the array of discovered peripherals through a block-based callback.
Blocking code should not be an issue as the code will be called asynchronously.
After reading up on the API documentation my initial plan of attack was to write an implementation for CBCentralManagerDelegate, use an init method to give it a block-based callback to call once the CBManagerState is PoweredOn, and then initialize this class with a callback that triggers the scanning and extracts the discovered Peripherals.
The issue is... it doesn't work. Except when it does.
Now I could work out a workaround to reach my goal, but for the sake of learning and understanding I am very interested in where exactly the issue originates from.
typedef void (^SomeBlock)(CBCentralManager*);
#interface TEST : NSObject <CBCentralManagerDelegate>
#property CBCentralManager* manager;
#property SomeBlock onPoweredOn;
#property NSMutableArray<CBPeripheral*>* peripherals;
- (void) init: (SomeBlock) onPoweredOn;
- (void) startScan;
- (void) stopScan;
#end
#implementation TEST
- (void) init: (SomeBlock) onPoweredOn {
NSLog(#"%#", #"init");
self.onPoweredOn = onPoweredOn;
self.manager = [CBCentralManager alloc];
dispatch_queue_attr_t attr = DISPATCH_QUEUE_CONCURRENT;
dispatch_queue_t queue =dispatch_queue_create("BTManagerHandler", attr);
self.manager = [self.manager initWithDelegate: self queue: queue];
}
- (void) startScan {
NSLog(#"%#", #"startScan");
[self.manager scanForPeripheralsWithServices: nil options: nil];
}
- (void) stopScan {
NSLog(#"%#", #"stopScan ");
[self.manager stopScan];
}
- (void) centralManagerDidUpdateState: (nonnull CBCentralManager *) manager {
NSLog(#"%#", #"centralManagerDidUpdateState:");
switch (manager.state) {
case CBManagerStateUnknown:
NSLog(#"%#", #"CBManagerStateUnknown:");
break;
case CBManagerStateResetting:
NSLog(#"%#", #"CBManagerStateResetting:");
break;
case CBManagerStateUnsupported:
NSLog(#"%#", #"CBManagerStateUnsupported:");
break;
case CBManagerStateUnauthorized:
NSLog(#"%#", #"CBManagerStateUnauthorized:");
break;
case CBManagerStatePoweredOff:
NSLog(#"%#", #"CBManagerStatePoweredOff:");
break;
case CBManagerStatePoweredOn:
NSLog(#"%#", #"CBManagerStatePoweredOn:");
if (self.onPoweredOn != nil) self.onPoweredOn(manager);
break;
}
}
- (void) centralManager: (nonnull CBCentralManager*) central didDiscoverPeripheral: (nonnull CBPeripheral*) peripheral advertisementData: (nonnull NSDictionary<NSString*, id>*) advertisementData RSSI: (nonnull NSNumber*) RSSI {
NSLog(#"%#", #"centralManager:didDiscoverPeripheral:advertisementData:RSSI:");
if (self.peripherals == nil) self.peripherals = [NSMutableArray array];
for (CBPeripheral* _peripheral in self.peripherals) {
if (peripheral.identifier == _peripheral.identifier) return;
}
[self.peripherals addObject: peripheral];
}
#end
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
NSLog(#"%#", #"_onPoweredOn_");
[test startScan];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
};
[test init: onPoweredOn];
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
I would expect the above code to work, but it doesn't.
The 'onPoweredOn' callback and the 'startScan' method are called correctly, but the 'centralManager:didDiscoverPeripheral:advertisementData:RSSI:' method is never called.
After some trial and error I found that the following works:
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
NSLog(#"%#", #"_onPoweredOn_");
[test startScan];
};
[test init: onPoweredOn];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
After some more trial and error I narrowed it down to one line of code:
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
SomeBlock onPoweredOn = ^(CBCentralManager* manager) {
NSLog(#"%#", #"_onPoweredOn_");
[test startScan];
[NSThread sleepForTimeInterval: 10.0]; // <<=== this line! ===
};
[test init: onPoweredOn];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
This suggests that that using a [NSThread sleepForTimeInterval:] blocks the discovery of bluetooth devices... but tat seems illogical to me because the same code works without the block-based callback:
+ (void) discoverDevices {
TEST* test = nil;
#try {
test = [TEST alloc];
[test init: nil];
[NSThread sleepForTimeInterval: 1.0];
[test startScan];
[NSThread sleepForTimeInterval: 10.0];
[managerHandler stopScan];
NSArray<CBPeripheral*>* discoveredPeripherals = managerHandler.peripherals;
// do stuff with discoveredPeripherals
} #catch(NSException* e) {
// exception handling
} #finally {
// cleanup
}
}
Conclusion: combining CBCentralManager, block-based callbacks and [NSThread sleepForTimeInterval:] leads to unexpected behaviour??
but why? what's so special about this specific combination?
-(void)init{
self.sema = dispatch_semaphore_create(1)
}
-(void)main{
//sending one message is fine
[self preSendMessage:#"hi"];
//deadlock happen when sending multiple msg in a short time.
for(int i = 0; i < 10; i++){
[self preSendMessage:#"yo"];
}
}
- (void)preSendMessage:(NSString*)msg
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
RACSignal* signal = [self sendMessage:msg];
#weakify(self);
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
#strongify(self);
dispatch_semaphore_signal(self.sema);
} completed:^{
#strongify(self);
dispatch_semaphore_signal(self.sema);
}];
});
}
Some informations:
In sendMessage function, I am using AFNetworking.
When I remove the semaphore logic, it works fine, I need this because need to control the sequence of sending msgs.
[Update] my semaphore is init with value 1, so It would run at the first time
My Target is to ensure the code inside preSendMessage execute when the previous one is completed/error
Edit: you should use dispatch_semaphore_create(0)
You should put dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER); after dispatch_async(....)
Something like this:
- (void)preSendMessage:(NSString*)msg{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RACSignal* signal = [self sendMessage:msg];
#weakify(self);
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
#strongify(self);
dispatch_semaphore_signal(self.sema);
} completed:^{
#strongify(self);
dispatch_semaphore_signal(self.sema);
}];
});
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
}
You are calling dispatch_semaphore_wait in the wrong order. Your code subscription does not get executed, because your semaphore is in a wait state. You should call your dispatch_semaphore_wait right after dispatching to background thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RACSignal* signal = [self sendMessage:msg];
#weakify(self);
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
#strongify(self);
dispatch_semaphore_signal(self.sema);
} completed:^{
#strongify(self);
dispatch_semaphore_signal(self.sema);
}];
});
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
You tell your app to wait for the semaphore, so your app waits for the semaphore. It doesn't execute any code after the wait call.
You need to set up everything, including the callbacks that will signal, and then you wait for the signal.
Here is my code:
#interface MyObject ()
#property(nonatomic) dispatch_queue_t queue;
#end
#implementation MyObject {
NSThread *_check;
}
- (id)init {
self = [super init];
if (self) {
_queue = dispatch_queue_create("com.Thread.queue", NULL);
dispatch_async(_queue, ^{
_check = [NSThread currentThread]; //for ex. thread number = 3
//some code here...
});
}
return self;
}
- (void)someMethod:(MyObjClass *)obj {
dispatch_async(_queue, ^{
//need th
if (_check != [NSThread currentThread]) { // it is sometimes number 3, but sometimes it changes
NSLog(#"Thread changed.");
}
[obj doSmth]; //got crash if currentThread != _check
});
}
#end
I need to make sure that all MyObjClass's methods performs in the same thread. But this code change thread by it's own will, but sometimes it work in single thread. Any way I can force it to use same thread all the time?
In a word, no. Other than the main queue, GCD does not have any notion of thread affinity. If you really need thread affinity, GCD is not really the right tool. If you like the idiom, and want to kind of "adapt" something to your needs you could do something like this:
#implementation AppDelegate
{
NSThread* thread;
}
void dispatch_thread_async(NSThread* thread, dispatch_block_t block)
{
if ([NSThread currentThread] == thread)
{
block();
}
else
{
block = [block copy];
[(id)block performSelector: #selector(invoke) onThread: thread withObject: nil waitUntilDone: NO];
}
}
void dispatch_thread_sync(NSThread* thread, dispatch_block_t block)
{
if ([NSThread currentThread] == thread)
{
block();
}
else
{
[(id)block performSelector: #selector(invoke) onThread: thread withObject: nil waitUntilDone: YES];
}
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
thread = [[NSThread alloc] initWithTarget: self selector:#selector(threadMain) object:nil];
[thread start];
dispatch_thread_async(thread, ^{
NSLog(#"Async Thread: %#", [NSThread currentThread]);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_thread_sync(thread, ^{
NSLog(#"Sync Thread: %#", [NSThread currentThread]);
});
});
}
- (void)threadMain
{
// You need the NSPort here because a runloop with no sources or ports registered with it
// will simply exit immediately instead of running forever.
NSPort* keepAlive = [NSPort port];
NSRunLoop* rl = [NSRunLoop currentRunLoop];
[keepAlive scheduleInRunLoop: rl forMode: NSRunLoopCommonModes];
[rl run];
}
#end
In case you have multiple instances of your class, you are overwriting your queue with each new init.
A singleton or a static could be used to resolve the issue.
I am using Zxing to scan the data matrix codes. I imported the zxing from github . When the app starts , the camera is scanning the code repeatedly as long as the camera is placed on a barcode.I want to stop the scanning once the barcode is decoded and I want to perform a task and then again start scanning. I stopped the scanning but unable to start it. Here is what I have done to stop the scanning.
Here is my ViewController.m
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result {
if (!result) return;
// We got a result. Display information about the result onscreen.
NSString *formatString = [self barcodeFormatToString:result.barcodeFormat];
NSString *display = [NSString stringWithFormat:#"Scanned!\n\nFormat: %#\n\nContents:\n%#", formatString, result.text];
//here i called the stop method
[self.capture stop];
//i want to start scanning again ,so i created this method
[self afterScan];
}
Now once the barcode is decoded the camera is stopped. Now i want to implement this method
-(void) afterScan{
// UIAlertVIew " code is decoded "
// store in database
// again start scanning
[self.capture start];
}
The problem is the camera is not starting again.
The start and stop methods in the ZXing are as follows:
- (void)start {
if (self.hardStop) {
return;
}
if (self.delegate || self.luminanceLayer || self.binaryLayer) {
[self output];
}
if (!self.session.running) {
static int i = 0;
if (++i == -2) {
abort();
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session startRunning];
});
}
self.running = YES;
}
- (void)stop {
if (!self.running) {
return;
}
if (self.session.running) {
[self.layer removeFromSuperlayer];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.session stopRunning];
//[self.session startRunning];
});
}
self.running = NO;
}
Could you please help me in solving this issue.
Thanks in advance.
When I did it, I used a BOOL property.
So put one in your view controller like this:
#property (nonatomic, assign) BOOL hasScannedResult;
Then you need an if() condition check to ensure your method doesn't get called repeatedly.
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result {
if(self.hasScannedResult == NO)
{
self.hasScannedResult = YES;
// do something with result
}
}
Now when you want to scan again, reset the BOOL flag:
-(void)startScan
{
// reset BOOL flag to enable scanning
self.hasScannedResult = NO;
// open the scanner
}
I stopped the camera by calling the [capture stopReading];
and I started it again by calling the [capture startReading];
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...