Writing iPhone 6 altimeter readings to an array class property inside queue block? - objective-c

New iOS developer here. I've been searching for an answer to this in documentation on blocks and the altimeter, but I'm coming up short. I assume there's some simple thing I'm missing, but can't figure it out.
I have a custom class called PressureSensor. Simplistically speaking, the class has a property:
#property (nonatomic, strong, readwrite) NSMutableArray *pressure;
I load NSNumber values from the altimeter into this array.
The initializer for the class is:
- (instancetype)init
{
self = [super init];
if (self)
{
if (self.altimeterIsAvailable)
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
[self.pressure addObject:altitudeData.pressure];
NSLog(#"Pressure 1: %#", [self.pressure lastObject]);
});
}];
NSLog(#"Pressure 2: %#", [self.pressure lastObject]);
}
}
return self;
}
When I run the app on my phone, I assume that pressure is successfully added to the self.pressure array, because the pressure is printed to the console by the "Pressure 1" line, which accesses the lastObject of self.pressure. However, it seems that these changes don't hold outside this block, as the Pressure 2 line outputs (null) to the console, and it doesn't seem like I can do anything with self.pressure outside this block.
Am I missing something about how blocks work exactly? Do I just need a __block somewhere? I'm completely at a loss here.
Addendum: self.altimeterIsAvailable is defined elsewhere. That part of the code shouldn't have any issues.
EDIT: The error ended up being elsewhere. For future readers who browse this post, the above code should be a perfectly valid way to add to a property array in a block.

This is not an answer but I'd like to mention it.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^ {
...
});
}];
Creating a queue, and dispatch_async to the main queue. It's redundant. You can use NSOperationQueue +mainQueue method for it directly.
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[self.altimeter startRelativeAltitudeUpdatesToQueue:queue withHandler:^(CMAltitudeData *altitudeData, NSError *error) {
...
}];

Related

How to stub method block in Kiwi?

I want to stub a method which takes a block as a parameter using Kiwi. Here is the full explanation with code:
I have a class named TestedClass which has a method testedMethod which dependent on class NetworkClass which calls via AFNetworking to a server, and return its response via block. Translating to code:
#interface TestedClass : NSObject
-(void)testMethod;
#end
-(void)testMethod
{
NetworkClass *networkClass = [[NetworkClass alloc] init];
[networkClass networkMethod:^(id result)
{
// code that I want to test according to the block given which I want to stub
...
}];
}
typedef void (^NetworkClassCallback)(id result);
#interface NetworkClass : NSObject
-(void)networkMethod:(NetworkClassCallback)handler;
#end
-(void) networkMethod:(NetworkClassCallback)handler
{
NSDictionary *params = #{#"param":#", #"value"};
NSString *requestURL = [NSString stringWithFormat:#"www.someserver.com"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURLURLWithString:requestURL]];
NSURLRequest *request = [httpClient requestWithMethod:#"GET" path:requestURL parameters:params];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
handler(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
handler(nil);
}];
[operation start];
}
How can I use Kiwi to stub networkMethod with block in order to unit test testMethod?
UPDATE: Found how to do this in Kiwi, see my answer below.
Here is how you do this in Kiwi:
First, you must dependency inject NetworkClass to TestedClass (if it's not clear how, please add a comment and I'll explain; this can be done as a property for simplicity. This is so that you can operate on a mock object for the NetworkClass)
Then your spec, create the mock for the network class and create your class that you want to unit test:
SPEC_BEGIN(TestSpec)
describe(#"describe goes here", ^{
it(#"should test block", ^{
NetworkClass *mockNetworkClass = [NetworkClass mock];
KWCaptureSpy *spy = [mockNetworkClass captureArgument:#selector(networkMethod:) atIndex:0];
TestedClass testClass = [TestedClass alloc] init];
testClass.networkClass = mockNetworkClass;
[testClass testMethod];
NetworkClassCallback blockToRun = spy.argument;
blockToRun(nil);
// add expectations here
});
});
SPEC_END
To explain what's going on here:
You are creating TestedClass and calling testMethod. However, before that, we are creating something called Spy - its job is to capture the block in the first parameter when networkMethod: is called. Now, it's time to actually execute the block itself.
It's easy to be confused here so I'll emphasize this: the order of calls is important; you first declare the spy, then call the tested method, and only then you're actually calling and executing the block!
This will give you the ability to check what you want as you're the one executing the block.
Hope it helps for other, as it took me quite sometime to understand this flow.

