I want to create instance variables dynamically at runtime, and I want to add these variables to a category. The number of the instance variables may change based on the configuration/properties file which I am using for defining them.
Any ideas??
Use Associative References - this is tricky, but that is the mechanism invented specifically for your use case.
Here is an example from the link above: first, you define a reference and add it to your object using objc_setAssociatedObject; then you can retrieve the value back by calling objc_getAssociatedObject.
static char overviewKey;
NSArray *array = [[NSArray alloc] initWithObjects:# "One", #"Two", #"Three", nil];
NSString *overview = [[NSString alloc] initWithFormat:#"%#", #"First three numbers"];
objc_setAssociatedObject (
array,
&overviewKey,
overview,
OBJC_ASSOCIATION_RETAIN
);
[overview release];
NSString *associatedObject = (NSString *) objc_getAssociatedObject (array, &overviewKey);
NSLog(#"associatedObject: %#", associatedObject);
objc_setAssociatedObject (
array,
&overviewKey,
nil,
OBJC_ASSOCIATION_ASSIGN
);
[array release];
I'd be inclined to just use a NSMutableDictionary (see NSMutableDictionary Class Reference). Thus, you would have an ivar:
NSMutableDictionary *dictionary;
You'd then initialize it:
dictionary = [NSMutableDictionary dictionary];
You can then save values to it dynamically in code, e.g.:
dictionary[#"name"] = #"Rob";
dictionary[#"age"] = #29;
// etc.
Or, if you are reading from a file and don't know what the names of the keys are going to be, you can do this programmatically, e.g.:
NSString *key = ... // your app will read the name of the field from the text file
id value = ... // your app will read the value of the field from the text file
dictionary[key] = value; // this saves that value for that key in the dictionary
And if you're using an older version of Xcode (before 4.5), the syntax is:
[dictionary setObject:value forKey:key];
Depends on exactly what you want to do, the question is vague but if you want to have several objects or several integers or so on, arrays are the way to go. Say you have a plist with a list of 100 numbers. You can do something sort of like this:
NSArray * array = [NSArray arrayWithContentsOfFile:filePath];
// filePath is the path to the plist file with all of the numbers stored in it as an array
That will give you an array of NSNumbers, you can then turn that into an array of just ints if you want like this;
int intArray [[array count]];
for (int i = 0; i < [array count]; i++) {
intArray[i] = [((NSNumber *)[array objectAtIndex:i]) intValue];
}
Whenever you want to get an integer from a certain position, lets say you want to look at the 5th integer, you would do this:
int myNewInt = intArray[4];
// intArray[0] is the first position so [4] would be the fifth
Just look into using a plist for pulling data, it will them be really easy to create arrays of custom objects or variables in your code by parsing the plist.
Related
Process -
NSObject Class used to generate a card with certain properties. This is added to a MutableArray and used accordingly. However, after the function to determine the hand outcome in another class, the MutableArray loses all it's values.
Now I know a MutableArray simply points to the objects as opposed to holding them, so for it to lose all it's values I'm assuming the objects are being swept up by ARC.
-(void)rankHand {
NSString *echo = [Hand returnHandRank:_hand withString:false]; // 7 values in _hand
// 0 values in _hand.
NSLog(#"%#", echo);
}
After breakpointing to see the issue, the issue arises after returnHandRank: withString:
#interface Cards : NSObject
#property (nonatomic, strong) NSString *face;
#property (nonatomic, strong) NSString *suit;
#property (nonatomic, strong) NSString *symbol;
#property (nonatomic) int prime;
#property (nonatomic) int rankByInt;
+(NSMutableArray*)createDeck:(id)sender {
[sender removeAllObjects];
NSArray *faces = [[NSArray alloc] initWithObjects:#"A",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K", nil];
NSArray *suits = [[NSArray alloc] initWithObjects:#"h",#"d",#"c",#"s", nil];
NSArray *primes = [[NSArray alloc] initWithObjects:[NSNumber numberWithInt:41],[NSNumber numberWithInt:2],[NSNumber numberWithInt:3],[NSNumber numberWithInt:5],[NSNumber numberWithInt:7],[NSNumber numberWithInt:11],[NSNumber numberWithInt:13],[NSNumber numberWithInt:17],[NSNumber numberWithInt:19],[NSNumber numberWithInt:23],[NSNumber numberWithInt:29],[NSNumber numberWithInt:31],[NSNumber numberWithInt:37], nil];
for (int i = 0; i < 52; i++) {
Cards *card = [[Cards alloc]init];
card.face = [NSString stringWithFormat:#"%#", faces[i % 13]];
card.suit = [NSString stringWithFormat:#"%#", suits[i / 13]];
card.rankByInt = i % 13;
card.symbol = [Cards symbolForSuit:card.suit];
card.prime = [[primes objectAtIndex:(i % 13)] intValue];
[sender addObject:card];
}
[sender shuffle];
return sender;
}
Creates the _deck then _hand is filled by
[_hand addObject:[_deck objectAtIndex:0]];
[_hand addObject:[_deck objectAtIndex:1]];
[_hand addObject:[_deck objectAtIndex:3]];
[_hand addObject:[_deck objectAtIndex:4]];
[_hand addObject:[_deck objectAtIndex:5]];
[_hand addObject:[_deck objectAtIndex:7]];
[_hand addObject:[_deck objectAtIndex:9]];
returnHandRank: withString: is a very long function in the Hand class. So that is why I'm assuming they're not being retained.
Can anyone elaborate? I see it pointless to add the cards again from the _deck, it will it be the best solution?
EDIT: Added returnHandRank: withString:
+(NSString *)returnHandRank:(id)cards withString:(BOOL)returnString {
NSArray *combinations = [self returnCombinations];
cards = [self organizeCardsRankOrder:cards];
__block int maxRank = 0;
__block int maxValue = 0;
for (int i = 0; i < [combinations count]; i++) {
NSArray *splitString = [combinations[i] componentsSeparatedByString:#" "]; // splits the combination string.
NSArray *pointerArray = [[NSArray alloc] initWithObjects:
[NSNumber numberWithInt:[splitString[0] intValue]],
[NSNumber numberWithInt:[splitString[1] intValue]],
[NSNumber numberWithInt:[splitString[2] intValue]],
[NSNumber numberWithInt:[splitString[3] intValue]],
[NSNumber numberWithInt:[splitString[4] intValue]],
nil]; // turns the combinations into int values in an array.
NSMutableArray *fiveCardHand = [[NSMutableArray alloc] initWithObjects:
[cards objectAtIndex:[[pointerArray objectAtIndex:0] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:1] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:2] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:3] intValue]],
[cards objectAtIndex:[[pointerArray objectAtIndex:4] intValue]],
nil]; // Create the 5 card hand for the combination loop we are in, we'll now check this to see what rank it returns.
//Check for hand rank.
fiveCardHand = [self organizeCardsRankOrder:fiveCardHand];
NSArray *fiveCardHandOrganized = fiveCardHand;
int strength = [self handRankWithFiveCards:fiveCardHandOrganized];
if (strength > maxRank) {
maxRank = strength;
maxValue = 0;
}
int value = [self associateStrengthToHand:fiveCardHandOrganized andHand:strength];
if (value > maxValue) {
maxValue = value;
}
}
if (returnString) {
return [self handForStrengthWithStrength:maxRank];
} else {
return [NSString stringWithFormat:#"%i", maxValue];
}
}
There have been a few recent question involving combinations, so unless you are creating accounts we suspect there is homework afoot... No problem, let's see if we can point you in the right direction. However we cannot answer the question, not because it might be homework but because there is not sufficient information to do so.
Now I know a MutableArray simply points to the objects as opposed to holding them,
Correct so far...
so for it to lose all it's values I'm assuming the objects are being swept up by ARC.
but now completely wrong :-( You are misunderstanding how automatic memory management in Objective-C works. First forget "retain", modern ARC-based management is about ownership - whether a variable storing a reference asserts ownership over the object the reference references. When it does assert ownership the variable has the attribute strong, when it stores a reference but does not assert ownership then it has the attribute weak (there are some other ownership attributes you will come across later, they can be ignored for the moment). Object reference variables by default have the attribute strong.
Let's try an analogy:
Consider a balloon ("object"), it will float away unless it is held down; and a hand ("variable"), which holds things.
Many different hands can hold strings (references) attached to the same balloon.
If the hand holds a string tightly (strong) the ballon cannot float away.
If the string is just laying on the palm of the hand (weak) the ballon will float away unless at least one other hand is holding another string attached to the ballon tightly.
A balloon will not float away as long as at least one hand is holding a string tightly.
ARC is the breeze, it blows away balloons not held tightly.
An unannotated variable defaults to strong, so when a reference is stored in it the variable asserts ownership of the referenced object and it will not be cleared away by ARC. An instance variable of a class, or a standard (strong) property, all assert ownership. All the standard collections (arrays, dictionaries, sets) assert ownership over the objects referenced by the references stored in the collection.
Therefore, if you store a reference in an NSMutableArray the referenced object will not be cleared away by ARC as long as the reference remains in the array. If you mutate the array and remove a reference then the object referenced by it will be recycled (returned to the available memory pool) by ARC if and only if there are no other references to it stored in strong variables.
The array itself will stay around as long as a reference to it is stored in a strong variable. When there is no strong reference remaining to the array the array itself will be recycled by ARC, in the process all references stored in the array will be removed and if those references are the last strong ones to the referenced objects they too will be recycled.
Hope that helps and understanding how this works will help you find out where you are either emptying your array, or losing all strong references to the array itself; e.g. by assigning a new reference (or nil) to the variable(s) referencing the array.
Now let's look at some of your code:
NSArray *suits = [[NSArray alloc] initWithObjects:#"h",#"d",#"c",#"s", nil];
This is old style syntax, you can more easily create an NSArray using an array literal, #[ ... ]:
NSArray *suits = #[#"h", #"d", #"c", #"s"];
There are no NSMutableArray literals so you use an NSArray one an make a mutable copy: [#[ ... ] mutableCopy] or the shorter #[ ... ].mutableCopy (opinions differ on the use of the latter). There is also a literal for NSNumber objects, your code:
[NSNumber numberWithInt:41]
can simply be replaced by #41.
Using the above literals will make your code shorter and easier to read.
Now your statement:
card.face = [NSString stringWithFormat:#"%#", faces[i % 13]];
suggests a misunderstanding of how references and immutable objects work. An NSString object is immutable, once created its value will never change. The method stringWithFormat: constructs an NSString according to its format and arguments, which in this case is a single string, so you are just copying the string equivalent to:
card.face = [faces[i % 13] copy];
However a copy of an immutable object is just the original object. You know faces contains only immutable strings as you create it using string literals, so the above is equivalent to:
card.face = faces[i % 13];
Important: You can use a mutable, NSMutableString, reference as an NSString one by sub-classing, so the last step here dropping the copy is only valid if you know the reference is to an NSString object and not to an NSMutableString one.
Having used direct indexing on faces and suits you switch to long form:
card.prime = [[primes objectAtIndex:(i % 13)] intValue];
and in a few other places. All of them can be replaced by [...], e.g.:
card.prime = [[primes[i % 13] intValue];
While you uses of division (i / 13) and remainder (i % 13) are all correct you might want to consider using two nested loops to avoid them, e.g. something like:
for(int suitRank = 0; suitRank < 4; suitRank++)
{ for(int cardRank = 0; cardRank < 13; cardRank++)
{ // now use suitRank for i / 13 and cardRank for i % 13
The above is all just tidying up to make your code shorter, more readable, and less error prone. Now a more serious issue:
+(NSMutableArray*)createDeck:(id)sender {
[sender removeAllObjects];
Never do this! While id has it uses it reduces the compilers ability to check your code is correct and can result in your code going wrong when it is run for simple errors the compiler would have caught. Here sender is clearly meant to be a reference to a mutable array, declare it as such:
+ (NSMutableArray *)createDeck:(NSMutableArray *)sender
{
[sender removeAllObjects];
Later (after applying the above use of literals) you have:
NSMutableArray *fiveCardHand = #[ cards[[pointerArray[0] intValue]],
...
].mutableCopy;
//Check for hand rank.
fiveCardHand = [self organizeCardsRankOrder:fiveCardHand];
Here you:
create a mutable array
assign a reference to it to fiveCardHand
overwrite the reference in fiveCardHand with the result of organizeCardsRankOrder:
So here you appear not to have mutated the array referenced by fiveCardHand but instead changed the variable to reference a different array. You don't need to use mutable arrays to do that, you are mutating the variable holding the reference not the referenced array. Now "appear" was used here as you have not supplied the code of organizeCardsRankOrder:, maybe that method does mutate the array passed to it, if that is the case it does not need to also return it and there is no need for the assignment to the variable. So look at your code carefully here and decide whether you are mutating arrays or just variables and change it accordingly.
Finally you do not provide any declarations in the question for _deck and _hand. By naming convention you might be directly accessing the backing variable of a property (doing this is often best avoided), or accessing an instance variable, both of some unspecified class. Therefore we cannot provide any real help with these, just check that if they are connected to an instance that you are using the same instance everywhere you expect to - a common early error is to set an instance variable in one instance, try to read it from another instance, and then wonder why the value is different...
HTH, happy debugging!
Is there any way to add a value to an existing key on a NSMutableDictionary?
Here is snippet of my code
NSMutableArray *mainFeedList = [NSMutableArray array];
[mainFeedList addObjectsFromArray:feedList];
for(int i = 0; i < mainFeedList.count; i++){
NSMutableArray *allFeed = [NSMutableArray array];
NSString *categoryId = [mainFeedList[i] valueForKey: #"categoryId"];
[allFeed addObject:mainFeedList[i]];
if(allFeed != nil && allFeed.count > 0) {
[feedContent setObject:allFeed
forKey:[combinedCategories[(int)[categoryId integerValue]] valueForKey: #"name"]];
}
Sample scenario:
NSMutableDictionary *mDict = #{#"key1":#"value1",#"key2": #"value2"};
I know that
[mDict setObject:mArray forKey:#"key1"];
will set an object to key1 but what I need is
add another object to key1 without replacing existing object (i need it both)
A structure of any NSDictionary is "one key to one object". If you would like to build a structure which maps one key multiple objects, you need an NSDictionary that maps keys to collections, such as NSArray or NSMutableArray:
NSMutableDictionary *mDict = #{
#"key1": [#[ #"value1" ] mutableCopy]
, #"key2": [#[ #"value2" ] mutableCopy]
};
Now you can add values to keys without replacing the existing ones:
[mDict[#"key1"] addObject:#"value3"];
NSDictionary only allows a single object corresponding to a single key. If you would like to add multiple objects corresponding to a single key, if you have string type of object then you can use separators also to combine strings like:
[mDict setObject:[NSString stringWithFormat:#"%#,%#", [mDict objectforKey:#"key1"], #"value2"] forKey:#"key1"];
Otherwise, you have to take collections, which you have already defined in your question.
add another object to key1 without replacing existing object...
why not set an dict to key1?
before:
[dict setObject:#"a" forKey:#"key1"];
U wanna:
add #"b" to "key1", in dict;
why not like:
[dict setObject:#{#"a":#"subKey1", #"b":#"subKey2"} forKey:#"key1"];
I would suggest storing an array as a key in your dictionary like I do below :
// Setting the value for "key1" to an array holding your first value
NSMutableDictionary *mDict = #{#"key1":#["value1"],#"key2": #"value2"};
Now when I want to add a new value I would do this:
// Create temp array
NSMutableArray *temp = mDict[#"key1"];
// Add new object
[temp addObject:#"value3"];
// Convert temp NSMutableArray to an NSArray so you can store it in your dict
NSArray *newArray = [[NSArray alloc] initWithArray:temp];
// Replace old array stored in dict with new array
mDict[#"key1"] = newArray;
Furthermore, if you are not sure if an array is already stored for that key you can run a check and populate with an empty dictionary like below:
if (mDict[#"key1"] == nil) {
mDict[#"key1"] = #[];
}
Here I am getting the cityName1 with the city names like Piscataway, Iselin, Broklyn etc fetched from the tgpList1 array and I need to put the values into an array called item5.
There are 133 records fetched by the above iteration. The following code stores only the last record's cityName1 and not the entire list of city names though inside the loop.
I tried many ways but I am missing something.
tgpList1 is an array.
tgpDAO is an NSObject with two objects NSString *airportCode and NSString *cityName
NSArray *item5 = [[NSArray alloc]init];
for (int currentIndex=0; currentIndex<[tgpList1 count]; currentIndex++)
{
tgpDAO *tgpTable = (tgpDAO *)[self.tgpList1 objectAtIndex:currentIndex];
NSLog(#"The array values are %#",tgpList1);
NSString *cityName1 = tgpTable.cityName;
item5 =[NSArray arrayWithObjects:cityName1, nil];
}
Use mutable array.
{
NSMutableArray *item5 = [[NSMutableArray alloc]initWithArray:nil];
for (int currentIndex=0; currentIndex<[tgpList1 count]; currentIndex++) {
tgpDAO *tgpTable = (tgpDAO *)[self.tgpList1 objectAtIndex:currentIndex];
NSLog(#"The array values are %#",tgpList1);
NSString *cityName1 = tgpTable.cityName;
[item5 addObject:cityName1];
}
}
Instead of
item5 =[NSArray arrayWithObjects:cityName1, nil];
use
[item5 addObject:cityName1];
There are more ways of achieving that. However, this is the one that is designed for that purpose and the most "readable" from my pont of view.
If you need to clear the contents of item5 before then call
[item5 removeAllObjects];
right before the for loop.
What you were doing: arrayWithObjects allways creates a new array that ist made of the objects that are passed to it as aguments. If you do not use ARC, then you would create some serious memory leak with your code because arrayWithObjects creates and retains an object on every loop and on the next loop all references to the array object, that was just created, are lost without being released. If you do ARC then you do not have to worry about in this case.
NSMutableArray *myCities = [NSMutableArray arrayWithCapacity:2]; // will grow if needed.
for( some loop conditions )
{
NSString* someCity = getCity();
[myCities addObject:someCity];
}
NSLog(#"number of cities in array: %#",[myCities count]);
I am using this code in a loop to populate an NSMutable Array of NSMutableSets (of NSString objects). The index of the NSSet is based on the length of the word.
// if set of this length not initialized yet, initialize set.
wordIndex = [NSString stringWithFormat:#"%d", currentWordLength];
if ([myWordArray objectForKey:wordIndex] == nil)
[myWordArray setObject:[[NSMutableSet alloc] initWithObjects:currentWord, nil] forKey:wordIndex];
else
[[myWordArray objectForKey:wordIndex] addObject:currentWord];
The final intention is to split up an array of words into an array of sets of words grouped by their lengths.
However, I see that [myWordArray count] is 0 after this. Why?
You are confusing the methods of NSMutableDictionary and NSMutableArray: In Objective-C arrays do not have keys but have indexes. If you change the class for myWordArray to NSMutableDicitionary it should work.
Try this, it looks very much like your logic, but (1) it uses NSNumbers as keys, which makes a little more sense, (2) handles the missing set condition more simply, but just adding the set, and (3) breaks up the source lines somewhat for easier debugging...
NSArray *inputStrings = // however these are initialized goes here
NSMutableDictionary *result = [NSMutableDictionary dictionary];
for (NSString *currentString in inputStrings) {
NSInteger currentWordLength = currentString.length;
wordIndex = [NSNumber numberWithInt:currentWordLength];
NSMutableSet *wordSet = [result objectForKey:wordIndex];
if (!wordSet) {
wordSet = [NSMutableSet set];
[result setObject:wordSet forKey:wordIndex];
}
[wordSet addObject:currentWord];
}
If you still have an empty dictionary after running this, it might be simpler to watch what's happening by stepping through it.
I'm not sure if I worded the subject correctly. I am looping through an array, within each loop I am trying to instantiate a class, but I want to dynamically create the name. Like so:
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
NSString* thisad = [NSString stringWithFormat:#"ad%d", i];
NSLog(#"%#", thisad);
AdData* thisad = [AdData new];
}
In the example above I want AdData* thisad... to be named dynamically - "ad1", "ad2", "ad3"...and so on. I get a conflicting type error.
This code also generated an error:
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
AdData* [NSString stringWithFormat:#"ad%d", i] = [AdData new];
}
Is there a way to do this?
You can't do that in Objective-C.
Use a NSString to AdData map--it'll do basically the same thing!
**edit: To clarify, use an:
NSMutableDictionary *dict;
with keys that are NSString* objects containing the ad names, and values that are the AdData* objects.
i.e.
[dict setValue:ad1 forKey:#"ad1"];
to set the values, and
[dict valueForKey:#"ad1"];
to get the values. (ignore the obvious memory leaks there with the strings...)
This isn't possible. While Objective-C is very dynamic, it's not that dynamic.
The suggested way to do this would be to create your instances and put them into an array, not assigning them to explicitly named variables.
You can then refer to them individually using their index in the array.
Something like this:
NSMutableArray *ads = [NSMutableArray array];
for(NSString* thisdatarow in filedata) {
AdData* thisad = [[[AdData alloc] init] autorelease];
[ads addObject:thisad];
}
// get third ad:
AdData *ad = [ads objectAtIndex:2];
Alternatively you could create an NSDictionary, if you really want to refer to them by a name, like this:
NSMutableDictionary *ads = [NSMutableDictionary dictionary];
int i = 0;
for(NSString* thisdatarow in filedata) {
i++;
AdData* thisad = [[[AdData alloc] init] autorelease];
NSString *keyName = [NSString stringWithFormat:#"ad%d", i];
[ads setObject:thisad forKey:keyName];
}
// get third ad
AdData *ad = [ads objectForKey:#"ad2"];
Cant be done Without using a C array, which would look like this:
AdData **ad = malloc(sizeof(AdData) * numberOfAds);
ad[1] = [AdData new];
// etc.
if (ad)
free(ad);
But I don't know how that would work because of how Objective-C classes are stored....
Local variable names are a purely compile-time concept. So you cannot do anything "dynamic" (i.e. at runtime) with it. The compiler is free to rename the variables and add or remove variables as it sees fit.
If you think about it, what is the point of dynamically manipulating local variable names? In order to use the dynamically-named variable again, you must either 1) explicitly refer to the variable name, in which case you have hard-coded the name (not so dynamic), or 2) dynamically construct the name again. If it's (1), then there is only a fixed set of variable names, so dynamic-ness is unnecessary. If it's (2), you're missing the point of local variable names (the whole point of which is so they can be referred to explicitly).