How to avoid temp objects when returning NSString under ARC - objective-c

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.

Related

Why does the object in this Objective C code not set?

I have the below code which simply read a bunch of cards and inputs their names into a Mutable Array if they are not present beforehand.
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSMutableArray *allCards = [[NSMutableArray alloc] init];
char cardAvailable = '\0';
do {
Card *newCard = [[Card alloc] init];
NSLog(#"What is the Card Name ?\n");
char cName[20];
scanf("%s",cName);
NSString *nName = [NSString stringWithCString:cName encoding:1];
[newCard setName:nName];
if([newCard wasMatch:allCards]) {
NSLog(#"Card Already Present");
} else {
NSLog(#" NEW CARD NAME %# %# %s",[newCard getName],newCard.name, cName);
[newCard addGlobal:newCard toArray:allCards];
}
NSLog(#"Is there another Card to Input?");
scanf("%c",&cardAvailable);
} while(cardAvailable != 'N' || cardAvailable != 'n');
NSLog(#":::: Card Names Are ::::");
for(Card *card in allCards)
{
NSLog(#"%#",card.name);
}
}
return 0;
}
However, first - The name is simply NOT SET. I don't know why this is happening. Today was my first day ever with Objective C, so please pardon me if this is too simple.
The Card class files are -
#import <Foundation/Foundation.h>
#interface Card : NSObject
#property(nonatomic) BOOL chosen;
#property (nonatomic, getter = getName) NSString *name;
-(BOOL)wasMatch:(NSMutableArray*) allCards;
-(void)addGlobal:(Card*) aCardName toArray:(NSMutableArray*) allCards;
-(void)setName:(NSString *)name;
-(void)setChosen:(BOOL)chosen;
#end
I get the error here - in the add global line.
#import "Card.h"
#implementation Card
#synthesize chosen = _chosen;
-(BOOL)chosen
{
return _chosen;
}
-(void)setChosen:(BOOL)chosen
{
_chosen = chosen;
}
#synthesize name = _name;
-(NSString*)getName
{
return _name;
}
-(void)setName:(NSString*)name
{
name = _name;
}
-(BOOL)wasMatch:(NSMutableArray *)allCards
{
for(Card *card in allCards)
{
if([self.name isEqualToString:card.name])
{
return true;
}
}
return false;
}
-(void)addGlobal:(Card *)aCardName toArray:(NSMutableArray *)allCards
{ NSLog(#" THE NS STRING %#",aCardName.name);
[allCards addObject:aCardName.name];
}
#end
Your setter is wrong.
-(void)setName:(NSString*)name
{
name = _name;
}
You assign the current instance variable (_name) to the parameter.
This should be the other way around.
-(void)setName:(NSString*)name
{
_name = name;
}
But you don't need this at all. There are various problems with your code.
Objective-C does not use getFoo for ordinary getters.
#property (nonatomic, getter = getName) NSString *name; should be
#property (copy, nonatomic) NSString *name;
And unless you are writing code in a ancient version of Objective-C (which you shouldn't) you don't need explicit getters and setters. In recent version you don't even have to explicitly synthesize.
In newer version of Objective-C your Card class should look more like this:
#interface Card : NSObject
#property (assign, nonatomic) BOOL chosen;
#property (copy, nonatomic) NSString *name;
-(BOOL)wasMatch:(NSMutableArray*) allCards;
-(void)addGlobal:(Card*) aCardName toArray:(NSMutableArray*) allCards;
#end
#import "Card.h"
#implementation Card
-(BOOL)wasMatch:(NSMutableArray *)allCards
{
for(Card *card in allCards)
{
if([self.name isEqualToString:card.name])
{
return YES;
}
}
return NO;
}
-(void)addGlobal:(Card *)aCardName toArray:(NSMutableArray *)allCards
{ NSLog(#" THE NS STRING %#",aCardName.name);
[allCards addObject:aCardName.name];
}
#end
Way less code. You might want to look for a more up to date learning resource.
The rest of your code looks questionable as well. I honestly don't see the point in addGlobal:toArray:. But I don't want to make this answer longer than necessary. ;-)
Also, for the love of your future self, don't use 1 in NSString *nName = [NSString stringWithCString:cName encoding:1]; use the constant. In your case NSASCIIStringEncoding. But because it's no longer 1980 you most likely want to use NSUTF8StringEncoding instead of ASCII.
As I said, please get more recent and decent learning resources.
In the -setName: property accessor method, you assign the the _name instance variable value to the name parameter. It should be swapped like this:
-(void)setName:(NSString*)name
{
_name = name;
}
To learn Objective-C i reccomment the most recent Stanford lectures by Paul Hegarty. CS193p the class is called. You can find all materials, and excellent videos on iTunes U. Other then Apple resource, there is no better then this.

Can't find a appropriate property attribute for a instance variable

I'm new to this objective-c property attribute thing. Here's the code:
student.h
#import <Foundation/NSObject.h>
#interface Student: NSObject
#property int age(assign, readwrite); //I don't know what attribute it should have
#property NSString *name(retain, readwrite); //this one either
-(Student*) initWithName: (NSString *) n andAge:(int) a;
-(void) speakName;
+(void) smoke:(NSString*) thing;
#end
student.m
#import "student.h"
#implementation Student
-(Student*) initWithName:(NSString*) n andAge:(int) a {
self = [super init];
if(self) {
[self setName: n];
[self setAge: a];
}
return self;
}
-(void) speakName {
NSLog(#"MyName is %#", _name);
}
+(void) smoke:(NSString*) thing {
NSLog(#"Smoking %#", thing);
}
#end
main.m
#import <Foundation/Foundation.h>
#import "student.h"
int main(void) {
Student *student = [[Student alloc] initWithName: #"Markson" andAge: 29];
[student speakName];
[Student smoke:#"weed"];
[student release];
return 0;
}
for the the line
#property int age(assign, readwrite); //I don't know what attribute it should have
#property NSString *name(retain, readwrite); //this one either
when I don't give nothing to them, the compiler give me 4 warnings:
In file included from student.m:1:
./student.h:5:1: warning: no 'assign', 'retain', or 'copy' attribute is
specified - 'assign' is assumed [-Wobjc-property-no-attribute]
#property NSString *name;
^
./student.h:5:1: warning: default property attribute 'assign' not appropriate
for non-GC object [-Wobjc-property-no-attribute]
2 warnings generated.
In file included from main.m:2:
./student.h:5:1: warning: no 'assign', 'retain', or 'copy' attribute is
specified - 'assign' is assumed [-Wobjc-property-no-attribute]
#property NSString *name;
^
./student.h:5:1: warning: default property attribute 'assign' not appropriate
for non-GC object [-Wobjc-property-no-attribute]
2 warnings generated.
Please anyone can tell what attributes should be used for age and name properties?
#property int age(assign, readwrite); //I don't know what attribute it should have
#property NSString *name(retain, readwrite); //this one either
Short Note:
If you are creating property for primitive types like int, float, BOOL etc then use assign
If you want to retain the value then use strong or retain.
In all other situations you can go with weak.
And always go with atomic, and the syntax is like this
#property (<assign/retain/weak>, <atomic/nonatomic>, <read/readwrite>) <dataType> <propertyName>;
Change those lines to this:
#property (assign, nonatomic) int age;
#property (retain, nonatomic) NSString *name;
readwrite is the default one, so you don't need to set it.
Check this documentation:
Encapsulating data in Objective-C

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"];

Saving to database using FMDB crashes on interpreting NSInteger

When the below function is called, I get the EXC_BAD_ACCESS crash. It looks like FMDB is having a problem interpreting the subject_id NSInteger as it makes it through the two NStrings and bombs when it hits this subject_id column in the WHERE clause.
- (void) saveAllData {
if(isDirty) {
DrillDownAppAppDelegate *appDelegate = (DrillDownAppAppDelegate *)[[UIApplication sharedApplication] delegate];
FMDatabase *database = [FMDatabase databaseWithPath:appDelegate.getDBPath];
if ([database open]) {
[database executeUpdate:#"update Subject Set subject = ?, category = ? where subject_id = ?",
self.title, self.category_title, self.subject_id];
[database close];
}
isDirty = NO;
}
//Reclaim all memory here.
[title release];
title = nil;
[category_title release];
category_title = nil;
}
The problem is the same as I ran into in another post on FMDB insert problems and this boils down to something wrong with my subject_id member. I believe I am using a wrong declaration in the header. Here it is:
//
// Subject.h
// DrillDownApp
#import <UIKit/UIKit.h>
#interface Subject : NSObject {
NSInteger subject_id;
NSString *category_title;
NSString *title;
// NSMutableArray *quotes;
BOOL isDirty;
// BOOL isDetailViewHydrated;
}
- (id) initWithPrimaryKey:(NSInteger)pk;
#property (nonatomic, readwrite) BOOL isDirty;
//#property (nonatomic, readwrite) BOOL isDetailViewHydrated;
- (void) addSubject;
- (NSInteger)getNextSubjectId;
#property (nonatomic, assign) NSInteger subject_id;
#property (nonatomic, copy) NSString * title;
#property (nonatomic, copy) NSString * category_title;
//#property (nonatomic, retain) NSMutableArray *quotes;
//- (void) saveAllData;
#end
(Note: I edited this majorly as I figured out the rest of it.)
Ok I solved it. FMDB will not work using Integers. You must convert them into Numbers. I found this done in the examples on FMDB doc and there is never an int being passed through an executeUpdate statement.
So in my example above the way I fixed this was with the following:
[database executeUpdate:#"update Subject Set subject = ?, category = ? where subject_id = ?", self.title, self.category_title, [NSNumber numberWithInt:self.subject_id]];
I wished this was better documented, but oh well.
I don't know from where you are calling the method saveAllData. Still your question lacks requirement so anyone to answer.
Also, I have found one problem with your code.
Instead of the following code,
//Update the value.
[sub setTitle:txtSubject.text];
[sub setCategory_title:txtCategory.text];
use the following code
//Update the value.
sub.title = txtSubject.text;
sub.category_title = txtCategory.text;
so that the strings have the copy property. If you override the setter method, then you might need to explicitly copy the string. Else it will just assign the value without copy.

Macro for defining kvc assessors in objective-c

Is there any macro to help simplify the creation of KVC macros in Objective C? As it stands in order to create a to-many mutable KVC compliant property is extremely tedious, to define a single property it takes the following
//Code.h
#property (strong, nonatomic, readonly) NSArray *prevSearches;
//Code.m
#property (strong, nonatomic, readwrite) NSArray *prevSearches;
...
#synthesize prevSearches = _prevSearches;
- (void)prevSearches {
return [_prevSearches copy];
}
- (void)setPrevSearches:(NSArray *)prevSearches {
_prevSearches = [NSMutableArray arrayWithArray:prevSearches];
}
- (void)insertObject:(SavedSearch *)object inPrevSearchesAtIndex:(NSUInteger)index {
[_prevSearches insertObject:object atIndex:index];
}
- (void)removeObjectFromPrevSearchesAtIndex:(NSUInteger)index {
[_prevSearches removeObjectAtIndex:index];
}
That's over 20 lines to define a single property, I often have several in a particular class... Surely there's an easier way?
have you tried a sofware like accessorizer?
http://itunes.apple.com/it/app/accessorizer/id402866670?mt=12
otherwise i think that a simple bash script can save your time ;)