MailCore concurrency support

I'm developing a mail client using the MailCore framework (based on the C library LibEtPan). I'd like to handle the server connection and all the requests in new thread or queue and pushing informations to the main queue for UI updates.
The problem it seems that MailCore variables can't be shared across threads.
#implementation Controller
{
NSOperationQueue *_queue;
CTCoreAccount *_account;
CTCoreFolder *_inbox;
NSArray *_messages;
}
- (id)init
{
// stuff
_queue = [[NSOperationQueue alloc] init];
[_queue addOperationWithBlock:^
{
_account = [[CTCoreAccount alloc] init];
BOOL success = [_account connectToServer:#"imap.mail.com" port:993 connectionType:CTConnectionTypeTLS authType:CTImapAuthTypePlain login:#"me#mail.com" password:#"Password"];
if (success)
{
CTCoreFolder *inbox = [_account folderWithPath:#"INBOX"];
NSArray *messages = [inbox messagesFromSequenceNumber:1 to:0 withFetchAttributes:CTFetchAttrEnvelope];
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
_messages = [messages copy];
// UI updates here
}];
}
}];
// Other stuff
}
Later, for example this method could be called :
- (void)foo
{
[_queue addOperationWithBlock:^
{
CTCoreMessage *message = [_messages objectAtIndex:index];
BOOL isHTML;
NSString *body = [message bodyPreferringPlainText:&isHTML];
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
// UI Updates
}];
}];
}
Here, body is empty because CTCore variables are unable to execute new requests from _queue.
According to this comment, each thread needs is own CTCoreAccount, etc ...
Threads on iOS are supposed to have shared memory. I don't exactly understand why reusing the same CTCoreAccount across threads doesn't work, even if references are used in the LibetPan library.
How to define a unique CTCoreAccount or CTCoreFolder "attached" to a different thread or queue that can be reused multiple times ?
Any advise would be appreciated. Thank you.
The answer has been given by MRonge here.
One way is to create an object that contains both the NSOperationQueue
(with the maxConcurrentOperationCount=1) and the CTCoreAccount. All
work for that account goes through the object, and is only executed on
one thread at a time. Then you can one of these objects for each
account you want to access.

Why isn't multithreading working in this implementation?

