Getting an object from an NSSet - objective-c

If you can't get an object with objectAtIndex: from an NSSet then how do you retrieve objects?

There are several use cases for a set. You could enumerate through (e.g. with enumerateObjectsUsingBlock or NSFastEnumeration), call containsObject to test for membership, use anyObject to get a member (not random), or convert it to an array (in no particular order) with allObjects.
A set is appropriate when you don't want duplicates, don't care about order, and want fast membership testing.

NSSet doesn't have a method objectAtIndex:
Try calling allObjects which returns an NSArray of all the objects.

it is possible to use filteredSetUsingPredicate if you have some kind of unique identifier to select the object you need.
First create the predicate (assuming your unique id in the object is called "identifier" and it is an NSString):
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:#"identifier == %#", identifier];
And then choose the object using the predicate:
NSObject *myChosenObject = [mySet filteredSetUsingPredicate:myPredicate].anyObject;

NSArray *myArray = [myNSSet allObjects];
MyObject *object = [myArray objectAtIndex:(NSUInteger *)]
replace NSUInteger with the index of your desired object.

For Swift3 & iOS10 :
//your current set
let mySet : NSSet
//targetted index
let index : Int
//get object in set at index
let object = mySet.allObjects[index]

NSSet uses the method isEqual: (which the objects you put into that set must override, in addition, the hash method) to determine if an object is inside of it.
So, for example if you have a data model that defines its uniqueness by an id value (say the property is:
#property NSUInteger objectID;
then you'd implement isEqual: as
- (BOOL)isEqual:(id)object
{
return (self.objectID == [object objectID]);
}
and you could implement hash:
- (NSUInteger)hash
{
return self.objectID; // to be honest, I just do what Apple tells me to here
// because I've forgotten how Sets are implemented under the hood
}
Then, you can get an object with that ID (as well as check for whether it's in the NSSet) with:
MyObject *testObject = [[MyObject alloc] init];
testObject.objectID = 5; // for example.
// I presume your object has more properties which you don't need to set here
// because it's objectID that defines uniqueness (see isEqual: above)
MyObject *existingObject = [mySet member: testObject];
// now you've either got it or existingObject is nil
But yeah, the only way to get something out of a NSSet is by considering that which defines its uniqueness in the first place.
I haven't tested what's faster, but I avoid using enumeration because that might be linear whereas using the member: method would be much faster. That's one of the reasons to prefer the use of NSSet instead of NSArray.

for (id currentElement in mySet)
{
// ** some actions with currentElement
}

Most of the time you don't care about getting one particular object from a set. You care about testing to see if a set contains an object. That's what sets are good for. When you want to see if an object is in a collection sets are much faster than arrays.
If you don't care about which object you get, use -anyObject which just gives you one object from the set, like putting your hand in a bag and grabbing something.
Dog *aDog = [dogs anyObject]; // dogs is an NSSet of Dog objects
If you care about what object you get, use -member which gives you back the object, or nil if it's not in the set. You need to already have the object before you call it.
Dog *spot = [Dog dogWithName:#"Spot"];
// ...
Dog *aDog = [dogs member:spot]; // Returns the same object as above
Here's some code you can run in Xcode to understand more
NSString *one = #"One";
NSString *two = #"Two";
NSString *three = #"Three";
NSSet *set = [NSSet setWithObjects:one, two, three, nil];
// Can't use Objective-C literals to create a set.
// Incompatible pointer types initializing 'NSSet *' with an expression of type 'NSArray *'
// NSSet *set = #[one, two, three];
NSLog(#"Set: %#", set);
// Prints looking just like an array but is actually not in any order
//Set: {(
// One,
// Two,
// Three
// )}
// Get a random object
NSString *random = [set anyObject];
NSLog(#"Random: %#", random); // Random: One
// Iterate through objects. Again, although it prints in order, the order is a lie
for (NSString *aString in set) {
NSLog(#"A String: %#", aString);
}
// Get an array from the set
NSArray *array = [set allObjects];
NSLog(#"Array: %#", array);
// Check for an object
if ([set containsObject:two]) {
NSLog(#"Set contains two");
}
// Check whether a set contains an object and return that object if it does (nil if not)
NSString *aTwo = [set member:two];
if (aTwo) {
NSLog(#"Set contains: %#", aTwo);
}

Related

Creating local objects, prefrence or simply better?

Is it better to create a local object for later use like
NSDictionary *dic = [NSDictionary Dictionary];
or
NSDictionary * dic = nil;
Is it preference thing or is one better then the other?
it's not like 'the one is better', it's like 'the other is bad'.
If you're going to assign a new object to it later, initialize it to nil, else (you leak memory by losing the reference to the first object created by error.) - EDIT: no, you're not leaking memory (either because of the autorelease or the automatic reference counting, but anyway, that's an extra unneeded method call.) That is bad.
If it's a mutable collection, create it before you use it, else it will continue being nil and ignoring essentially all messages sent to it, which is also bad.
Conclusion: it's not a matter of preference - you must think logically and choose whichever is suited for the specific purpose you are using it for.
If you will use that object later, then you should instantiate it with the first option. If you will have to create an object in some if-else block where you will be reinitializing it with some custom values, then the second option is the way to go.
For example the first option:
NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 5; i++) {
[arr addObject:#"string"];
}
or
NSDictionary *dictionary = nil;
BOOL flag;
if (flag) {
dictionary = [NSDictionary dictionaryWithObject:#"string" forKey:#"myKey"];
}
else {
NSArray *objects;
NSArray *keys;
dictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}

Objective-C method returns NSDictionary instead of NSMutableDictionary. Why?

I am using the included method to return a pointer to a NSMutableDictionary, that is contained in an NSArray. However, the NSMutableArray (theOne) is being created as a non-mutuable NSDictionary. This is a problem because I want to modify the dictionary after retrieving it with this method.
- (NSMutableDictionary*)getMatFromBoutKey:(NSString*) boutKey
{
/*
* Returns the mat object with the provided boutKey.
* Returns nil if no mat has that boutKey.
*/
NSUInteger idx = [[event objectForKey:#"mats"] indexOfObjectPassingTest:
^ BOOL (NSMutableDictionary* obj, NSUInteger idx, BOOL *stop)
{
return [[obj objectForKey:#"boutKey"] isEqualToString:boutKey];
}];
if (idx == NSNotFound)
return nil;
else {
NSMutableDictionary* theOne = [[event objectForKey:#"mats"] objectAtIndex: idx];
return theOne;
}
}
Here's an image of the debugger stopped on a breakpoint right after the theOne is first referenced.
Why isn't theOne mutable? How can I return a pointer to the NSMutableDictionary so that I can modify it after I get the value returned to me?
Thanks!
I'm going to assume that you have a dictionary of arrays. Then that array contains a bunch of regular dictionaries. So when you pull it out of the array it is still a regular dictionary regardless of what you assign it to.
For instance, take the following code for example
NSDictionary *dict = [[NSDictionary alloc] init];
NSMutableDictionary *mutDict = dict;
mutDict will contain a regular dictionary because it has not properly been casted to a mutable dictionary.
either make sure when you create the array that is at [event objectForKey:#"mats"] that you put NSMutable dictionaries inside of it OR
use
NSMutableDictionary* theOne = [[[event objectForKey:#"mats"] objectAtIndex: idx] mutableCopy];
When taking the data out
In general, I think it's better practice to work with immutable objects when mutability isn't strictly necessary. Mutable objects use more memory, and of course have the potential of being accidentally changed. Maybe in the block are being changed to the enumerator(I'm not sure, but it is possible. For faster indexing). If you want to change a mutable object is via mutableCopy. or using a other method.
Is it inserted as mutable elsewhere in the code? If so it should return as mutable, if not you can send it the mutableCopy message to get a mutable copy (that has a reference count of 1, so be sure to release it when necessary).

Add object to array only if the object is not in already

I want to add object to array only if the array already does not contain that object.
How to do opposite of containsObject method in NSArray ?
Use an NSMutableOrderedSet, whose addObject: method does exactly what you want:
Appends a given object to the mutable ordered set, if it is not already a member.
Here's how I'd do it:
if (![myArray containsObject:objectToAdd]){
[myArray addObject:objectToAdd];
}
More detail here:
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
Note that because the containsObject method queries every object in the array there are some performance considerations when using it on larger arrays.
if your object is of NSString* type you can do fast enumeration like this
BOOL found = NO;
for(NSString *object in YourArray)
{
if([object isEqualtoString:#"My text"])
{
found = YES;
}
}
if(!found)
{
//addObject
}
there are many isEqual methods in objective-c for different data types

Approaches to create a nested tree structure of NSDictionaries?

I'm parsing some input which produces a tree structure containing NSDictionary instances on the branches and NSString instance at the nodes.
After parsing, the whole structure should be immutable. I feel like I'm jumping through hoops to create the structure and then make sure it's immutable when it's returned from my method.
We can probably all relate to the input I'm parsing, since it's a query string from a URL. In a string like this:
a=foo&b=bar&a=zip
We expect a structure like this:
NSDictionary {
"a" => NSDictionary {
0 => "foo",
1 => "zip"
},
"b" => "bar"
}
I'm keeping it just two-dimensional in this example for brevity, though in the real-world we sometimes see var[key1][key2]=value&var[key1][key3]=value2 type structures. The code hasn't evolved that far just yet.
Currently I do this:
- (NSDictionary *)parseQuery:(NSString *)queryString {
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSArray *pairs = [queryString componentsSeparatedByString:#"&"];
for (NSString *pair in pairs) {
NSRange eqRange = [pair rangeOfString:#"="];
NSString *key;
id value;
// If the parameter is a key without a specified value
if (eqRange.location == NSNotFound) {
key = [pair stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
value = #"";
} else {
// Else determine both key and value
key = [[pair substringToIndex:eqRange.location] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
if ([pair length] > eqRange.location + 1) {
value = [[pair substringFromIndex:eqRange.location + 1] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
} else {
value = #"";
}
}
// Parameter already exists, it must be a dictionary
if (nil != [params objectForKey:key]) {
id existingValue = [params objectForKey:key];
if (![existingValue isKindOfClass:[NSDictionary class]]) {
value = [NSDictionary dictionaryWithObjectsAndKeys:existingValue, [NSNumber numberWithInt:0], value, [NSNumber numberWithInt:1], nil];
} else {
// FIXME: There must be a more elegant way to build a nested dictionary where the end result is immutable?
NSMutableDictionary *newValue = [NSMutableDictionary dictionaryWithDictionary:existingValue];
[newValue setObject:value forKey:[NSNumber numberWithInt:[newValue count]]];
value = [NSDictionary dictionaryWithDictionary:newValue];
}
}
[params setObject:value forKey:key];
}
return [NSDictionary dictionaryWithDictionary:params];
}
If you look at the bit where I've added FIXME it feels awfully clumsy, pulling out the existing dictionary, creating an immutable version of it, adding the new value, then creating an immutable dictionary from that to set back in place. Expensive and unnecessary?
I'm not sure if there are any Cocoa-specific design patterns I can follow here?
Expensive and unnecessary?
Yes. Apple's Cocoa APIs regularly say they return an immutable object, but actually return a mutable subclass that's been cast to the immutable version. This is a standard operating procedure and an accepted Cocoa design principle. You just trust that your clients aren't going to cast it back to a mutable version and change things from underneath you.
From Cocoa Core Competencies: Object Mutability:
Receiving Mutable Objects
When you call a method and receive an object in return, the object could be mutable even if the method’s return type characterizes it as immutable. There is nothing to prevent a class from declaring a method to return an immutable object but returning a mutable object in its implementation. Although you could use introspection to determine whether a received object is actually mutable or immutable, you shouldn’t. Always use the return type of an object to judge its mutability.
See also: Cocoa Fundamentals Guide: Cocoa Objects.

Objective C /iPhone : Is it possible to re initialize an NSArray?

I read that non mutable data types can't be modified once created.(eg NSString or NSArray).
But can they be re-initialized to point to a different set of objects?
If so, do I use release to free any alloc from first time round in between uses? eg:
myArray declared as NSArray *myArray in interface, and as nonatomic/retain property.myArray set in initialization code to a point to an array of strings as follows.
self.myArray = [myString componentsSeparatedByString:#","];
But later I want to re-initialize myArray to point to a different set of strings
self.myArray = [myOtherString componentsSeparatedByString:#","];
Is it possible? Thanks...
It really depends what you mean with re-initialize. You can assign another immutable object to a pointer, because the pointers aren't constant.
Example:
#interface MyObj : NSObject {
NSString *name; // not needed in 64bit runtime AFAIK
}
#property(retain) NSString *name; // sane people use copy instead of retain
// whenever possible. Using retain can
// lead to some hard to find errors.
#end
/* ... another file ... */
MyObj *theObject = [[[MyObj alloc] init] autorelease];
theObject.name = #"Peter";
NSString *oldName = theObject.name;
NSLog(#"%#", theObject.name); // -> Peter
NSLog(#"%#", oldName); // -> Peter
theObject.name = #"Martin";
NSLog(#"%#", theObject.name) // -> Martin
NSLog(#"%#", oldName) // -> Peter
If the behavior above is what you want, that's fine.
If you want that last line to return Martin you're in trouble. Those are constant strings and are not meant to be modified. You could, if you really want, modify the memory of the object directly, but this is dangerous and not recommended. Use mutable objects if you need such behaviour.
Yes you can reinitialized the NSArray. Here is the sample code that i used to re-initialized the NSArray.
NSString *keywords = #"FirstName|LastName|Address|PhoneNumber";
NSArray *arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"First Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
arr = nil;
keywords = #"First_Name|Last_Name|_Address|_PhoneNumber";
arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"Second Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
Of course they can. Saying that an NSArray is immutable doesn't mean that an attribute of a class of that type cannot be changed. You can't change the content, but you can assign new content to it.
If you want to make also changing the reference impossible you should use const keyword.