objective-c struct properties are released before usage - objective-c

I have a struct in my Objective-C code like this >
typedef struct {
__unsafe_unretained NSString *string1;
__unsafe_unretained NSString *string2;
__unsafe_unretained NSString *string3;
__unsafe_unretained NSString *string4;
__unsafe_unretained NSString *string5;
.
.
.
__unsafe_unretained NSString *string10;
} MyStruct;
In my model class I store these structs in an array declared
#property (nonatomic, strong) NSMutableArray *myStructArray;
I construct this in my .m file during runtime like this
NSMutableArray *myTempArray = [NSMutableArray array];
for (NSDictionary *jsonDict in jsonArray)
{
MyStruct stringsStruct = parseWithDictionary(jsonDict);
[myTempArray addObject:[NSValue value:&stringsStruct withObjCType:#encode(MyStruct)]];
}
myObject.myStructArray = myTempArray;
The problem is when I try to access this after parsing / constructing the inside objects are already deallocated & I received the error
-[CFURL length]: message sent to deallocated instance
Here is how I access the properties later by the way,
MyStruct myStruct;
[[myObject.myStructArray objectAtIndex:someIndex] getValue:&myStruct];
NSLog(#"%#",myStruct.string1); // << bam! crash
How do I fix this ? Is there a way to make sure the objects objects remain intact without deallocing until i'm done with it ? I'm using ARC & cannot remove with __unsafe_unretained hence.

You explicitly told ARC to get out of the way by using __unsafe_unretained and it's the only way to get a struct hold object values. This doesn't come without a price: you pay the memory management fee.
You manually have to retain/release any object you place in your structs by using CFRelease/CFRetain and this is very error prone.
Let me stress a point: __UNSAFE_unretained. The name hasn't been picked randomly.
My advice is: stop using structs and turn them into objects.
#interface MyClass : NSObject
#property (nonatomic, copy) NSString *string1;
#property (nonatomic, copy) NSString *string2;
...
#property (nonatomic, copy) NSString *string10;
#end
It's better under many point of views:
you get memory management "for free" with ARC
you can define convenience methods in the class
it's more idiomatic Objective-C wise

Related

Should I be creating a class instead of a struct so that I can put the data into an NSArray?

Is it possible to create something like a C struct for Objective-C? I need to be able to use it in an NSArray so it cannot be a traditional struct. Right now I am declaring a whole class just to accomplish this and I was wondering if there is a simpler way.
What I currently have:
#interface TextureFile : NSObject
#property NSString *name;
#property GLKTextureInfo *info;
#end
#implementation TextureFile
#synthesize name = _name;
#synthesize info = _info;
#end
NSMutableArray *textures;
What I want to do:
typedef struct {
NSString *name;
GLKTextureInfo *info;
} TextureFile;
NSMutable array *textures;
It depends what kind of data you're using, the example you are using in your question seems okay for a struct.
If you need to store a C struct in an NSArray, which requires an object, you can convert the C-struct to NSValue and store it like that, you then convert back to its C struct type when you read it.
Check the Apple Documentation.
Given this struct:
typedef struct {
NSString *name;
GLKTextureInfo *info;
} TextureFile;
To store it:
TextureFile myStruct;
// set your stuct values
NSValue *anObj = [NSValue value:&myStruct withObjCType:#encode(TextureFile)];
NSArray *array = [NSArray arrayWithObjects:anObj, nil];
To read it again:
NSValue *anObj = [array objectAtIndex:0];
TextureFile myStruct;
[anObj getValue:&myStruct];

Objective-C property, expose only superclass

I am declaring a property in my class in header file;
#property (readonly) NSArray *pages
That's how I want it to be exposed publicly. Internally though, I am going to allocating it as NSMutableArray so I can add/remove stuff from it. But to do that, I will have to type cast every time. Is there a better way to do this?
Thanks
Your approach is really bad. If you insist on exposing a mutable array with dynamic content, then modify your getter to return an immutable copy, otherwise you are going to get weird side effects and exceptions for mutations during fast enumeration.
There isn't a solution for this. You have to cast every time, or use different properties. Here is a sample for the second approach:
#interface MyClass : NSObject
#property (nonatomic, strong, readonly) NSArray *pages;
-(void)addObject:(id)obj;
#end
#interface MyClass()
#property (nonatomic, strong, readwrite) NSMutableArray *mPages;
#end
#implementation MyClass
-(id) init {
self = [super init]
if (self){
_mPages = [NSMutableArray array];
}
return self;
}
-(NSArray*)pages {
return [NSArray arrayWithArray:self.mPages];
}
-(void)addObject:(id)obj {
[self.mPages addObject:obj];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
MyClass *m = [MyClass new];
[m addObject:#"x"]; // the collection is mutable
NSLog(#"%#",[m pages]); // but only accessible as an immutable copy
}
}
This will be expensive if you access the collection frequently, and may be out of sync with the internal mutable collection (which may be mutated while you iterate on the copy).
Copying can be avoided returning the internal mutable instance (NSMutableArray) disguised as an immutable class (NSArray), but that incurs the following risks:
The client could cast to mutable and change it.
The internal copy could be mutated. This will crash the application if you are iterating, or may cause an index out of range exception.
Note that the following idiom doesn't solve the problem:
#interface MyClass : NSObject
#property (nonatomic, strong, readonly) NSArray *pages;
#end
#interface MyClass()
#property (nonatomic, strong, readwrite) NSMutableArray *pages;
#end
This lets you set the variable, but not use it as a different class than the one declared in the interface. In other words, it forces you to cast on every use:
[(NSMutableArray*)pages addObject:#"x"];

How to copy a custom Objective-C object?

I have a class, say the class Person. In than class I have several NSMutableArrays.
#property NSMutableArray *arrayOne;
#property NSMutableArray *arrayTwo;
...
Now I want to copy that Class so I can return it from a function and use the copy and change it's data. I want to have a new copy of the object in memory not another reference to the same address.
To do that I have implemented this in my Person.m:
-(id)copyWithZone:(NSZone *)zone
{
Person *copy = [[Person allocWithZone:zone] init;
copy.arrayOne = [NSMutableArray arrayWithArray:self.arrayOne];
copy.arrayTwo = [NSMutableArray arrayWithArray:self.arrayTwo];
...
return copy;
}
So far this works just like I want it to, but when I try to sort the arrays of the copy, I get an error:
-[__NSArrayI sortUsingSelector:]: unrecognized selector sent to instance 0x100108a30
I have noticed that the original arrays are of the type '_NSArrayM' but the copy is '_NSArrayI'...
So what did I wrong? I have heard of deep copying using NSArchiver and NSUnarchiver... Do I have to use that? Sorry, I am quite new to C and Objective-C... :D
I hope you can help me out of this. =)
Sorry about my 'school-english'....
Cheers,
Nick
EDIT: The arrays consist of NSString objects, so I can use
sortUsingSelector:#selector(caseInsensitiveCompare:)
to sort the array.
How are your mutable array properties being declared in your header file?
// Wrong
#property (copy) NSMutableArray *array;
#property (assign) NSMutableArray *array;
// Right
#property (strong) NSMutableArray *array;
NB If you're just doing
#property NSMutableArray *array;
then I'm amazed your code has got this far :) The default property memory semantics is assign - which won't tell ARC to retain your arrays in any way at all :) You need to specify strong.

How to avoid temp objects when returning NSString under ARC

I've got a class with two properties:
#interface Contact : NSObject {
NSString *lastname;
NSString *lastNameUpper;
}
I've declared lastname as a property (and synthesize it in the .m-file):
#property (nonatomic, retain) NSString *lastname;
However, I want to write my own method to access the lastNameUpper, so I declared a method:
- (NSString *) lastNameUpper;
and implemented it like this:
- (NSString *) lastNameUpper {
if (!lastNameUpper) {
lastNameUpper = [lastname uppercaseString];
}
return lastNameUpper;
}
This works all right, but as this is called quite often, a lot of temporary objects are called. Interestingly, the Instruments show a lot of "Malloc (4k)", and the number increase each time lastNameUpper is accessed. I can also see that the memory is allocated in objc_retailAutoreleaseReturnValue.
As this was working fine before I converted my project to ARC, I'm assuming that I have to make some ARC specific additions to the method signature, but I can't seem to be able to make it work.
Any suggestions?
0: you should copy your NSString properties:
#property (nonatomic, copy) NSString * lastname;
I'm guessing that returning the string is implemented by copying it.
nope. copy of an immutable string is a retain operation. just run it in the profiler to see how much this costs in time and memory. also, there's no implicit copy in this case.
Update
I tested this on Lion-64. uppercaseString may return a mutable string.
To be safe, you may consider assigning a copy of the result of uppercaseString: lastNameUpper = [[lastname uppercaseString] copy];. that may result in more or less allocations, depending on how you used the string in your implementation. if your properties copy, then a copy will be made each time you assign it. the easy generalization is to assign a copy, and the rest usually takes care of itself.
Test Program
// ARC enabled
#import <Foundation/Foundation.h>
#interface Contact : NSObject
{
NSString * lastname;
NSString * lastNameUpper;
}
#property (nonatomic, copy) NSString *lastname;
#end
#implementation Contact
#synthesize lastname;
- (NSString *) lastNameUpper {
if (!lastNameUpper) {
lastNameUpper = [lastname uppercaseString];
}
return lastNameUpper;
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
int n = 0;
while (n++ < 100000) {
Contact * c = [Contact new];
c.lastname = #"skjdhskjdhaksjhadi";
NSString * lastNameUpper = c.lastNameUpper;
}
}
return 0;
}
Override the - (void)setLastname:(NSString*)aLastname method (created automatically by #synthesize lastname, and set lastNameUpper as in the existing method.
Now create a lastNameUpper property (and synthesize it):
#property (nonatomic, readonly) NSString *lastNameUpper;
Since this will return the pointer of the lastNameUpper instance variable, no copies should be made whenever this is accessed.

Ok to release a pointer thats nil?

If I create a new object that includes two object pointers (see below) when the object is created the pointers are set to point to nil;
#interface rocketShip : NSObject {
NSString *name;
NSNumber *thrust;
}
If (for some unexpected reason) I don't assign these pointers and later release them in my dealloc method is that ok, I am pretty sure it is, just wanted to check?
- (void)dealloc{
[name release];
name = nil;
[thrust release];
thrust = nil;
[super dealloc];
}
gary
Sending a message to nil won't cause an error, so this is fine. You need to make sure the pointers are actually nil though - sending a message to a garbage pointer will likely cause errors.
Yes, you can do this because you can safely send a message (such as release) to nil and it will just do nothing.
If you use the accessor property to create your get/set methods:
#interface rocketShip : NSObject {
NSString *name;
NSNumber *thrust;
}
#property (retain, nonatomic) NSString *name;
#property (retain, nonatomic) NSNumber *thrust;
And in your .m file:
#synthesize name;
#synthesize thrust;
You can then just set your variable to nil in dealloc. This will in fact call your setter and decrement the reference count by one and clean things up.
It’s important in Objective-C to distinguish between objects and variables. You cannot release a pointer (the value of a variable), you can only release an object (the object to which a pointer refers). A nil pointer refers to no object, so messages sent to nil do nothing (which is generally safe).