Q1: Can I call a method and have it execute on a background thread from inside another method that is currently executing on the main thread?
Q2: As an extension of the above, can I call a method and have it execute on a background thread from inside another method that is currently executing on some other background thread itself?
Q3: And one final question given the above : if I initialize an instance of some object X on some thread (main/background) and then have a method Y, of that object X, executing on some other background thread, can this method Y send messages and update an int property (e.g. of that Object X, or is such communication not possible ?
The reason I'm asking this last question is because I've been going over and over it again and I can't figure what is wrong here:
The following code returns zero acceleration and zero degrees values :
MotionHandler.m
#implementation MotionHandler
#synthesize currentAccelerationOnYaxis; // this is a double
-(void)startCompassUpdates
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
NSLog(#"compass updates initialized");
}
-(int) currentDegrees
{
return (int)locationManager.heading.magneticHeading;
}
-(void) startAccelerationUpdates
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
self.currentAccelerationOnYaxis = motion.userAcceleration.y;
}
];
}
#end
Tester.m
#implementation Tester
-(void)test
{
MotionHandler *currentMotionHandler = [[MotionHandler alloc] init];
[currentMotionHandler performSelectorInBackground:#selector(startCompassUpdates) withObject:nil];
[currentMotionHandler performSelectorInBackground:#selector(startAccelerationUpdates) withObject:nil];
while(1==1)
{
NSLog(#"current acceleration is %f", currentMotionHandler.currentAccelerationOnYaxis);
NSLog(#"current degrees are %i", [currentMotionHandler currentDegrees]);
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
#end
However, the following code returns those values normally :
Tester.m
#interface Tester()
{
CLLocationManager *locationManager;
double accelerationOnYaxis;
// more code..
}
#end
#implementation Tester
- (id) init
{
locationManager=[[CLLocationManager alloc] init];
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
locationManager.delegate=self;
[locationManager startUpdatingHeading];
// more code..
}
-(void) test
{
CMMotionManager *motionManager = [[CMMotionManager alloc] init];
motionManager.deviceMotionUpdateInterval = 0.01;
[motionManager startDeviceMotionUpdatesToQueue:[NSOperationQueue mainQueue]
withHandler:^(CMDeviceMotion *motion, NSError *error)
{
accelerationOnYaxis = motion.userAcceleration.y;
}
];
while(1==1)
{
NSLog(#"current acceleration is %f", accelerationOnYaxis);
NSLog(#"current degrees are %i", locationManager.heading.magneticHeading);
}
}
SomeViewController.m
#implementation SomeViewController
-(void) viewDidLoad
{
[myTester performSelectorInBackground:#selector(test) withObject:nil];
}
What's wrong with the first version? I really want to use that first one because it seems much better design-wise.. Thank you for any help!
Calling performSelectorInBackground:withObject: is the same as if you called the detachNewThreadSelector:toTarget:withObject: method of NSThread with the current object, selector, and parameter object as parameters (Threading Programming Guide). No matter where you call it, a new thread will be created to perform that selector. So to answer your first two questions: yes and yes.
For your final question, as long as this Object X is the same object in both methods, any of X's properties can be updated. But, beware that this can yield unexpected results (ie. see Concurrency Programming Guide). If multiple methods are updating X's property, values can be overwritten or disregarded. But, if you are only updating it from method Y and reading it from all other methods, such problems shouldn't occur.
You should take a look at the Grand Central Dispatch documentation from Apple. It allows you to use multiple threads in a block-based structure.
2 importants function are dispatch_sync() and dispatch_async().
Some examples:
To execute a certain block of code on a background thread and wait until it is finished:
__block id someVariable = nil;
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
someVariable = [[NSObject alloc] init];
});
NSLog(#"Variable: %#", someVariable);
This function modifies the variable someVariable which you can use later on. Please note that the main thread will be paused to wait for the background thread. If that is not what you want, you can use dispatch_async() as follows:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// do some heavy work in the background
NSObject *someVariable = [[NSObject alloc] init];
// notify main thread that the work is done
dispatch_async(dispatch_get_main_queue(), ^{
// call some function and pass someVariable to it, it will be called on the main thread
NSLog(#"Variable: %#", someVariable);
});
});

UIManagedDocument insert objects in background thread

This is my first question on Stack Overflow, so please excuse me if I'm breaking any etiquette. I'm also fairly new to Objective-C/app creation.
I have been following the CS193P Stanford course, in particular, the CoreData lectures/demos. In Paul Hegarty's Photomania app, he starts with a table view, and populates the data in the background, without any interruption to the UI flow. I have been creating an application which lists businesses in the local area (from an api that returns JSON data).
I have created the categories as per Paul's photo/photographer classes. The creation of the classes themselves is not an issue, it's where they are being created.
A simplified data structure:
- Section
- Sub-section
- business
- business
- business
- business
- business
- business
My application starts with a UIViewController with several buttons, each of which opens a tableview for the corresponding section (these all work fine, I'm trying to provide enough information so that my question makes sense). I call a helper method to create/open the URL for the UIManagedDocument, which was based on this question. This is called as soon as the application runs, and it loads up quickly.
I have a method very similar to Paul's fetchFlickrDataIntoDocument:
-(void)refreshBusinessesInDocument:(UIManagedDocument *)document
{
dispatch_queue_t refreshBusinessQ = dispatch_queue_create("Refresh Business Listing", NULL);
dispatch_async(refreshBusinessQ, ^{
// Get latest business listing
myFunctions *myFunctions = [[myFunctions alloc] init];
NSArray *businesses = [myFunctions arrayOfBusinesses];
// Run IN document's thread
[document.managedObjectContext performBlock:^{
// Loop through new businesses and insert
for (NSDictionary *businessData in businesses) {
[Business businessWithJSONInfo:businessData inManageObjectContext:document.managedObjectContext];
}
// Explicitly save the document.
[document saveToURL:document.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success){
if (!success) {
NSLog(#"Document save failed");
}
}];
NSLog(#"Inserted Businesses");
}];
});
dispatch_release(refreshBusinessQ);
}
[myFunctions arrayOfBusinesses] just parses the JSON data and returns an NSArray containing individual businessses.
I have run the code with an NSLog at the start and end of the business creation code. Each business is assigned a section, takes 0.006 seconds to create, and there are several hundred of these. The insert ends up taking about 2 seconds.
The Helper Method is here:
// The following typedef has been defined in the .h file
// typedef void (^completion_block_t)(UIManagedDocument *document);
#implementation ManagedDocumentHelper
+(void)openDocument:(NSString *)documentName UsingBlock:(completion_block_t)completionBlock
{
// Get URL for document -> "<Documents directory>/<documentName>"
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:documentName];
// Attempt retrieval of existing document
UIManagedDocument *doc = [managedDocumentDictionary objectForKey:documentName];
// If no UIManagedDocument, create
if (!doc)
{
// Create with document at URL
doc = [[UIManagedDocument alloc] initWithFileURL:url];
// Save in managedDocumentDictionary
[managedDocumentDictionary setObject:doc forKey:documentName];
}
// If the document exists on disk
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]])
{
[doc openWithCompletionHandler:^(BOOL success)
{
// Run completion block
completionBlock(doc);
} ];
}
else
{
// Save temporary document to documents directory
[doc saveToURL:url
forSaveOperation:UIDocumentSaveForCreating
completionHandler:^(BOOL success)
{
// Run compeltion block
completionBlock(doc);
}];
}
}
And is called in viewDidLoad:
if (!self.lgtbDatabase) {
[ManagedDocumentHelper openDocument:#"DefaultLGTBDatabase" UsingBlock:^(UIManagedDocument *document){
[self useDocument:document];
}];
}
useDocument just sets self.document to the provided document.
I would like to alter this code to so that the data is inserted in another thread, and the user can still click a button to view a section, without the data import hanging the UI.
Any help would be appreciated I have worked on this issue for a couple of days and not been able to solve it, even with the other similar questions on here. If there's any other information you require, please let me know!
Thank you
EDIT:
So far this question has received one down vote. If there is a way I could improve this question, or someone knows of a question I've not been able to find, could you please comment as to how or where? If there is another reason you are downvoting, please let me know, as I'm not able to understand the negativity, and would love to learn how to contribute better.
There are a couple of ways to this.
Since you are using UIManagedDocument you could take advantage of NSPrivateQueueConcurrencyType for initialize a new NSManagedObjectContext and use performBlock to do your stuff. For example:
// create a context with a private queue so access happens on a separate thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
// insert this context into the current context hierarchy
context.parentContext = parentContext;
// execute the block on the queue of the context
context.performBlock:^{
// do your stuff (e.g. a long import operation)
// save the context here
// with parent/child contexts saving a context push the changes out of the current context
NSError* error = nil;
[context save:&error];
}];
When you save from the context, data of the private context are pushed to the current context. The saving is only visible in memory, so you need to access the main context (the one linked to the UIDocument) and do a save there (take a look at does-a-core-data-parent-managedobjectcontext-need-to-share-a-concurrency-type-wi).
The other way (my favourite one) is to create a NSOperation subclass and do stuff there. For example, declare a NSOperation subclass like the following:
//.h
#interface MyOperation : NSOperation
- (id)initWithDocument:(UIManagedDocument*)document;
#end
//.m
#interface MyOperation()
#property (nonatomic, weak) UIManagedDocument *document;
#end
- (id)initWithDocument:(UIManagedDocument*)doc;
{
if (!(self = [super init])) return nil;
[self setDocument:doc];
return self;
}
- (void)main
{
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
[moc setParentContext:[[self document] managedObjectContext]];
// do the long stuff here...
NSError *error = nil;
[moc save:&error];
NSManagedObjectContext *mainMOC = [[self document] managedObjectContext];
[mainMOC performBlock:^{
NSError *error = nil;
[mainMOC save:&error];
}];
// maybe you want to notify the main thread you have finished to import data, if you post a notification remember to deal with it in the main thread...
}
Now in the main thread you can provide that operation to a queue like the following:
MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];
P.S. You cannot start an async operation in the main method of a NSOperation. When the main finishes, delegates linked with that operations will not be called. To say the the truth you can but this involves to deal with run loop or concurrent behaviour.
Hope that helps.
Initially I was just going to leave a comment, but I guess I don't have the privileges for it. I just wanted to point out the UIDocument, beyond the change count offers
- (void)autosaveWithCompletionHandler:(void (^)(BOOL success))completionHandler
Which shouldn't have the delay I've experienced with updating the change count as it waits for a "convenient moment".

Syntax/formatting when nesting objective-c blocks

I'm nesting blocks, and it looks UGGGGLY. Is there a way to write this less ugly? Mostly looking for syntax suggestions, rather than structural, but I'll accept either.
My block factory method,
-(NSImage *(^)(CGFloat size, BOOL preview))resizeBlock {
return (NSImage *(^)(CGFloat size, BOOL preview))[[^(CGFloat size, BOOL preview){
// image-resizing code
return [[[NSImage alloc] init] autorelease];
} copy] autorelease];
}
Which is called from a number of functions similar to this,
-(void)queueResize:(CGFloat)targetSize toView:(NSImageView *)targetView {
NSImage*(^sizeBlock)(CGFloat,BOOL) = [self resizeBlock];
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^(void) {
NSImage *previewImage = (NSImage*)sizeBlock(targetSize,YES);
targetView.image = previewImage;
}];
[queue addOperation:bo];
}
queue is an NSOperationQueue object. It won't compile without all the (ugly ugly) casting. Amidoinitrite?
Edit:
As per Dave DeLong's answer, and http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/, I changed the line
targetView.image = previewImage;
to be,
[targetView performSelectorOnMainThread:#selector(setImage:) withObject:previewImage waitUntilDone:YES];
Use typedef:
typedef NSImage *(^KWResizerBlock)(CGFloat size, BOOL preview);
This makes your code become:
- (KWResizerBlock) resizeBlock {
KWResizerBlock block = ^(CGFloat size, BOOL preview){
// image-resizing code
return [[[NSImage alloc] init] autorelease];
};
return [[block copy] autorelease];
}
-(void)queueResize:(CGFloat)targetSize toView:(NSImageView *)targetView {
KWResizerBlock sizeBlock = [self resizeBlock];
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSImage *previewImage = sizeBlock(targetSize, YES);
//do something with previewImage
}];
[queue addOperation:bo];
}
One word of caution:
Your NSBlockOperation is going to be executing on a thread that's not the main thread, and so it is unsafe to manipulate any UI element from within that context. If you need to put the previewImage onto the UI, then you should dispatch_async() back to the main thread (or something functionally equivalent).
It may work right now, but it is strongly discourage and can lead to undefined behavior.