Process :
App is on the home view controller and is requesting data on API to set an NSObject property. The request is processing on a private method.
User change the view controller to a second view controller (the request is still processing asynchronously)
The second view controller is loaded
The request is ending and app return EXC_BAD_ACCESS when it setting the object property
It seems the object has not the correct memory access.
I would like than the user can switch view controller, even if there is a request pending, and the application doesn't crash.
I don't want to block the user on the view controller during loading.
User.h
#interface User : NSObject
[...]
#property (nonatomic) NSString *notification;
[...]
-(void) methodName;
#end
User.m
-(void) methodName{
//there is code around but useless for this problem
[...]
NSError *error;
NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
self.notification = [[dict objectForKey:#"infos"] objectForKey:#"notification"]; //Here is the EXC_BAD_ACCESS
[...]
}
MyController.m
#interface MyController ()
#end
User *user;
#implementation HomeCVViewController
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
user = [User new];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[user someMethod];
dispatch_async(dispatch_get_main_queue(), ^{
[...]//some code
});
});
}
#end
EDIT :
I just put #property (nonatomic) User *user; in MyController.h and remove User *user; in MyController.m . The user is not deallocated and there is no crash.
Verify that [dict objectForKey:#"infos"] is not NSNull - Crash
can be here.
Other code looks OK.
Also add -(void)deallocto your object and put a
break point there to verify that the object is not being released
before the assignment.
Check your output at the NSError
NSError *error;
NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"%#", error.localizedDescription); <----- HERE
self.notification = [[dict objectForKey:#"infos"] objectForKey:#"notification"];
When you try to convert from/to JSON, improper JSON structure will cause it to crash. See the error for more information.
Usually, this type of error appears when object deallocated but your code try to access it's data
so, first of all, check how do you hold your User entity in memory, throw, for example, property:
#property (nonatomic, strong) User *u;
and make sure it is still in memory when operation completed:
Hope this will help you.
Instead of
self.notification = [[dict objectForKey:#"infos"] objectForKey:#"notification"]; //Here is the EXC_BAD_ACCESS
write
NSDictionary* infoDict = dict [#"infos"];
id notification = infoDict [#"notification"];
self.notification = notification;
set a breakpoint, and examine each of infoDict, notification, and self. That takes the guesswork out of it. Right now you don't even know which of these three assignments goes wrong.
And since you have lots of lines of code that are in your opinion bug free and irrelevant, chances are good that the actual bug is hiding somewhere in those lines. Remember: There is a bug in your code. Assuming that any of your code is bug free is daft, because you already know it isn't!
Related
I'm getting occasional crashes in my GCDWebServer handlers, which access mutable dictionaries. The GCDWebServer ReadMe says the handlers "are executed on arbitrary threads within GCD so special attention must be paid to thread-safety and re-entrancy," and I think that's my problem. Is there a best practice or recommended pattern for accessing mutable properties of the parent object from the handlers?
I don't know if I can synchronize between threads since I'm not creating the handler threads. Also, I imagine I could use an asynchronous handler, then call a method on the main thread from there, then do my work in that method, then send the response, but that seems more complicated and less efficient than necessary.
Here's a simplified version of my code:
#property (nonatomic, strong) NSMutableDictionary *data;
#property (nonatomic, strong) GCDWebServer *webServer;
- (void)setup {
self.data = [NSMutableDictionary dictionary];
[self.data setObject:#"1" forKey:#"status"];
self.webServer = [[GCDWebServer alloc] init];
[self.webServer addHandlerForMethod:#"GET" path:#"/getStatus.txt" requestClass:[GCDWebServerRequest class] processBlock:^(GCDWebServerRequest *request) {
return [self handleStatusRequest:request];
}];
}
- (GCDWebServerDataResponse *)handleStatusRequest:(GCDWebServerRequest *)request {
NSString *status = [self.data objectForKey:#"status"]; // crash here
return [GCDWebServerDataResponse responseWithText:status];
}
Are you mutating your data dictionary after creating it? If so that would explain the crashes.
You must prevent concurrent access to your data dictionary by using locks. The easiest way is through GCD e.g.
#property dispatch_queue_t lock;
__block NSString* status;
dispatch_sync(self.lock, ^{
status = [self.data objectForKey:#"status"];
});
NSString* status = #"Hello";
dispatch_async(self.lock, ^{
[self.data setObject:status forKey:#"status"];
}); // Use dispatch_sync() or dispatch_async() here depending on your needs
My app crashes whenever I try the following line:
[[NSUserDefaults standardUserDefaults] setNilValueForKey:#"my_key"];
with this error: 'NSInvalidArgumentException', reason: '[<NSUserDefaults 0x7a2423d0> setNilValueForKey]: could not set nil as the value for the key my_key.'
But when I do this it seems to work:
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:#"my_key"];
Can someone explain the difference between these 2 functions?
setNilValueForKey: is part of the NSKeyValueCoding protocol and is not intended to be called directly, just overridden by classes with custom NSKeyValueCoding implementations. setObject:forKey:, however, is a method provided by NSUserDefaults and essentially removes the key from the defaults when sent with a nil object (though this behavior may look different in Swift).
According to Apple doc : "The default implementation raises an NSInvalidArgumentException." Ovbiusly when you call it the NSInvalidArgumentException will be launched.
You can call, but you will are launching an exception. Where use Apple this methods, well according to its doc. again: "Invoked by setValue:forKey: when it’s given a nil value for a scalar value (such as an int or float)."
Let's see a little example:
We create a new class, TestObject, we only add a couple of properties in its header (.h) file:
#import <Foundation/Foundation.h>
#interface TestObject : NSObject
#property (nonatomic, strong) NSString *keyString;
#property (nonatomic) int keyInteger;
#end
Well, now we import it in our viewController and we add the next code to the viewDidLoad methods in order to test:
TestObject *testObject = [[TestObject alloc] init];
[testObject setValue:#"Hola" forKey:#"keyString"];
[testObject setValue:#2 forKey:#"keyInteger"];
#try {
// This is the method wich launch the exception.
[testObject setValue:nil forKey:#"keyInteger"];
// If you test this the exception won't be launched.
// [testObject setKeyInteger:nil];
}
#catch (NSException *exception) {
NSLog(#"The exception name is %#",[exception name]);
}
NSLog(#"View values: %#\n%d",testObject.keyString,testObject.keyInteger);
Of course we can override this methods in our subclass, and change its behavior.
All info: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/#//apple_ref/occ/instm/NSObject/setNilValueForKey:
I have a method which requests access to Twitter, and then sets the username using - (void) setUsername:(NSString *)username. I have to pass ACAccountStore.requestAccessToAccountsWithType... a completion handler (block) as follows:
[self.accounts requestAccessToAccountsWithType:twitterAccountType options:NULL completion:^(BOOL granted, NSError *actualError) {
if(granted == YES) {
self.haveTwitterAccess = YES;
ACAccount *account = [self.accounts accountsWithAccountType:twitterAccountType][0];
[self setUsername: account.username];
// [...]
When I do this, however, I get EXC_BAD_ACCESS in the setUsername method (to which I'm trying to pass account.username). If I try NSLog(#"%#", account.username); then I can see that the value is correct, but when passed to setUsername, the username variable is nil.
I imagine that it is being released in between the threads somewhere here, but am not sure, what with all this new ARC stuff, how to stop it happening. In essence, this boils down to passing strings between threads I suppose?
I've tried things like [account.username copy] and setting the accounts store as retain etc., but nothing seems to work. I've also tried referencing self using:
__block UserManager *blockSafeSelf = self;
How to I ensure that the variable passed from within a callback block to a method on another object is retained?
EDIT:
This is in a class called UserManager which has a property
#property (nonatomic) ACAccountStore *accounts;
which deals with the Twitter access. It also has the method:
- (void) setUsername: (NSString *) username;
which I'm trying to call with the account username inside the completion block. I hope that helps pinpoint the issue a bit better.
EDIT (2):
username is a property of UserManager and is declared as follows:
#property (nonatomic) NSString *username;
Given the following interface:
#interface Country : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSNumber * isAggressive;
#end
I have a screen where a user may see the list of Countries and toggle the isAgressive flag. The options only get saved when the user hits apply. They also have the option to hit cancel.
Based on this, I use the following code to load all the countries when the screen loads:
tempContext = [NSManagedObjectContext MR_context];
// Load our countries.
countries = [Country MR_findAllSortedBy: #"name"
ascending: YES
inContext: tempContext];
I do so in a tempContext rather than the default context, as I don't want these objects to interfere with anything else.
On a cancel, I'm not doing anything specific. Just allowing the tempContext to leave scope. On apply, I'm attempting to perform the following:
// Save changes.
[MagicalRecord saveWithBlock: ^(NSManagedObjectContext * saveLocalContext)
{
[countries enumerateObjectsUsingBlock: ^(Country * country, NSUInteger countryIndex, BOOL * stop)
{
[country MR_inContext: saveLocalContext];
}];
} completion:^(BOOL success, NSError *error) {
NSLog(#"Completed: %#, %#.", success ? #"true" : #"false", error.localizedDescription);
//This is called when data is in the store, and is called on the main thread
}];
This, however does not seem to make any changes. When running in debug, I get the following log messages:
[NSManagedObjectContext(MagicalSaves) MR_saveWithOptions:completion:](0x6000001dc020) NO CHANGES IN ** UNNAMED ** CONTEXT - NOT SAVING
Completed: false, (null).
And my updates are not being saved. How should I properly deal with the updated objects and perform the save?
The problem is that [MagicalRecord saveWithBlock... saves the defaultContext rather than your tempContext.
Try calling something like [tempContext MR_saveToPersistentStoreWithCompletion ... instead
When you call [MagicalRecord saveWithBlock:], this method creates a new context for you to perform your save operations within the block. Your use case is slightly different. You already have a scratch context to work with, so you want to use the following pattern:
NSManagedObjectContext *scratchContext = ...;
country = [Country MR_createInContext:scratchContext];
country.name = #"Belgium";
//...what ever other data is entered here.
//Somewhere in your apply method
[self.scratchContext MR_saveToPersistentStoreAndWait];
There are a few variations on the save method, have a look at the header and source code for more details. But basically, you have 2 options. The first is save and block, or wait for it to complete. The second is save in the background. You can pass in a completion block to know when the save operation is complete and if it was successful or not.
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
}