Objective C: Memory Management, releasing an object with multiple references - objective-c

I would like to release a Car object present in the dealer. I would like to know what is the right way of doing it. NSMutableArray Inventory stores the cars for a particular dealer. So, now I would like to present the user with a delete functionality, so, user could select the car using the Vin Number and delete it. But if I try to find the car and release the reference that doesn't works. Would I need to remove the object from the Array and then, release the reference? I am fairly new to objective c and in the initial learning phase. Thank you.
#import "Dealer.h"
#import "Address.h"
#import "PhoneNumber.h"
#implementation Dealer
static NSInteger dealerIdAllocater = 0;
-(id) init{
self = [super init];
self.dealerId = ++dealerIdAllocater;
self.addressList = [[NSMutableArray alloc] init];
self.inventory = [[NSMutableArray alloc] init];
return self;
}
#synthesize dealerId, name, addressList, inventory;
-(void)addCarInInventory:(Car*)car{
[self.inventory addObject: car];
}
-(void)displayAddresses{
for(Address *address in self.addressList){
NSLog(#"Street Address: %#", address.streetAddress);
NSLog(#"City: %#", address.city);
NSLog(#"State: %#", address.state);
NSLog(#"Country: %#", address.country);
for(int i=0; i<[address.phoneNumber count]; i++){
PhoneNumber *phoneNumber = [address.phoneNumber objectAtIndex:i];
NSLog(#"Phone Number %i, %#", i, phoneNumber.phoneNumber);
}
NSLog(#"--------------");
}
}
-(void)displayInventory{
for(Car *car in self.inventory){
[car displayInformation];
}
NSLog(#"--------------");
}
-(Car *)findCarById:(NSString *)vinNumber{
for(Car *car in self.inventory){
if ([vinNumber isEqualToString:car.vinNumber]) {
return car;
}
}
return nil;
}
#end

Would I need to remove the object from the Array and then, release the reference?
Yes, containers such as NSMutableArrays increment the retain count of objects by 1 when added to them. This is to make sure the container will always hold a valid reference to an object. When you remove an object from the container, the retain count will be decremented by 1.

Related

Create a NSSet from NSArray based on property

How does one create a NSSet of objects from an array based on a property.
e.g. Array of objects, each with a strong reference to a type property, and multiple occurrences of each type exist in the array. How can this be turned into an NSSet holding a single object of each type.
NSSet *distinctSet = [NSSet setWithArray:[array valueForKeyPath:#"#distinctUnionOfObjects.property"]];
A dictionary essentially has this functionality already. Its keys are a set, so you can create the dictionary to hold the objects, keyed by whatever attribute you're interested in:
[NSDictionary dictionaryWithObjects:arrayOfObjects
forKeys:[arrayOfObjects valueForKey:theAttribute]];
If you ask the dictionary for allValues now, you have only one object for each attribute. I should mention that with this procedure, the later objects will be kept in favor of earlier. If the order of your original array is significant, reverse it before creating the dictionary.
You can't actually put those objects into an NSSet, because the NSSet will use the objects' isEqual: and hash methods to determine whether they should be members, rather than the key attribute (of course, you can override these methods if this is your own class, but that would likely interfere with their behavior in other collections).
If you really really feel that you need a set, you will have to write your own class. You can subclass NSSet, but conventional wisdom is that composition of Cocoa collections is far easier than subclassing. Essentially, you write a class which covers any set methods you're interested in. Here's a (quite incomplete and totally untested) sketch:
#interface KeyedMutableSet : NSObject
/* This selector is performed on any object which is added to the set.
* If the result already exists, then the object is not added.
*/
#property (assign, nonatomic) SEL keySEL;
- (id)initWithKeySEL:(SEL)keySEL;
- (id)initWithArray:(NSArray *)initArray usingKeySEL:(SEL)keySEL;
- (void)addObject:(id)obj;
- (NSArray *)allObjects;
- (NSArray *)allKeys;
- (BOOL)containsObject:(id)obj;
- (NSUInteger)count;
-(void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block;
// And so on...
#end
#import "KeyedMutableSet.h"
#implementation KeyedMutableSet
{
NSMutableArray * _objects;
NSMutableSet * _keys;
}
- (id)initWithKeySEL:(SEL)keySEL
{
return [self initWithArray:nil usingKeySEL:keySEL];
}
- (id)initWithArray:(NSArray *)initArray usingKeySEL:(SEL)keySEL
{
self = [super init];
if( !self ) return nil;
_keySEL = keySEL;
_objects = [NSMutableArray array];
_keys = [NSMutableSet set];
for( id obj in initArray ){
[self addObject:obj];
}
return self;
}
- (void)addObject:(id)obj
{
id objKey = [obj performSelector:[self keySEL]];
if( ![keys containsObject:objKey] ){
[_keys addObject:objKey];
[_objects addObject:obj];
}
}
- (NSArray *)allObjects
{
return _objects;
}
- (NSArray *)allKeys
{
return [_keys allObjects];
}
- (BOOL)containsObject:(id)obj
{
return [_keys containsObject:[obj performSelector:[self keySEL]]];
}
- (NSUInteger)count
{
return [_objects count];
}
- (NSString *)description
{
return [_objects description];
}
-(void)enumerateObjectsUsingBlock:(void (^)(id, BOOL *))block
{
for( id obj in _objects ){
BOOL stop = NO;
block(obj, &stop);
if( stop ) break;
}
}
#end
NSMutableSet* classes = [[NSMutableSet alloc] init];
NSMutableSet* actualSet = [[NSMutableSet alloc] init];
for(id object in array) {
if([classes containsObject:[object class]] == NO) {
[classes addObject:[object class]];
[actualSet addObject:object];
}
}
You would use:
NSSet* mySetWithUniqueItems= [NSSet setWithArray: yourArray];
This should work regardless of the type of objects in your array and would populate the NSSet with only one occurence of any duplicate objects in your array.
I hope this helps.
Update:
Next best thing: is use concatenation of class name and object property first then use the above method.
self.concatenatedArray=[NSMutableArray arrayWithCapacity:4];
for (TheClass* object in self.myArray)
[self.concatenatedArray addObject:[NSString stringWithFormat:#"%#-%#",[object class], object.theProperty]];
self.mySet=[NSSet setWithArray:self.concatenatedArray];
I am not sure what you will use the NSSet output for but you can probably modify the concatenation elements to have the information you need in the NSSet output.
I have created a simple library, called Linq to ObjectiveC, which is a collection of methods that makes this kind of problem much easier to solve. In your case you need the Linq-to-ObjectiveC distinct method:
NSSet* dictionary = [NSSet setWithArray:[sourceArray distinct:^id(id item) {
return [item type] ;
}]];
This returns a set where each item has a distinct type property.

UILabel Address label is blank and Local Declaration hides instance variable

I am new to Xcode, and I am attempting to have my app access the Address Book, choose a person, and then create NSString values for the person (First Name, Last Name, Organization, Address, Email and Telephone Number) I can pull the first and last name, the organization, the first email entered (it would be nice to display all of the email address, and let the user choose), and the first phone number entered in (again, it would be nice to be able to choose), but the address for the person is always blank. I would really appreciate any help you can provide. In addition, I keep getting local declaration hides instance variable warnings - I have no idea how to resolve these.
#import "TACustomer.h"
#interface TACustomer ()
#end
#implementation TACustomer
#synthesize custfirstName;
#synthesize custlastName;
#synthesize custOrganization;
#synthesize custEmail;
#synthesize custAddress;
#synthesize custphoneNumber;
- (IBAction)showPicker:(id)sender
{
// Creating the Address Book Picker
ABPeoplePickerNavigationController *picker = [[ABPeoplePickerNavigationController alloc] init];
// Place the delegate of the picker to the control.
picker.peoplePickerDelegate = self;
// Showing the picker.
[self presentModalViewController:picker animated:YES];
}
- (void)peoplePickerNavigationControllerDidCancel:(ABPeoplePickerNavigationController *)peoplePicker
{
//assigning control back to the main controller.
[self dismissModalViewControllerAnimated:YES];
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
{
[self displayPerson:person];
[self dismissModalViewControllerAnimated:YES];
return NO;
}
- (BOOL)peoplePickerNavigationController:(ABPeoplePickerNavigationController *)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person
property:(ABPropertyID)property
identifier:(ABMultiValueIdentifier)identifier
{
// Only inspect the value if it's an address.
if (property == kABPersonAddressProperty)
{
//Set up an ABMultiValue to hold the address values; copy from a book record.
ABMutableMultiValueRef multicustValue = ABRecordCopyValue(person, property);
// Set up an NSArray and copy values into it.
NSArray *thecustArray = (__bridge id)ABMultiValueCopyArrayOfAllValues(multicustValue);
// Figure out which values we want and store the index.
const NSUInteger customerIndex = ABMultiValueGetIndexForIdentifier (multicustValue, identifier);
// Set up an NSDictionary to hold the contents of the array.
NSDictionary *custDict = [thecustArray objectAtIndex:customerIndex];
// Set up NSStrings to hold the keys and values. First, how many are there?
const NSUInteger theCount = [custDict count];
NSString * __unsafe_unretained keys[theCount];
NSString *__unsafe_unretained values[theCount];
// Get the keys and values from the CFDictionary.
[custDict getObjects:values andKeys:keys];
// Set the address label's text.
NSString *customeraddress;
customeraddress = [NSString stringWithFormat:#"%#, %#, %#, %#, %#",
[custDict objectForKey:(NSString *)kABPersonAddressStreetKey],
[custDict objectForKey:(NSString *)kABPersonAddressCityKey],
[custDict objectForKey:(NSString *)kABPersonAddressStateKey],
[custDict objectForKey:(NSString *)kABPersonAddressZIPKey],
[custDict objectForKey:(NSString *)kABPersonAddressCountryKey]];
self.custAddress.text = customeraddress;
}
return NO;
}
- (void)displayPerson:(ABRecordRef)person
{
// Get Customer First Name
NSString* custfirstname = (__bridge_transfer NSString*)ABRecordCopyValue(person,kABPersonFirstNameProperty);
self.custfirstName.text = custfirstname;
// Get Customer Last Name
NSString* custlastname = (__bridge_transfer NSString*)ABRecordCopyValue(person,kABPersonLastNameProperty);
self.custlastName.text = custlastname;
// Get Customer Organization
NSString* custorganization = (__bridge_transfer NSString*)ABRecordCopyValue(person,kABPersonOrganizationProperty);
self.custOrganization.text = custorganization;
//Get Customer Email Address
NSString* custemail = nil;
ABMultiValueRef custemailAddresses = ABRecordCopyValue (person,kABPersonEmailProperty);
if (ABMultiValueGetCount(custemailAddresses) > 0)
{
custemail = (__bridge_transfer NSString*)ABMultiValueCopyValueAtIndex(custemailAddresses, 0);
} else
{
custemail = #"[None]";
}
self.custEmail.text = custemail;
CFRelease(custemailAddresses);
// Get Customer Phone Number
NSString* custphone = nil;
ABMultiValueRef phoneNumbers = ABRecordCopyValue (person,kABPersonPhoneProperty);
if (ABMultiValueGetCount(phoneNumbers) > 0)
{
custphone = (__bridge_transfer NSString*)ABMultiValueCopyValueAtIndex(phoneNumbers, 0);
} else
{
custphone = #"[None]";
}
self.custphoneNumber.text = custphone;
CFRelease(phoneNumbers);
bundle:nil;
//[self.navigationController pushViewController:tempExamInfoView animated:YES];
}
#end
I have no answer for the problem that the address data is blank.
To get rid of the "local declaration hides instance variable" warning you need to use different variable names that do not clash with the names of the properties you are synthesizing.
For instance, in displayPerson: you have a local variable custfirstname. Because this uses the same name as a property, the local variable hides the instance variable of the same name that is being synthesized.
If you want to keep the local variable name, I believe it is also possible to tell #synthesize to use a different name for the instance variable that it generates. I am not familiar with the syntax, so if you want to go that way you have to look it up yourself.

Storing objects in an array in objective c

I'm trying to store 25 objects in an array
for (int iy=0; iy<5; iy++) {
for (int ix=0; ix<5; ix++) {
TerrainHex *myObject = [[TerrainHex alloc] initWithName:(#"grassHex instance 10000") width:mGameWidth height:mGameHeight indexX:ix indexY:iy];
myObject.myImage.y += 100;
[TerrainHexArray addObject:myObject];
[self addChild:(id)myObject.myImage];
}
}
NSLog(#"Terrain array: %u", [TerrainHexArray count]);
The log is coming back as zero though.
In the .h file I have
#property NSMutableArray *TerrainHexArray;
And in the .m file I have..
#synthesize TerrainHexArray;
I just tried what someone suggested below, which is..
NSMutableArray *TerrainHexArray = [[NSMutableArray] alloc] init];
But it's just giving me a warning saying expected identifier.
It's almost certain that TerrainHexArray does not exist when you're doing the addObject calls and the NSLog. You say you tried adding the alloc/init after someone suggested it, which indicates you don't understand object management in Objective-C.
I'd suggest you step back, find a book on Objective-C, and read at least the first few chapters (up through the discussion of alloc/init et al) before you attempt any more coding.
Incidentally, it's standard C++/Objective-C coding practice (except in Microsoft) to use identifiers with a leading lower case character for instance names, reserving leading caps for types/class names.
What is TerrainHexArray? It looks like a class name, not an instance of an array. If you create a mutable array, then you can add the items to the array.
NSMutableArray *hexArray = [[NSMutableArray] alloc] init];
for (int iy=0; iy<5; iy++) {
for (int ix=0; ix<5; ix++) {
TerrainHex *myObject = [[TerrainHex alloc] initWithName:(#"grassHex instance 10000") width:mGameWidth height:mGameHeight indexX:ix indexY:iy];
myObject.myImage.y += 100;
[hexArray addObject:myObject];
[self addChild:(id)myObject.myImage];
}
}
NSLog(#"Terrain array: %u", [hexArray count]);

Array object doesn't retain it's data

I have an object that contains a array. On initialization of this object, the array is allocated and properly filled (as I can see in the debugger). This object is use to manage elements in a single view.
My problem is that when I try to call the object a second time, the array (and all other parameter of this object) are nil yet they have a memory address (again as seen in debugger).
This is the .h of the object in question :
#import <Foundation/Foundation.h>
#import "ObjectDef.h"
#import "AbstractNode.h"
#interface RenderingMachine : NSObject
{
NSMutableArray* _objectID; // pair list
NSMutableArray* _objectList; // node list
ObjectDef* _defs; // definition of pairs
unsigned int _size;
unsigned int _edgeSize;
AbstractNode* _lastNode;
}
-(void) InitializeMachine;
-(bool) AddObjectByIndex:(int)index :(float)x :(float)y :(float)originX :(float)originY;
-(bool) AddObjectByType:(NSString*)type;
-(NSMutableArray*) GetObjectID;
-(NSMutableArray*) GetObjectList;
-(unsigned int) Size;
-(void) DrawAllNode;
-(int) ComputePar;
-(void) ComputeLastEdge:(int)edgeCount;
//+(RenderingMachine*) GetMachine;
#end
My main problem right now is with _defs which is filled in InitDefinitions :
-(void) InitializeMachine
{
_defs = [[ObjectDef alloc] init];
[_defs InitDefinitions];
_objectID = [[NSMutableArray alloc] init];
_objectList = [[NSMutableArray alloc] init];
_objectID = [NSMutableArray arrayWithObject:[_defs GetPair:3]]; // adding the field node ID
AbstractNode* rootNode = [[FieldNode alloc] init];
_objectList = [NSMutableArray arrayWithObject:rootNode]; // adding the field node as root node
_size = 1;
_edgeSize = 0;
}
What I'd like to know is if might be a bad alloc / init call or could it be a problem with the ARC of xcode because this particular file compiles with ARC (the other being ignore with "-fno-objc-arc").
Also, as mentionned the _defs is problematic, but all the property declared under #interface are having the same problem.
First you create a retained object with _objectID = [[NSMutableArray alloc] init];
and then you overwrite it with an autoreleased one _objectID = [NSMutableArray arrayWithObject:[_defs GetPair:3]];
to add the object better use [_objectID addObject:[_defs GetPair:3]];
same thing with the _objectList Object

Add a tag to NSMutableArray

Is it possible to set a tag for an NSMutableArray? I have to somehow determine, in an array of arrays, the single array which needs to be rewritten, and if I could just set the tag to that inner array to 1 (or some other number), this would be extremely easy.
Example:
NSMutableArray* outerArray = [NSMutableArray new];
NSMutableArray* innerArray1 = [NSMutableArray new];
NSMutableArray* innerArray2 = [NSMutableArray new];
NSMutableArray* innerArray3 = [NSMutableArray new];
NSMutableArray* innerArray4 = [NSMutableArray new];
[outerArray addObject:innerArray1];
[outerArray addObject:innerArray2];
[outerArray addObject:innerArray3];
[outerArray addObject:innerArray4];
//now let's say innerArray1 needs to be rewritten
//I would like to be able to do this
[innerArray1 setTag:100];
//then later, when I need to determine which of the arrays inside outerArray
//needs to be rewritten, I can just do this
for(NSMutableArray* temp in outerArray) {
if(temp.tag == 100) {
//do what I need to do
}
}
But you can't use setTag: with NSMutableArrays. What would be a workaround?
Arrays are ordered collections, so why don't you just keep track of which index needs to be rewritten.
When something happens such that the array at index 0 (which, in your example, would be innerArray1) of outer array needs to be written, cache index 0 -- as a property if this routine needs to span across separate methods.
Then, when it comes time to do the rewrite, consult the cached index. Retrieve the array to be rewritten like this: NSArray *arrayToRewrite = [outerArray objectAtIndex:cachedIndexToRewrite]; Or access it directly: [[outerArray objectAtIndex:cachedIndexToRewrite] replaceObjectAtIndex:whatever withObject:whatever];
You could use an NSMutableDictionary instead. The "tag" would just be the key and the array would be the value.
Use associated objects. You can even add a category to NSMutableArray that would add a tag property to them.
#interface NSMutableArray (TagExtension)
#property (nonatomic, assign) NSInteger tag;
#end
#implementation NSMutableArray (TagExtension)
#dynamic tag;
static char TagExtensionKey;
-(NSInteger)tag {
NSNumber *ourTag = (NSNumber *)objc_getAssociatedObject(self, &TagExtensionKey);
if( ourTag ) {
return( [ourTag integerValue] );
}
return(0);
}
-(void)setTag:(NSInteger)newTag {
objc_setAssociatedObject(self, &TagExtensionKey, [NSNumber numberWithInteger:newTag], OBJC_ASSOCIATION_RETAIN);
}
#end
See also: How to add properties to NSMutableArray via category extension?
Not sure why a dictionary is a bad idea hereā€¦ as alternatives, you can:
remember the index
or if each entry is a unique array, you can simply refer to it by pointer:
NSArray * tagged = theArray;
for (NSMutableArray * at in outerArray) {
if (tagged == at) {
//do what I need to do
}
}
Make your inner arrays class variables. Then you can just access them as:
for(NSMutableArray* temp in outerArray) {
if(temp == self.innerArray1) {
//do what I need to do
}