I am having a lot of trouble wrapping my head around the best way to use blocks. I am trying to retrieve pedometer data, and the method of accessing the data is a block...
[self.pedometer queryPedometerDataFromDate:yesterday
toDate:midnightOfToday
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Pedometer is NOT available.");
}
else {
NSLog(#"Steps %#", pedometerData.numberOfSteps);
yesterdaysNumbersLabel.text = [pedometerData.numberOfSteps stringValue];
[pedometerDictionary setValue:[pedometerData.numberOfSteps stringValue] forKey:#"2"];
}
});
}];
Using the above code I am able to get the data, log the data, and update the label on the screen, But I can't figure out how to set the data into an array or dictionary so I can do something else with it.
I understand why the arrays and dictionaries are always null... the blocks are running on a different thread and I am accessing them before the blocks have completed.
Can someone help me get through my head how to do something more with the data.
Update 1:
Right now I have this in .h
#property (strong, atomic) NSMutableDictionary *pedometerDictionary;
and I am synthesizing it in .m and I call this...
[self getNumbersForYesterday];
NSLog(#"Dictionary: %#", pedometerDictionary);
...which runs the above function and immediately tries to log the result. And like I said, I understand all the reasons it is NOT working. I just need to figure out how to change what i am doing to get it working.
Update 2:
This is in .h
#property (strong, atomic) NSMutableDictionary *pedometerDictionary;
and this is in .m
#synthesize pedometerDictionary;
- (id)init {
self = [super init];
if (self != nil) {
self.pedometerDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
and I am using it like this.
[self getNumbersForYesterday];
NSLog(#"Dictionary: %#", self.pedometerDictionary);
to call this.
- (void)getNumbersForYesterday {
[self.pedometer queryPedometerDataFromDate:yesterday
toDate:midnightOfToday
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Pedometer is NOT available.");
}
else {
NSLog(#"Steps %#", pedometerData.numberOfSteps);
yesterdaysNumbersLabel.text = [pedometerData.numberOfSteps stringValue];
[self.pedometerDictionary setValue:[pedometerData.numberOfSteps stringValue] forKey:#"2"];
}
});
}];
}
If I just wanted to keep all the work in the block I would be fine. What I have come to understand is that since blocks are asynchronous, I am trying to NSLog my dictionary, and the block isn't finished running yet. So, my dictionary is still NULL.
Dollars to donuts, your pedometerDictionary was never created in the first place (or it was, but the declaration isn't in a useful spot).
I.e. where is your line of code that says pedometerDictionary = [[NSMutableDictionary alloc] init];? And where is pedometerDictionary declared? How did you try to NSLog() values from it?
Also, use setObject:forKey:.
It is also odd that it is named pedometerDictionary. That is evidence that it is either declared as a global (which it shouldn't be), a local variable of whatever method contains the above code (which won't work), or you are declaring and using an instance variable directly.
The issue you are having is not a block timing issue, your dictionary should never be nil at worst it would contain no values.
You need to create your dictionary before using it. The appropriate place would be init method for most objects. If you are creating your object in Interface Builder then the method should be awakeFromNib.
To do something with the dictionary you can use an NSTimer or call a method from queryPedometerDataFromDate block handler. The use of #synchronized() directive is an example of how to keep access to the dictionary from overlapping at the same time in a threaded environment. This is not the case in this particular example as you are dispatching on the main thread and NSTimer also runs on the main thread. But should you go threaded #synchronized() would keep you from overlapping access.
#interface HelloWorld : NSObject
#property (retain, atomic) NSMutableDictionary *pedometerDictionary;
#property (retain, nonatomic) NSTimer *timer;
#end
#implementation HelloWorld
#synthesize pedometerDictionary, timer;
...
- (id)init {
self = [super init];
if (self != nil) {
self.pedometerDictionary = [NSMutableDictionary dictionary];
self.timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(doSomethingInterestingWithDictionary:) userInfo:nil repeats:YES];
}
return self;
}
or
- (void)awakeFromNib {
self.pedometerDictionary = [NSMutableDictionary dictionary];
self.timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:#selector(doSomethingInterestingWithDictionary:) userInfo:nil repeats:YES];
}
...
- (void)getNumbersForYesterday {
[self.pedometer queryPedometerDataFromDate:yesterday
toDate:midnightOfToday
withHandler:^(CMPedometerData *pedometerData, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
NSLog(#"Pedometer is NOT available.");
}
else {
NSLog(#"Steps %#", pedometerData.numberOfSteps);
yesterdaysNumbersLabel.text = [pedometerData.numberOfSteps stringValue];
#synchronized (self) {
[self.pedometerDictionary setValue:[pedometerData.numberOfSteps stringValue] forKey:#"2"];
}
[self doSomethingInterestingWithDictionary:nil];
}
});
}];
}
// Will be called when queryPedometerDataFromDate returns and from a timer every 5 seconds.
- (void)doSomethingInterestingWithDictionary:(NSTimer *)aTimer {
#synchronized (self) {
NSLog(#"My days dictionary: %#", self.pedometerDictionary);
}
}
Related
I have an object like this:
typedef void (^ Completion) (Response *);
// Response class
#interface Response : NSObject {
NSDictionary * kdata;
}
- (id)initWithJson:(NSDictionary *)data;
#property (nonatomic, assign) NSDictionary * data;
#end
#implementation Response
- (id)initWithJson:(NSDictionary *)data { kdata = data; }
- (NSDictionary *) data { return kdata; }
- (void) setData: (NSDictionary *)data { kdata = data; }
- (NSDictionary *) msg { return kdata[#"msg"]; }
#end
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
Response * responseObj = [[Response alloc] initWithJson:json];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) { completion (responseObj); }
});
}
// inside caller method
[X doSomething:^(Response * response) {
NSLog (#"%#", [response msg]);
}
This code will raise error on accessing kdata[#"msg"], even though I'm sure from the debug that the object was initialised properly with a dictionary contains key "msg". When I debug the object, on the watch window, it shows me that the kdata data type keeps changing, from NSArrayM, NSSet, NSDictionary, etc. And its contents also keep changing. I even add retain keyword when calling completion ([responseObj retain]); but still produce error.
But if the code in class X is changed into like this:
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
Response * responseObj = [[Response alloc] initWithJson:json];
if (completion != nil) { completion (responseObj); } // here is the change, no more switching to main thread
}
// inside caller method - no change here
[X doSomething:^(Response * response) {
NSLog (#"%#", [response msg]);
}
The code works perfectly. Why is that happened? This is built in Xcode without ARC.
EDIT: someone mentioned about the init. This is my mistake that what was written above is not exactly my code, and I copy the init method wrong. This is my init method:
- (instancetype) initWithData:(NSDictionary *)freshData {
NSParameterAssert(freshData); // make sure not nil
self = [super init];
if (self) {
kdata = freshData;
}
return self;
}
The problem is the object get's released right when you call the 'async' .
The way you declared your object is added to the autorelease pool since the control does not wait for 'async' to complete and the control return's by reaching the end of function 'doSomething' and releasing it's local objects which were added to the autorelease pool, and after that the memory location is used for other data and that's what you see confusing data.
I think by adding the __block specifier in front of your declaration you instruct the code to capture this object in following blocks strongly and release it when the block finished executing. Give it a try.
// inside a networking class X implementation
- (void) doSomething:(completionBlock)completion {
NSDictionary * json = // get from networking function, which will always have key "msg".
__block Response * responseObj = [[Response alloc] initWithJson:json];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion != nil) { completion (responseObj); }
});
}
- (id)initWithJson:(NSDictionary *)data { kdata = data; }
You need call supers init here and return self.
Start to learn basics.
I have production code which I see that it crashes once in a while with a EXC_BAD_ACCESS KERN_INVALID_ADDRESS on the block handler. I could for the life of me figure out what is wrong with my code. I have tried to reproduce this and could not in my controlled environment. Here is the stripped out and cleaned up code :
Code Snippet :
typedef void (^TestCallBackHandler)(NSString* location, NSError* error);
#interface _TestClass : NSObject
#property (nonatomic, copy) TestCallBackHandler handler;
#property (nonatomic, strong)NSTimer *endTimer;
- (void)fireOneOff:(TestCallBackHandler)handler;
#end
#implementation _TestClass
- (void)fireOneOff:(TestCallBackHandler)handler
{
_handler = handler;
NSLog(#"** New %p %# Incoming %p, %# Ours %p %#",self,self,handler, handler, _handler, _handler);
_endTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(_stop) userInfo:nil repeats:NO];
}
- (void)dealloc
{
NSLog(#"%# Dealloced",self);
_handler = nil;
}
- (void)_stop
{
NSLog(#"** Stopping ? %#",self);
if (_handler)
_handler([NSString stringWithFormat:#"Hello %#",[NSDate date]], nil);
}
#end
The calling code in my class is defined as :
#property (nonatomic, strong)_TestClass *testClassInstance;
and called like this :
- (void)startTestClass {
_testClassInstance = [[_TestClass alloc] init];
[_testClassInstance fireOneOff:^(NSString *newString, NSError *error) {
NSLog(#"Got new String! %#",newString);
_testClassInstance = nil;
}];
}
Few things to note :
The startTestClass can be called multiple times
The app wakes up in the background and this can be created.
Any pointers, help highly appreciated.. I just cant put a finger in this code and say that is what is wrong. Please help!
Two hints which occur a little bit strange to me:
First: why are you setting the object to nil in it's own block.
- (void)startTestClass {
_testClassInstance = [[_TestClass alloc] init];
[_testClassInstance fireOneOff:^(NSString *newString, NSError *error) {
NSLog(#"Got new String! %#",newString);
_testClassInstance = nil;
}];
}
Second your object may be released already and the NSTimer is trying to execute a method on a release object
- (void)fireOneOff:(TestCallBackHandler)handler
{
_handler = handler;
NSLog(#"** New %p %# Incoming %p, %# Ours %p %#",self,self,handler, handler, _handler, _handler);
_endTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(_stop) userInfo:nil repeats:NO];
}
I also dont get what the code does exactly and why. I believe there would be an easier and better maintainable solution for what you are achieving in this class.
If you want to have more information about EXC_BAD_ACCESS, then you can turn of NSZombies. In Xcode go to Product > Scheme > Edit Scheme and set checked Enable Zombie Objects.
I would like to do the following:
In a class (shared instance) I will have a method that takes as parameters a data object (nsstring) and a delegate. This will create a new background thread and dispatch some calculations on that thread. The thing is that the method may be called hundreds of times with different data and possibly different delegates passed in . I would like the results to go to the correct delegate (I will need to keep the delegates in an array right? or can I just pass them to the background thread as they come and when that thread finishes it will send the result only to that delegate?).
One more thing... all this methods will use a very large data structure (an array with 10000 nsstring objects,they only need to read from it). How do I make sure this is not duplicated on each thread? And is only allocated when needed and deallocated when no thread uses it?
Here is the code I decided to use:
if (!self.dictPasswords) {
// read everything from text
NSString* fileContents = [NSString stringWithContentsOfFile:fileRoot
encoding:NSUTF8StringEncoding error:nil];
// separate by new line
self.dictPasswords = [fileContents componentsSeparatedByCharactersInSet:
[NSCharacterSet newlineCharacterSet]];
}
__weak id<PSPasswordDictionaryVerificationDelegate> wdelegate = delegate;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[wdelegate willBeginPasswordVerificationForPassword:password];
for (NSString *posiblePass in self.dictPasswords) {
if ([password isEqualToString:posiblePass]) {
dispatch_async(dispatch_get_main_queue(), ^{
[wdelegate password:password isInDictionary:YES];
});
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[wdelegate password:password isInDictionary:NO];
});
});
However... after this runs I get a permanent 24MB added to the used memory. I would like to detect when no threads are using the self.DIctPasswords array and deallocate it. It will be read from the file again later if somebody calls this method again...
Thanks for the help guys.
Just let the block capture the delegate.
No need to hold it otherwise
Class
#import <Foundation/Foundation.h>
#protocol ProcessorDelegate;
#interface Processor
- (void)process:(id)data forDelegate:(id<ProcessorDelegate>)delegate;
+ (Processor*)sharedInstance;
#end
#protocol ProcessorDelegate
- (void)processor:(Processor*)processor didProcess:(id)data withResult:(id)result;
#end
#implementation Processor
- (void)process:(id)data forDelegate:(id<ProcessorDelegate>)delegate {
__weak id<ProcessorDelegate> wdelegate = delegate; //capture weak to counter potential cycles
__weak id wself = self;
dispatch_async(dispatch_get_global_queue(0,0), ^{
NSLog(#"WORK");
id result = data; //TODO
[wdelegate processor:wself didProcess:data withResult:result];
});
}
+ (Processor*)sharedInstance {
static Processor *p = nil;
if(!p) {
p = [[Processor alloc] init];
}
return p;
}
#end
DEMO
#interface Demo : NSObject <ProcessorDelegate>
- (void)doIt;
#end
#implementation Demo
- (void)doIt {
[Processor sharedInstance] process:#"TEST" forDelegate:self];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
Demo *d1 = [[Demo alloc] init];
Demo *d2 = [[Demo alloc] init];
Demo *d3 = [[Demo alloc] init];
Demo *d4 = [[Demo alloc] init];
[d1 doIt];
[d2 doIt];
[d3 doIt];
[d4 doIt];
[[NSRunLoop currentRunLoop] run];
}
}
It seems more appropriate to encapsulate the calculations plus data and delegate in a class of its own. Then you can have an array of those objects in your singleton. You may want to consider using NSOperation here.
OMT: Simply pass this large array as a pointer (to each calculation object) and use regular strong properties (not copy) if you're using any properties at all, saving a reference to it using an ivar is fine too. One concern is that this data-structure must be read-only; otherwise (when you'd modify it in each thread), you'd need some data locking.
I have done it with blocks : a singleton that have all the functions you needs (like an API) and a delegate class
// singleton.h
typedef void (^request_handler_t)(NSData* data);
- (void) foo:(NSString *)str withBlock:(request_handler_t)callback;
// singleton.m
- (void) foo:(NSString *)str withBlock:(request_handler_t)callback;{
MyDelegate *delegate = [MyDelegate delegateWithBlock:callback];
[yourMethodThatNeedDelegate:delegate];
}
// MyDelegate.h
+ (MyDelegate*) delegateWithBlock:(api_request_handler_t)block;
- (void)delegateMethod1;
//Delegate.m
+ (MyDelegate*) requestWithBlock:(api_request_handler_t)block;{
//... alloc init
_callback = block;
}
- (void)delegateMethod1;{
// delegate finished the job
block(myResultingData);
}
// Usage :
[MySingleton singleton] foo:(NSString *)str withBlock:^(NSData *data){
//do something with the async data
}];
I am using UNIRest to make a call and return a JSON object to my app. I have it returning the proper data as a NSDictionary and it logs our perfect. I am now trying to take that data and display it inside of my view. I cannot use my dictionary outside of the callback.
I have been digging around here on StackOverflow for similar results and posts related to variables. I feel it is a scope issue with it being limited to inside of the callback block.
My header file: (UIViewController)
#property (nonatomic, strong) NSDictionary *tideData;
My implementation:
#interface TideDetailViewController ()
#end
#implementation TideDetailViewController
#synthesize tideData;
- (void)viewDidLoad {
[super viewDidLoad];
// tideData = [[NSDictionary alloc] init];
// location is working, I removed it for testing to call a static string for now
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
self.locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[self.locationManager startUpdatingLocation];
NSString *locationQueryURL = #"http://api.wunderground.com/api/XXXXXXXXXXXXX/tide/geolookup/q/43.5263,-70.4975.json";
NSLog(#"%#", locationQueryURL);
[[UNIRest get:^(UNISimpleRequest *request) {
[request setUrl: locationQueryURL];
}] asJsonAsync:^(UNIHTTPJsonResponse *response, NSError *error) {
// This is the asyncronous callback block
self.code = [response code];
NSDictionary *responseHeaders = [response headers];
UNIJsonNode *body = [response body];
self.rawResults = [response rawBody];
// I tried this as self as well
tideData = [NSJSONSerialization JSONObjectWithData:self.rawResults options: 0 error: &error];
// this logs perfectly.
NSLog(#"tideData %#", tideData);
// tried setting it to the instance
//self.tideData = tideData;
}];
// returns null
NSLog(#"tideData outside of call back %#", self.tideData);
// this is where I will be setting label text for now, will refactor once I get it working
// rest of file contents........
I have tried a good amount of items related to scoping, clearly just missing the mark. Any ideas? I have searched setting global variables, etc. Been stuck on this for a bit now.
Thanks,
Ryan
The reason you see nil is because you are logging it too soon: when you call
NSLog(#"tideData outside of call back %#", self.tideData);
the get:asJsonAsync: method has not received the results yet.
You can fix this problem by adding a setter for your property, and adding some special handling to it, like this:
-(void)setTideData:(NSDictionary*)dict {
_tideData = dict;
NSLog(#"tideData outside of call back %#", _tideData);
}
This method will be called from the asynchronous code when you do the tideData = ... assignment.
Try setting the object on main thread:
[self performSelectorOnMainThread:#selector(setTideData:) withObject:[NSJSONSerialization JSONObjectWithData:self.rawResults options: 0 error: &error] waitUntilDone:NO];
- (void)setTideData:(NSDictionary*)dict {
self.tideData = dict;
}
I'm using JSONModel to retrieve data from a simple webservice. I need to get the values of key #"message" into a mutable array.
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
self.dataSource = self;
NSString *conversationid = #"xx";
NSString *callURL = [NSString stringWithFormat:#"http://mydomain.com/inbox_messages.php?conversation=%#", conversationid];
_feed = [[MessageFeed alloc] initFromURLWithString:callURL
completion:^(JSONModel *model, JSONModelError *err)
{
self.messages = [[NSMutableArray alloc]initWithObjects:[_feed.messagesinconversation valueForKey:#"message"], nil];
NSLog(#"messages %#", self.messages);
}];
NSLog(#"test %#", self.messages);
}
The problem I'm experiencing is that while: NSLog(#"messages %#", self.messages); returns all the right data, NSLog(#"test %#", self.messages); returns (null).
The variable is declared in .h of my class as: #property (strong, nonatomic) NSMutableArray *messages;
This is probably a noob question but I'm a beginner and if somebody could give me a pointer in the right direction, I would be very happy.
Your NSLog for self.messages is outside of the completion block. The completion block is called after the data is loaded. The log is called immediately after creating the MessageFeed request. So, of course, the object self.messages is null because the request has not completed.
The solution to this would be to either handle all of your parsing within the completion block, or call another class method to parse the received data.
Your completion handler is being called after your NSLog("test %#", self.messages); is.
Blocks usually happen concurrently and when a certain event has occurred like the completion handler here or sometimes an error handler. By looking at your code you're probably getting something like:
test nil
messages
So your MessageFeed object is being run but it continues through your code and runs the NSLog outside of the completion handler scope first. When your JSON object has downloaded, which happens after, and parses it then runs the completion handler.
- (void)viewDidLoad
{
[super viewDidLoad];
self.delegate = self;
self.dataSource = self;
NSString *conversationid = #"xx";
NSString *callURL = [NSString stringWithFormat:#"http://mydomain.com/inbox_messages.php?conversation=%#", conversationid];
_feed = [[MessageFeed alloc] initFromURLWithString:callURL //this method takes some time to complete and is handled on a different thread.
completion:^(JSONModel *model, JSONModelError *err)
{
self.messages = [[NSMutableArray alloc]initWithObjects:[_feed.messagesinconversation valueForKey:#"message"], nil];
NSLog(#"messages %#", self.messages); // this is called last in your code and your messages has been has been set as an iVar.
}];
NSLog(#"test %#", self.messages); // this logging is called immediately after initFromURLWithString is passed thus it will return nothing
}