Message sent to deallocated instance with ARC using custom getter and setter - ios

I'm trying to implement a custom getter and setter for my custom object HFObject and my app crashed with a Message sent to deallocated instance error despite using ARC.
I've read every single related post, the ones that were written pre-ARC don't apply, and everything else didn't help. I have the zombie object debugger option turned on.
Setting up the custom HObject
Within HObject.h I have declared these four properties:
#property (retain) NSString *email; //Will use custom getter/setter
#property (retain) NSString *firstName; //Will use custom getter/setter
#property (retain) NSDate *date; //Will use custom getter/setter
#property (nonatomic, retain) NSMutableDictionary *values;
In the implementation of HObject, I have removed the automatic getting and setting of email. firstName, and date by utilizing #dynamic like so
#dynamic email;
#dynamic firstName;
#dynamic date;
I also allocate the values dictionary in my HObject init
- (id)init {
self = [super init];
if (self) {
self.values = [NSMutableDictionary dictionary];
}
return self;
}
Implementing Custom Getter & Sender
For my custom getter/setter. I have overridden
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
and
- (void)forwardInvocation:(NSInvocation *)invocation
As shown below:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:#"set"].location == 0) {
return [NSMethodSignature signatureWithObjCTypes:"v#:#"];
} else {
return [NSMethodSignature signatureWithObjCTypes:"##:"];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:#"set"].location == 0) {
key = [key substringWithRange:NSMakeRange(3, [key length]-4)];
id obj;
[invocation getArgument:&obj atIndex:2];
[self.values setObject:obj forKey:key];
} else {
id obj = [self.values objectForKey:key];
[invocation setReturnValue:&obj];
}
}
What I'm trying to do here is store all of the values of the property into my values dictionary and retrieve them from there as well.
App Crashing
In the implementation of my view controller, I try to create a HObject and set values for my properties, and then I log the values dictionary to see all of my values.
- (void)buttonPressed:(id)sender {
HObject *obj = [[HObject alloc] init];
NSString *name = #"this is a string object";
obj.date = [NSDate date];
obj.email = #"email#website.com";
obj.firstName = [NSString stringWithFormat:#"%#", name];
NSLog(#"Values %#", [obj values]);
}
At that point the app crashes and this is my console log
2014-07-27 04:12:37.899 App[61501:60b] Values {
Date = "2014-07-27 08:12:37 +0000";
Email = "email#website.com";
FirstName = "this is a string object";
}
2014-07-27 04:12:37.901 HeadsUp[61501:60b] *** -[CFString release]: message sent to deallocated instance 0x109473fe0
If you can help me from here, I would greatly appreciate it. I am also including my debugging process in case that will help you
My debugging Process (Long, skip if you can already help me)
I originally created many of these objects and stored them in an array, and when I do that, as opposed to creating a single object. my app crashed a bit different.
My array:
#property (nonatomic, strong) NSArray *array;
Methods:
- (void)createArray
{
int i = 1; //number of testobjs
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:i];
for (int j = 0; j<i; j++) {
HFObject *obj = [[User alloc] init];
NSString *name = #"this is a string object";
[obj setObject:[NSDate date] forKey:#"Date"];
obj.email = #"email#website.com";
obj.firstName = [NSString stringWithFormat:#"%#", name];
[objects addObject:obj];
}
self.array = [NSArray arrayWithArray:objects];
}
- (void)buttonPressed:(id)sender {
HObject *object = [self.array objectAtIndex:0];
NSLog(#"Values %#", [object values]);
}
Crash log:
2014-07-27 04:34:02.893 App[61623:60b] *** -[CFString isNSString__]: message sent to deallocated instance 0x1094988f0
(lldb)
Now this crash log is almost the same as the one before, except this didn't log the values inside of [object values]
Investigating the issue a bit, I looked at the left window (not sure what it is actually called) of the debugger and I saw this:
(Treat HFObject as HObject and dirtyValues as values; I renamed them for presentational purposes)
You can see that under the key #"FirstName" there is no value.
I did several similar tests where I changed the values of the properties I was setting and changed the data types. More often than not, not only did FirstName not have a value, neither did Date. However, the value of email was always present.
After researching about dataTypes, I realized it was because email was a string literal which can't deallocated. On the other hand firstName and date were objects, which can be deallocated.
The crash log refers to a CFString property, which I learned doesn't use ARC. I never created a Core Foundation object, so I set out to found out it was being created in setter by logging the [obj class]:
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:#"set"].location == 0) {
key = [key substringWithRange:NSMakeRange(3, [key length]-4)];
id obj;
[invocation getArgument:&obj atIndex:2];
NSLog(#"%#", [obj class]); //I ADDED THIS LOG
[self.values setObject:obj forKey:key];
} else {
id obj = [self.values objectForKey:key];
[invocation setReturnValue:&obj];
}
}
After crashing one more time, I got the obj classes
2014-07-27 04:58:03.893 HeadsUp[61765:60b] __NSDate
2014-07-27 04:58:03.894 HeadsUp[61765:60b] __NSCFConstantString
2014-07-27 04:58:03.894 HeadsUp[61765:60b] __NSCFString
2014-07-27 04:58:03.904 HeadsUp[61765:60b] *** -[__NSDate release]: message sent to deallocated instance 0x109554370
(lldb)
Here you can see Date is being deallocated for some reason, and my string are now __NSCF strings.
I tried resetting the strings to NSStrings using (__brigde NSString *)obj and every other possible way you can bridge a CF object to ARC, however that didn't work either.
Here is everything I've done. I appreciate any and all help.

The problem is here:
id obj;
[invocation getArgument:&obj atIndex:2];
getArgument simply copies the object pointer into obj without retaining it.
However, since obj is (by default) a __strong variable, it will be released at
the end of the current method. To solve the problem, use
__unsafe_unretained id obj;
[invocation getArgument:&obj atIndex:2];
Note also that your getter implementation does not work. For example, setFirstName:
stores the key in the dictionary using the key "FirstName", but the getter firstName
tries to read the value for the key "firstName".
(As already mentioned in a comment, it would probably easier and less error-prone
to just override the accessor methods for the three properties separately, instead
of dynamic forwarding.)

Related

Trouble filtering array of custom objects by an NSNumber using NSPredicate

This should be straightforward but something is preventing me from filtering an array of custom objects by NSNumber using NSPredicate. Perhaps it has something to do with the datatype when converting from JSON but I can't figure it out.
I download data from a JSON in an array of custom Objects that look like:
{"hid":"47","public":"1"}
The code to parse the JSON looks like:
if (feedElement[#"public"] && ![feedElement[#"public"] isEqual:[NSNull null]]) {
newMyObject.pub = feedElement[#"public"]== nil ? #"0" : feedElement[#"public"];}
The object looks like:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyObject : NSObject
#property (nonatomic, retain) NSNumber * hid;
#property (nonatomic, retain) NSNumber * pub;
#end
NS_ASSUME_NONNULL_END
The objects are placed in an NSArray * myObjects
My NSPredicate and filter code looks like:
NSPredicate *pubPred = [NSPredicate predicateWithFormat:#"pub == 1"];
NSArray *filteredArray = [myObjects filteredArrayUsingPredicate:pubPred];
When I log [myObjects valueForKey:#"pub"], it logs as 1,1,1, etc. so I know that the values for pub are all 1 however the resulting filteredArray is empty.
What could be wrong with my code?
Thanks for any suggestions.
Edit: I changed public to pub in the object in case public was a reserved word but it did not change anything
With sample code : {"hid":"47","public":"1"}
newMyObject.pub = feedElement[#"public"]== nil ? #"0" : feedElement[#"public"];
In case of public value is present in JSON, you'll set pub property with feedElement[#"public"], which means, it will be #"1" (with the sample), which means you'll put a NSString.
In case of public value is present in JSON, you'll set pub property with #"0" which means you'll put a NSString.
It doesn't matter if it's declared #property (nonatomic, retain) NSNumber * pub;, you are setting a NSString, not a NSNumber.
Want some code testing?
#interface MyObject : NSObject
#property (nonatomic, retain) NSNumber *hid;
#property (nonatomic, retain) NSNumber *pub;
+(void)test;
#end
And
#implementation MyObject
-(id)initWithPub:(NSNumber *)pub andHID:(NSNumber *)hid {
self = [super init];
if (self) {
self.pub = pub;
self.hid = hid;
}
return self;
}
-(NSString *)description {
return [NSString stringWithFormat:#"%# pub: %# - hid: %#", [super description], [self pub], [self hid]];
}
+(NSArray *)arrayList {
return #[[[MyObject alloc] initWithPub:#1 andHID:#3], //Here with real NSNUmbner
[[MyObject alloc] initWithPub:#"1" andHID:#"2"]]; //Here with NSString instead
}
+(void)test {
NSArray *list = [MyObject arrayList];
NSPredicate *predicateNSNumber = [NSPredicate predicateWithFormat:#"pub == %#", #1];
NSArray *filteredNSNumber = [list filteredArrayUsingPredicate:predicateNSNumber];
NSLog(#"Filtered-NSNumber: %#", filteredNSNumber);
NSPredicate *predicateNSString = [NSPredicate predicateWithFormat:#"pub == %#", #"1"];
NSArray *filteredNSString = [list filteredArrayUsingPredicate:predicateNSString];
NSLog(#"Filtered-NSString: %#", filteredNSString);
}
Output:
$> Filtered-NSNumber: (
"<MyObject: 0x60000024cba0> pub: 1 - hid: 3"
)
$> Filtered-NSString: (
"<MyObject: 0x60000024d1e0> pub: 1 - hid: 2"
)
You can state: "Yeah, but you have a warning in [[MyObject alloc] initWithPub:#"1" andHID:#"2"]: Incompatible pointer types sending 'NSString *' to parameter of type 'NSNumber *', yes, I have. You should have it too.
In fact, you'll have it too if you wrote your ternary if :
if (feedElement[#"public"] == nil) {
newMyObject.pub = #"0"; //Incompatible pointer types assigning to 'NSNumber * _Nonnull' from 'NSString *'
} else {
newMyObject.pub = feedElement[#"public"]; //Here, no warning, because it's hope you know what you are doing and setting a NSNumber here.
}
What about using this to your code to check:
for (MyObject *anObject in list) {
NSLog(#"Testing: %#", anObject);
if ([[anObject pub] isKindOfClass:[NSString class]]) {
NSLog(#"Pub is a String");
} else if ([[anObject pub] isKindOfClass:[NSNumber class]]) {
NSLog(#"Pub is a NSNumber");
}
NSLog(#"Attempt to call -stringValue which is a NSNumber method, not a NSString one");
NSLog(#"Attempt Call: %#\n", [[anObject pub] stringValue]);
}
You should get a -[__NSCFConstantString stringValue]: unrecognized selector sent to instance error, because it's really a NSString, not a NSNumber.
Solutions:
You need to fix your parsing, or change the type of property in MyObject.
With keeping the property as a NSNumber:
if ([feedElement[#"public"] isKindOfClass:[NSString class]]) {
self.pub = #([feedElement[#"public"] integerValue]); //Transform it into a NSInteger, then into a NSNumber with #(someInteger)
} else ([feedElement[#"public"] isKindOfClass:[NSNumber class]]) {
self.pub = feedElement[#"public"]; //It's already a NSNumber instance
} else {
self.pub = #(0); //Default value because unknown class or not present
}

Null Value causes Realm Crashed

I am using Realm 0.92.3 but it crashed when I have null value despite I have set the default properties. Is there any solution on this? If not I might convert using coredata as this is very important to me. The null will be random on several properties
#interface WatchlistNews : RLMObject
#property NSString *nid;
#property NSString *tid;
#property NSString *country;
#end
#implementation WatchlistNews
+ (NSString *)primaryKey {
return #"nid";
}
+ (NSDictionary *)defaultPropertyValues {
return #{#"nid" : #"", #"tid": #"", #"country": #""};
}
#end
Data response:
nid = 509319;
tid = <null>;
country = my;
Error code:
-[NSNull UTF8String]: unrecognized selector sent to instance 0x10712b4c0
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull UTF8String]: unrecognized selector sent to instance 0x10712b4c0'
Realm do not support complex data types, so if you try to assign a complex value such as <null>, it gets crashed.
You should check the response you are getting from the server for the <null> values. And if it exists in the response replace it with an empty string. Try following code on the response you are getting, to remove the occurance of <null> values.
-(NSMutableDictionary *) removeNullFromDictionary:(NSDictionary *) originalDictionary{
NSArray *allKeysArray = [originalDictionary allKeys];
const NSMutableDictionary *replaced = [originalDictionary mutableCopy];
const id nul = [NSNull null];
const NSString *blank = #"";
for(NSString *key in allKeysArray) {
const id object = [originalDictionary objectForKey:key];
if(object == nul) {
[replaced setObject:blank forKey:key];
}
}
return [replaced copy];
}
Realm does not yet support setting nil for NSString properties, but you can track progress on that by following https://github.com/realm/realm-cocoa/issues/628.

how to store classobject that returns self

I am making a NSObjectClass that has a method in it that returns self.
This is what it looks like roughtly
storageclass.h
// storageclass vars go here
- (storageclass)assignData:(NSDictionary *)dictionary;
storageclass.m
//#synthesise everything
- (storageclass)assignData:(NSDictionary *)dictionary {
//assign values from dictionary to correct var types (i.e. NSString, Int, BOOL)
//example
Side = [dictionary valueForKey:#"Side"];
return self;
}
Then what I want to do is use this class by passing a NSDictionary var through its method to return a object of type storageclass that I can then use to access the vars using dot notation.
this is how I am trying to access this class at the moment
accessorViewController.h
storageclass *store;
#property (strong, nonatomic) storageclass *store;
accessorViewController.m
#synthesize store;
- (void)getstoreready {
[store assignData:someDictionary];
nslog(#"%#", store);
}
this NSLog returns nothing and in the debugger all of stores class vars are empty showing nothing has been assigned. I am 100% positive the dictionary vars being used in the assignData method have the correct valueForKey values.
I think it has something to do with how I am using it here [store assignData:someDictionary]; how do i catch the turned data so I can use it?
any help would be appreciated.
The store object is never initialized so it will be nil thats obvious isn't it. Initialize the store object first, then call its instance methods onto it. And by doing that, you'll have a storageclass object which is properly assigned with some dictionary already.
And if you want to have a storageclass object like your code shows, you should make your (storageclass)assignData:(NSDictionary *)dictionary method a class method instead of an instance method by putting a + sign
+(storageclass*)assignData:(NSDictionary *)dictionary;
Then properly initialize it and assign the data (dictionary to variables) accordingly and return it to the caller. For example :-
in .m file
+(storageclass*)assignData:(NSDictionary *)dictionary{
storageclass *test = [[storageclass alloc] init];
if (test) {
test.someDict = dictionary;
}
return test;
}
Then use this class method in your view controller as
- (void)getstoreready {
store = [storageClass assignData:someDictionary];
nslog(#"%#", store);
}
Also Do follow the naming convention for classes and instances. A class's name must start with a capital letter only and the opposite for any class instances.
In User.h
#interface User : NSObject
#property (nonatomic, copy) NSString *name;
- (id)initWithDictionary:(NSDictionary *)dictionary;
+ (NSArray *)usersFromArray:(NSArray *)array;
#end
In User.m
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
if (dictionary)
{
self.name = dictionary[#"kUserName"];
}
}
return self;
}
+ (NSArray *)usersFromArray:(NSArray *)array
{
NSMutableArray *users = [NSMutableArray array];
for (NSDictionary *dict in array) {
User *user = [[User alloc]initWithDictionary:dict];
[users addObject:user];
}
NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES];
return [users sortedArrayUsingDescriptors:#[descriptor]];
}
In ViewController.m
import "User.h"
self.currentArray = [User usersFromArray:array];

Objective-C KVC changes the type of property

I'm new to Objective-c. I'm writing a simple method to convert a dictionary to a object to practice the KVC knowledge:
+(id) ConvertDictionary:(NSDictionary *)dict
ToClassInstance:(Class)type{
id targetObj = nil;
if(dict){
NSArray *dictKeys = dict.allKeys;
targetObj = [[type alloc] init];
for (NSString*item in dictKeys) {
[targetObj setValue:[dict objectForKey:item] forKey:item];
}
}
}
return targetObj;
}
And a simple class:
#import <Foundation/Foundation.h>
#interface foo : NSObject
#property (nonatomic) int value;
#property(nonatomic,strong) NSDate *dateTime;
#end
And run the methods with following code:
-(void)testConvert
{
NSArray *value = [NSArray arrayWithObjects:
[NSNumber numberWithInt:2],#"wrongDateString",nil];
NSArray *key = [NSArray arrayWithObjects:#"value",#"dateTime", nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjects: value forKeys:key];
id result= [EPMClassUtility ConvertDictionary:dict
ToClassInstance:[foo class]];
foo *convert = (foo*) result;
NSLog(#"get value: %#",convert.dateTime);
//console write the line: get value:wrongDateString
}
My question is, I give a value with wrong type(should be NSDate but actually be NSString) to the property through setValue:forKey:. But why there is no exception occurs runtime or in XCode and how can the property dateTime accept the NSString value?
There is no automatic run-time type checking for objective-c, and using setValue:frorKey: avoids over any compile-time checking because the value argument can be of any type.
If you want run-time type checking, you will need to implement your own -setDateTime: (as an example) that checks the type at run time.

[_PFArray MR_deleteInContext:]: unrecognized selector sent to instance

I am using MagicalRecord (MR) to delete all records belonging to a selected client (I successfully delete the client record, then go after the appointment records for that client). In doing so, I am getting the error.
[_PFArray MR_deleteInContext:]: unrecognized selector sent to instance
Here is the code, along with the pertinent definitions:
// set up predicate using selectedClientKey
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"aClientKey == %#", selectedClientKey];
ClientInfo *clientSelected = [ClientInfo MR_findFirstWithPredicate:predicate inContext:localContext];
if(clientSelected) {
[clientSelected MR_deleteInContext:localContext];
[localContext MR_saveToPersistentStoreAndWait];
}
// delete clients appointments...
predicate = [NSPredicate predicateWithFormat:#"aApptKey == %#", selectedClientKey]; // use client key
AppointmentInfo *apptSelected = [AppointmentInfo MR_findAllWithPredicate:predicate inContext:localContext];
if(apptSelected) {
[apptSelected MR_deleteInContext:localContext];
[localContext MR_saveToPersistentStoreAndWait];
}
Here is the definition of AppointmentInfo:
#interface AppointmentInfo : NSManagedObject
#property (nonatomic, retain) NSString * aApptKey;
#property (nonatomic, retain) NSDate * aEndTime;
#property (nonatomic, retain) NSString * aServiceTech;
#property (nonatomic, retain) NSDate * aStartTime;
On the findAllWithPredicate statement, I am getting this compiler warning:
CalendarViewController.m:80:43: Incompatible pointer types assigning
to 'NSMutableArray *' from 'NSArray *__strong'
I understand that the findAllWithPredicate statement will return a NSArray; however I have seen examples using NSManagedObject, which is what AppointmentInfo is. ClientInfo in the 3rd line down is also a NSManagedObject and it has NO compiler warning. I thought that it might be because there was only one (1) record returned from the find statement, but it makes no difference, one record or multiple records.
Am I getting the run error due to the compiler warning, or is there something else wrong? (I have looked at Google and SO, and found nothing that addresses this particular issue).
You are correct that findAllWithPredicate: will return an array. The examples you've seen are most likely using the findFirstWithPredicate: or similar style method. Find First, as the name implies, will return the first object in the results returned from the request. This is most likely what you want as well.
I figured it out... for those who might have the same issue, MR_findAll returns a NSArray which you have to "walk through" and delete each individually. Here's the corrected code from above:
// delete clients appointments...
predicate = [NSPredicate predicateWithFormat:#"aApptKey == %#", selectedClientKey]; // use client key
NSArray *apptDataArray = [AppointmentInfo MR_findAllWithPredicate:predicate inContext:localContext];
for(int i = 0; i < apptDataArray.count; i++) {
AppointmentInfo *ai = [apptDataArray objectAtIndex: i];
[ai MR_deleteEntity];
}
[localContext MR_saveToPersistentStoreAndWait];

Resources