iOS Logic Unit Test target crashes after unit test passes with NSTimer - ios

I want a timer class that can post messages to a delegate when there are 1/2/3 seconds to go.
My test target consistently crashes.
iOS logic unit test target.
Tests class that times a duration using a repeating NSTimer
One test with no asserts. The test passes, but then the target crashes with:
/Developer/Tools/RunPlatformUnitTests.include: line 415: 770 Bus error "${THIN_TEST_RIG}" "${OTHER_TEST_FLAGS}" "${TEST_BUNDLE_PATH}"
It seems to me that it's some kind of memory allocation error, but I can't figure out what I'm missing. The problem is associated with the stop timer routine somehow. It's only when the timer runs out that the target crashes.
Things I've tried
Build and Analyze - no errors reported
Remove -framework and UIKit from the linker flags
Removing dealloc - this has no effect
Test Code
-(void)testGivenThreeSecondDurationAtOneSecondDelegateShouldBeToldToShowGreenCard {
JGTimerController *timer = [JGTimerController timerWithDurationValue:1 delegate:nil];
[timer startTimer];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.1]];
}
Class Code
#interface JGTimerController : NSObject {
NSNumber *duration;
NSTimer *timer;
id <NSObject, JGTimerControllerDelegate> _delegate;
}
#property (nonatomic, retain) NSNumber *duration;
... public methods...
#end
#implementation JGTimerController
#synthesize duration;
+(JGTimerController *)timerWithDurationValue:(NSUInteger)durationValue delegate:(id <JGTimerControllerDelegate>)delegate_ {
JGTimerController *instance = [[[JGTimerController alloc] initWithDurationValue:durationValue delegate:delegate_] autorelease];
return instance;
}
-(JGTimerController *)initWithDurationValue:(NSUInteger)durationValue delegate:(id <JGTimerControllerDelegate>)delegate_ {
self = [super init];
timer = nil;
[self setDurationValue:durationValue];
_delegate = delegate_;
return self;
}
-(NSUInteger)durationValue {
NSNumber *result = [self duration];
return result ? [result intValue] : 0;
}
-(void)setDurationValue:(NSUInteger)value_ {
[self setDuration:[NSNumber numberWithInt:value_]];
}
-(BOOL)stopTimerAtZeroDuration:(NSTimer *)timer_ {
if ([self durationValue] == 0) {
[self stopTimer];
return YES;
}
return NO;
}
-(void)startTimer {
if ([self stopTimerAtZeroDuration:nil])
return;
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerDidCountDownByASecond:) userInfo:nil repeats:YES];
}
-(void)stopTimer {
if ([self durationValue] == 0 && [_delegate conformsToProtocol:#protocol(JGTimerControllerDelegate)])
[_delegate showRedCard];
[timer invalidate];
[timer release];
}
-(BOOL)timerIsRunning {
return (timer != nil);
}
-(void)timerDidCountDownByASecond:(NSTimer *)timer_ {
[self setDurationValue:[self durationValue] - 1];
[self stopTimerAtZeroDuration:timer_];
}
-(void)dealloc {
[_delegate release];
[timer release];
[duration release];
[super dealloc];
}
#end

There should be no [_delegate release] in dealloc because you did not retain it.

Likewise, timer should not be released. NSTimer is like every other NSObject, if you did not alloc, copy or retain, you do not need to release.

Related

Read data from NSOperation subclass to multiple viewcontrollers

I will explain scenario.
I have a NSOperation subclass. In this class , I am reading data from multiple bluetooth devices.
I am creating an object of NSOperation class in ViewController A and get data using delegate methods in NSoperation subclass.
Now, I want to read data from Viewcontroller B without creating an object of NSoperation.
Please check my NSOperation Subclass
NOPerationSubclass.h
`
#protocol NOPerationSubclassDelegate`;
#interface NOPerationSubclass : NSOperation{
BOOL executing;
BOOL finished;
}
#property id<NOPerationSubclassDelegate> delegate;
- (id)initWithConnectDevice:(ConnectDevice *)cDevice toPeripheral:(CBPeripheral *)peripheral;
#end
#protocol NOPerationSubclassDelegate
-(void)updateUIFromOperation:(NOPerationSubclass *)operation;
#end
NOPerationSubclass.m
- (id)initWithConnectDevice:(ConnectDevice *)cDevice toPeripheral:(CBPeripheral *)peripheral{
if (self = [super init]) {
executing = NO;
finished = NO;
self.connectDevice = cDevice;
[self.connectDevice setDelegate:self];
self.connectedPeripheral = peripheral;
dataDic = [[NSMutableDictionary alloc] init];
}
return self;
}
-(BOOL)isConcurrent{
return YES;
}
- (BOOL)isExecuting {
return executing;
}
- (BOOL)isFinished {
return finished;
}
-(void) terminateOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (void)start {
#autoreleasepool {
if (self.isCancelled){
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerFired:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] run];
}
-(void)timerFired:(id)sender{
if (self.isCancelled){
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[connectDevice calldiscoverServicesForPeripheral:connectedPeripheral];
}
-(void)getDataFromPeripheral:(CBPeripheral *)peripheral Data:(NSString *)data{
[dataDic setValue:[peripheral.identifier UUIDString] forKey:#"identifier"];
[dataDic setValue:data forKey:#"data"];
[[[AppDelegate app] devicesDataArray] addObject:dataDic];
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(updateUIFromOperation:) withObject:dataDic waitUntilDone:NO];
NSLog(#"PERIPHERAL DATA::+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++%#",peripheral.name);
}
And, I am calling this NSOpeartion class from ViewController A like this
NOPerationSubclass *queue = [[NOPerationSubclass alloc] initWithConnectDevice:connectDevices toPeripheral:peripheral];
queue.delegate = self;
[[[AppDelegate app] mainOperationQueue] addOperation:queue];
You can use a shared instance class, this is what I always do:
Database.h
#import <Foundation/Foundation.h>
#interface Database : NSObject
#property (nonatomic, readonly) NSArray* myTable;
+(Database*) sharedInstance;
#end
Database.m
#import "Database.h"
#implementation Database
Database* _db = nil;
+(Database*) sharedInstance {
if (!_db)
_db = [[Database alloc] init];
return _db;
}
-(id) init {
self = [super init];
// Do loading here
return self;
}
#end
Then whenever you want to access the data:
[Database sharedInstance].myTable;

Implementing a strobe function

So I am trying to create a LED strobe light and I have managed to make a on/off switch for the light. Here is my code:
#implementation ViewController
- (void) setTorchOn:(BOOL)isOn
{
AVCaptureDevice* device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
[device lockForConfiguration:nil];
[device setTorchMode:isOn ? AVCaptureTorchModeOn : AVCaptureTorchModeOff];
[device unlockForConfiguration];
}
-(IBAction)changedSate:(id)sender {
UISwitch *switchValue = (UISwitch*)sender;
[self setTorchOn:[switchValue isOn]];
I was wondering if anyone could help me with this part.
Just make a loop that continuously turns the torch on and off. The type of loops depends on how you wish this to be implemented.
I think you should use the NSTimer class to repeatedly toggle the torch. There are other ways, but just do not loop with a sleep() call.
// Have an NSTimer* timer and BOOL torchOn and volatile BOOL stopStrobe property in your class...
- (void) startFlashing{
self.timer = [[NSTimer alloc] initWithFireDate:[NSDate timeInvervalSinceNow: 0] interval:0.1 target:self selector:#selector(toggleTorch) userInfo:nil repeats:YES];
}
- (void) toggleTorch{
if (stopStrobe){
[self.timer invalidate];
}
torchOn = !torchOn
[self setTorchOn:torchOn];
}
// Set stopStrobe to YES elsewhere in your program when you want it to stop.
is probably what you're looking for.
UPDATE: I know this isn't what you originally asked, but I know it's often best to learn by example, so here is the full example of using this (untested):
#interface ViewController()
#property(nonatomic) BOOL torchOn;
#property(atomic) BOOL stopStrobe;
#end
#implementation ViewController
- (id) init{
self = [super init];
if (self){
self.torchOn = NO;
self.stopStrobe = NO;
}
}
- (void) setTorchOn:(BOOL)isOn
{
AVCaptureDevice* device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
[device lockForConfiguration:nil];
[device setTorchMode:isOn ? AVCaptureTorchModeOn : AVCaptureTorchModeOff];
[device unlockForConfiguration];
}
- (void) toggleTorch{
if (stopStrobe){
[self.timer invalidate];
}
self.torchOn = !self.torchOn
[self setTorchOn:self.torchOn];
}
- (void) startFlashing{
self.timer = [[NSTimer alloc] initWithFireDate:[NSDate timeInvervalSinceNow: 0] interval:0.1 target:self selector:#selector(toggleTorch) userInfo:nil repeats:YES];
}
-(IBAction)changedSate:(id)sender {
UISwitch *switchValue = (UISwitch*)sender;
if ([switchValue isOn]{
self.stopStrobe = NO;
[self startFlashing];
}
else{
[self.stopStrobe = YES];
}
}
This will start the flashing whenever you turn the switch on and stop it once you turn the switch off.

Use of delegates in NSOperation

I am trying to make use of CLLocationManager in an NSOperation. As part of this I require the ability to startUpdatingLocation then wait until a CLLocation is received before completing the operation.
At present I have done the following, however the delegate method never seems to be called. Please can someone advise what the issue is?
- (void)main
{
#autoreleasepool {
if (self.isCancelled)
return;
// Record the fact we have not found the location yet
shouldKeepLooking = YES;
// Setup the location manager
NSLog(#"Setting up location manager.");
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
locationManager.delegate = self;
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[locationManager startUpdatingLocation];
while (shouldKeepLooking) {
if (self.isCancelled)
return;
// Do some other logic...
}
}
}
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
// None of this ever seems to be called (despite updating the location)
latestLocation = [locations lastObject];
[manager stopUpdatingLocation];
shouldKeepLooking = NO;
}
Going back to the runloop discussion, this is how I generally solve that in my base NSOperation implementation:
// create connection and keep the current runloop running until
// the operation has finished. this allows this instance of the operation
// to act as the connections delegate
_connection = [[NSURLConnection alloc] initWithRequest:[self request]
delegate:self];
while(!self.isFinished) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
}
I key off of isFinished, which I keep updated through setters for isCancelled and isFinished. Here's the isCancelled setter as an example:
- (void)setIsCancelled:(BOOL)isCancelled {
_isCancelled = isCancelled;
if (_isCancelled == YES) {
self.isFinished = YES;
}
}
That said, I second some of the questions about why this is necessary. If you don't need to kick something off until a location is found, why not just fire up your location manager on the main thread, wait for the appropriate delegate callback and then kick off the background operation?
Update: updated solution
While the original answer generally stands, I've fully implement a solution and it does require a slight change to how you manage the run loop. That said, all code is available on GitHub - https://github.com/nathanhjones/CLBackgroundOperation. Here is a detailed explanation of the approach.
Tl;dr
Change
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
to
[[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes
beforeDate:[NSDate distantFuture]];
Details
Within your operations interface define the following three properties. We'll be indicating that these operations are concurrent thus we'll manage their state manually. In the solution on GitHub these are part of NJBaseOperation.
#property(nonatomic,assign,readonly) BOOL isExecuting;
#property(nonatomic,assign,readonly) BOOL isFinished;
#property(nonatomic,assign,readonly) BOOL isCancelled;
Within your operations implementation you'll want to make those readwrite like so:
#interface NJBaseOperation ()
#property(nonatomic,assign,readwrite) BOOL isExecuting;
#property(nonatomic,assign,readwrite) BOOL isFinished;
#property(nonatomic,assign,readwrite) BOOL isCancelled;
#end
Next, you'll want to synthesize the three properties you defined above so that you can override the setters and use them to manage your operations state. Here's what I generally use, but sometimes there are some additional statements added to the setIsFinished: method depending on my needs.
- (void)setIsExecuting:(BOOL)isExecuting {
_isExecuting = isExecuting;
if (_isExecuting == YES) {
self.isFinished = NO;
}
}
- (void)setIsFinished:(BOOL)isFinished {
_isFinished = isFinished;
if (_isFinished == YES) {
self.isExecuting = NO;
}
}
- (void)setIsCancelled:(BOOL)isCancelled {
_isCancelled = isCancelled;
if (_isCancelled == YES) {
self.isFinished = YES;
}
}
Lastly, just so that we don't have to manually send the KVO notifications we'll implement the following method. This works because our properties are named isExecuting, isFinished and isCancelled.
+ (BOOL) automaticallyNotifiesObserversForKey:(NSString *)key {
return YES;
}
Now that the the operations foundation is taken care of it's time to knockout the location stuff. You'll want to override main and within it fire up your location manager and instruct the current run loop to keep running until you tell it otherwise. This ensures that your thread is around to receive the location delegate callbacks. Here's my implementation:
- (void)main {
if (_locationManager == nil) {
_locationManager = [[CLLocationManager alloc] init];
_locationManager.delegate = self;
_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
[_locationManager startUpdatingLocation];
}
while(!self.isFinished) {
[[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes
beforeDate:[NSDate distantFuture]];
}
}
You should receive a delegate callback at which point you can do some work based on location and then finish the operation. Here's my implementation that counts to 10,000 and then cleans up.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
NSLog(#"** Did Update Location: %#", [locations lastObject]);
[_locationManager stopUpdatingLocation];
// do something here that takes some length of time to complete
for (int i=0; i<10000; i++) {
if ((i % 10) == 0) {
NSLog(#"Loop %i", i);
}
}
self.isFinished = YES;
}
The source on GitHub includes a dealloc implementation, which simply logs that it's being called and also observes changes to the operationCount of my NSOperationQueue and logs the count - to indicating when it drops back to 0. Hope that helps. Let me know if you've got questions.
I think you have two options.
Create a separate thread, with its own run loop, for location services:
#import "LocationOperation.h"
#import <CoreLocation/CoreLocation.h>
#interface LocationOperation () <CLLocationManagerDelegate>
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation LocationOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self performSelector:#selector(main) onThread:[[self class] locationManagerThread] withObject:nil waitUntilDone:NO modes:[[NSSet setWithObject:NSRunLoopCommonModes] allObjects]];
}
- (void)main
{
[self startStandardUpdates];
}
- (void)dealloc
{
NSLog(#"%s", __FUNCTION__);
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
if (executing != _executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (finished != _finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[self stopStandardUpdates];
[super cancel];
[self completeOperation];
}
#pragma mark - Location Manager Thread
+ (void)locationManagerThreadEntryPoint:(id __unused)object
{
#autoreleasepool {
[[NSThread currentThread] setName:#"location manager"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)locationManagerThread
{
static NSThread *_locationManagerThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_locationManagerThread = [[NSThread alloc] initWithTarget:self selector:#selector(locationManagerThreadEntryPoint:) object:nil];
[_locationManagerThread start];
});
return _locationManagerThread;
}
#pragma mark - Location Services
- (void)startStandardUpdates
{
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 500;
[self.locationManager startUpdatingLocation];
}
- (void)stopStandardUpdates
{
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
// do whatever you want with the location
// now, turn off location services
if (location.horizontalAccuracy < 50) {
[self stopStandardUpdates];
[self completeOperation];
}
}
#end
Alternatively, even though you're using an operation, you could just run location services on the main thread:
#import "LocationOperation.h"
#import <CoreLocation/CoreLocation.h>
#interface LocationOperation () <CLLocationManagerDelegate>
#property (nonatomic, readwrite, getter = isFinished) BOOL finished;
#property (nonatomic, readwrite, getter = isExecuting) BOOL executing;
#property (nonatomic, strong) CLLocationManager *locationManager;
#end
#implementation LocationOperation
#synthesize finished = _finished;
#synthesize executing = _executing;
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (void)start
{
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self startStandardUpdates];
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
if (executing != _executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)setFinished:(BOOL)finished
{
if (finished != _finished) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[self stopStandardUpdates];
[super cancel];
[self completeOperation];
}
#pragma mark - Location Services
- (void)startStandardUpdates
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (nil == self.locationManager)
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
self.locationManager.distanceFilter = 500;
[self.locationManager startUpdatingLocation];
}];
}
- (void)stopStandardUpdates
{
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation* location = [locations lastObject];
// do whatever you want with the location
// now, turn off location services
if (location.horizontalAccuracy < 50) {
[self stopStandardUpdates];
[self completeOperation];
}
}
#end
I think I'd be inclined to do the second approach (just making sure that I don't do anything too intensive in didUpdateLocations, or if I did, make sure to do it asynchronously), but both of these approaches appear to work.
Another approach is to keep the run loop alive until the operation is finished:
while (![self isFinished]) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
}
But this doesn't appear to work in conjunction with CLLocationManager, as runUntilDate doesn't immediately return (it's almost as if CLLocationManager is attaching its own source to the runloop, which prevents it from exiting). I guess you could change the runUntilDate to something a little closer than distantFuture (e.g. [NSDate dateWithTimeIntervalSinceNow:1.0]). Still, I think it's just as easy to run this operation start location services on the main queue, like the second solution above.
Having said that, I'm not sure why you would want to use location manager in an operation at all. It's already asynchronous, so I would just start the location manager from the main queue and call it a day.
UIWebView with UIWebViewDelegate method callbacks in an NSOperation
A server I wanted to grab a URL from a server that changes values based upon JavaScript execution from various browsers. So I slapped a dummy UIWebView into an NSOperation and use that to grab out the value I wanted in the UIWebViewDelegate method.
#interface WBSWebViewOperation () <UIWebViewDelegate>
#property (assign, nonatomic) BOOL stopRunLoop;
#property (assign, nonatomic, getter = isExecuting) BOOL executing;
#property (assign, nonatomic, getter = isFinished) BOOL finished;
#property (copy, nonatomic, readwrite) NSURL *videoURL;
#property (strong, nonatomic) UIWebView *webView;
#end
#implementation WBSWebViewOperation
- (id)init
{
self = [super init];
if (self) {
_finished = NO;
_executing = NO;
}
return self;
}
- (id)initWithURL:(NSURL *)episodeURL
{
self = [self init];
if (self != nil) {
_episodeURL = episodeURL;
}
return self;
}
- (void)start
{
if (![self isCancelled]) {
self.executing = YES;
[self performSelector:#selector(main) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO modes:[[NSSet setWithObject:NSRunLoopCommonModes] allObjects]];
} else {
self.finished = YES;
}
}
- (void)main
{
if (self.episodeURL != nil) {
NSURLRequest *request = [NSURLRequest requestWithURL:self.episodeURL];
UIWebView *webView = [[UIWebView alloc] init];
webView.delegate = self;
[webView loadRequest:request];
self.webView = webView;
}
}
#pragma mark - NSOperation methods
- (BOOL)isConcurrent
{
return YES;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
- (void)completeOperation
{
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[self.webView stopLoading];
[super cancel];
[self completeOperation];
}
#pragma mark - UIWebViewDelegate methods
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *episodeVideoURLString = [webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('playerelement').getAttribute('data-media')"];
NSURL *episodeVideoURL = [NSURL URLWithString:episodeVideoURLString];
self.videoURL = episodeVideoURL;
if ([self.delegate respondsToSelector:#selector(webViewOperationDidFinish:)]) {
[self.delegate webViewOperationDidFinish:self];
}
[self completeOperation];
}
#end
Its going to call the delegate method in the same operation queue as main is running in. And NSOperation queues are serial by default. Your while loop is just spinning forever (because the operation is never cancelled) and the call to your delegate method is sitting in the queue behind it never able to run.
Get rid of the while loop entirely and let the operation finish. Then when the delegate method is called, if it's cancelled discard the result by returning.

Stop gyro updates

I cant work out to stop the Gyro updates in my Cocos2D game,
I have got the following code in my init: ` //gyro
motionManager = [[CMMotionManager alloc] init];
referenceAttitude = nil;
[motionManager startGyroUpdates];
timer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(doGyroUpdate)
userInfo:nil
repeats:YES];
Then in the gyro update itself I check when the progress is more than 100% if (progress > 100) {
[self pauseSchedulerAndActions];
[self stopGyroUpdates];
Then:
- (void)stopGyroUpdates{
NSLog(#"Stop gyro update");
}
but it keeps checking... So the code within the if statement keeps getting called.
I found the solution with the following code: `
-(void) callgyro:(int)gyroprocess {
NSLog(#"%i", gyroprocess);
if (gyroprocess < 100 ) {
motionManager = [[CMMotionManager alloc] init];
referenceAttitude = nil;
[motionManager startGyroUpdates];
timertwee = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(doGyroUpdate)
userInfo:nil
repeats:YES];
}
else {
[motionManager stopGyroUpdates];
[timertwee invalidate];
NSLog(#"Gyro moet stoppen!");
}`
and within the gyro update itself:
if (progress > 100) {
[self callgyro:progress];
[self.timer invalidate];
self.timer = nil;
[Blikje setImage:[UIImage imageNamed:#"pop3.png"]];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setFloat:numhundreds forKey:#"score"];
progress = 0;
[self stopGyroUpdates];
}
Like rickster said, you need to call stopGyroUpdates on the CMMotionManager instance.so you need to create an instance variable or property in the interface implementation.
In your .m file where your declare the interface:
#interface ViewController ()
#property (strong, nonatomic) CMMotionManager *motionManager;
#end
Then initialize it as you require
motionManager = [[CMMotionManager alloc] init];
Then when you need to stop updates you call
[self.motionManager stopGyroUpdates]
You need to call stopGyroUpdates on your instance of CMMotionManager, not (or in addition to) on self. (This means that instance needs to persist beyond the scope if the method in which you call startGyroUpdates -- say, in a property or ivar -- if it doesn't already.)

How to properly open and close a NSStream on another thread

I have an application that connects to a server using NSStream on another thread. The application also closes the connection should the user decide to log out. The problem is that I am never able to successfully close the stream or the thread upon having the user disconnect. Below is my code sample on how I approach creating a thread for my network and trying to close the stream:
+ (NSThread*)networkThread
{
static NSThread *networkThread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
networkThread = [[NSThread alloc] initWithTarget:self selector:#selector(networkThreadMain:) object:nil];
[networkThread start];
});
return networkThread;
}
+ (void)networkThreadMain:(id)sender
{
while (YES)
{
#autoreleasepool {
[[NSRunLoop currentRunLoop] run];
}
}
}
- (void)scheduleInThread:(id)sender
{
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream open];
}
- (void)closeThread
{
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[inputStream release];
inputStream = nil;
}
Call made when trying to connect the inputstream:
[self performSelector:#selector(scheduleInThread:) onThread:[[self class] networkThread] withObject:nil waitUntilDone:YES];
Any advice is greatly appreciated.
The way you're mixing static and instance variables is confusing. Are you married to doing it that way? If you put this inside an NSOperation and ran it using an NSOperationQueue I think you'd get much cleaner encapsulation. The operation will manage its own async thread so you don't have to. Also, I highly recommend using ARC if you can.
A few notes:
Make sure to set the stream's delegate and handle delegate events. You should probably do that inside the operation (make the operation the delegate) and close the stream and finish the operation when necessary.
There may be other failure conditions for the stream besides NSStreamStatusClosed, such as NSStreamStatusNotOpen, etc. You will probably need to add additional handling, which can be done by listening to the delegate methods.
Your code is probably not working right mainly because your while loop runs the runloop forever. You have to have conditions in which to break out. NSOperation gives you some pretty good standardized ways of doing this.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface AsyncStreamOperation : NSOperation
#end
NS_ASSUME_NONNULL_END
#import "AsyncStreamOperation.h"
#interface AsyncStreamOperation ()
#property (atomic, strong) AsyncStreamOperation *config;
#property (atomic, strong) NSInputStream *stream;
#property (atomic, assign, getter=isExecuting) BOOL executing;
#property (atomic, assign, getter=isFinished) BOOL finished;
#end
#implementation AsyncStreamOperation
#synthesize executing = _executing;
#synthesize finished = _finished;
- (instancetype)initWithStream:(NSInputStream *)stream
{
self = [super init];
if(self) {
_stream = stream;
}
return self;
}
- (BOOL)isAsynchronous
{
return YES;
}
- (BOOL)isExecuting
{
#synchronized (self) {
return _executing;
}
}
- (void)setExecuting:(BOOL)executing
{
#synchronized (self) {
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (BOOL)isFinished
{
#synchronized (self) {
return _finished;
}
}
- (void)setFinished:(BOOL)finished
{
#synchronized (self) {
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
}
- (void)start
{
// Get runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// Schedule stream
[self.stream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[self.stream open];
// Loop until finished
// NOTE: If -cancel is not called, you need to add your own logic to close the stream so this loop ends and the operation completes
while(self.executing && !self.finished && !self.cancelled && self.stream.streamStatus != NSStreamStatusClosed) {
#autoreleasepool {
// Maximum speed once per second or CPU goes through the roof
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
}
}
self.executing = NO;
self.finished = YES;
}
- (void)cancel
{
[super cancel];
[self.stream close];
self.executing = NO;
self.finished = YES;
}
#end

Resources