Retrieve object from NSArray by specifying class name - objective-c

The task:
Return an object from an NSArray that's instance of the class who's name is given as parameter to the function.
Right now I have this function:
+ (id)objectOfType:(NSString *)name fromArray:(NSArray *)array
{
for (NSObject* instance in array)
{
if ([instance.className isEqualToString:name])
return instance;
}
return nil;
}
However, given that I can transform an array of objects into an array of class names of the objects with this simple method call on an NSArray
[array valueForKeyPath:#"className"]
shouldn't there also be a more concise way to retrieve the object with the specified class name..?

Here is a concise method, using NSPredicate and array filtering.
+ (id)objectOfType:(NSString *)name fromArray:(NSArray *)array {
return [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"class == %#", NSClassFromString(name)]].lastObject;
}

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.

NSArray. How do I implement the Map function?

In iOS I want to implement the map function for NSArray. This is a function that is applied to every element of the array. What is the best way to implement this?
Thanks,
Doug
You are looking for apply as map should return a new array with the transformed values.
However you can augment NSArray with the methods you look for by creating a custom category. Here are two examples - apply and map:
#implementation NSArray (CMMap)
- (NSArray *) map:(id(^)(id obj))block {
NSMutableArray *a = #[].mutableCopy;
for (id o in self) {
id on = block(o);
if (!on) {
NSLog(#"NSArray::map() - object returned by block is nil!");
abort();
}
[a addObject: on];
}
return [NSArray arrayWithArray: a];
}
- (void) apply:(void(^)(id obj))block {
for (id o in self) {
block(o);
}
}
#end
You can use enumerateObjectsUsingBlock: function of NSArray.
[myArray enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop) {
// Body of the function
}];
If you are using Objective-C, you can simply use these methods:
- (void)makeObjectsPerformSelector:(SEL)aSelector;
- (void)makeObjectsPerformSelector:(SEL)aSelector withObject:(nullable id)argument;
For example:
[array makeObjectsPerformSelector:#selector(doSomething)];

Question about isKindOfClass

Please look at this code snipped
- (SoapRequest*)AddFlyData:(id)_target
action:(SEL)_action
sessionid:(int)sessionid
datasets:(FlyNetArrayOfDataSet*)datasets
{
if ([datasets isKindOfClass:[FlyNetArrayOfDataSet class]]) {
NSLog(#"Yeah");
} else {
NSLog(#"Not Yeah");
}
}
Why, when i look on my console, I get
2011-09-06 23:08:00.917 soap-test[2133:207] Not Yeah
I'm a beginner and I'm completely confused .. :s When I look in the Debugger, the variable type is SoapArray (who is the parent class of FlyNetArrayOfDataSet).
I used a method from SoapArray to initiate my instance of 'datasets', that means the class is automatically defined as Soap and not as FlyNetArrayOfDataSet ?!
Thank you
EDIT: I made a mistake, it's not NSArray but it inherits from SoapArray
This is the header file of the class FlyNetArrayOfDataSet
#import "Soap.h"
#interface FlyNetArrayOfDataSet : SoapArray
{
}
+ (NSMutableString*) serialize: (NSArray*) array;
#end
But that didn't explain me why isKindOfClass returns false ..
EDIT2: Ok I have the response of my question..
I used this method to initialize my instance
FlyNetArrayOfDataSet * arr = [FlyNetArrayOfDataSet arrayWithObject:data];
This is a static method of the superclass SoapArray that create an instance of SoapArray (Helper) .. but not an instance of FlyNetArrayOfDataSet (!)
Look at its implementation :
+ (id)arrayWithObjects:(id)firstObj, ...{
SoapArray* a = [SoapArray array];
id eachObject;
va_list argumentList;
if (firstObj) {
[a.items addObject: firstObj];
va_start(argumentList, firstObj);
while (eachObject = va_arg(argumentList, id)) {
[a.items addObject: eachObject];
}
va_end(argumentList);
}
return a;
}
If I initialize my instance like this
FlyNetArrayOfDataSet * arr = [[FlyNetArrayOfDataSet alloc] init];
It's work perfectly and the method isKindOfClass return true :-)
Suppose you have a class named "FlyNetArrayOfDataSet" which inherits from (=is a subclass of) NSArray.
If you instantiate a variable like:
FlyNetArrayOfDataSet *arr = [[FlyNetArrayOfDataSet alloc] init];
As you can see, I'm initializing the array with a method of NSArray. However, my "arr" object will be of kind FlyNetArrayOfDataSet, and NOT NSArray, because I called the FlyNetArrayOfDataSet class (see [FlyNetArrayOfDataSet arrayWithObject....).
NSLog(#"%d", [arr isKindOfClass:[FlyNetArrayOfDataSet class]]);
NSLog(#"%d", [arr isKindOfClass:[NSArray class]]);
Both will return "1", which means "true", because arr is an object of the FlyNetArrayOfDataSet class, which inherits from NSArray.
EDIT
Let's see if I can explain it better:
arr1 = [[FlyNetArrayOfDataSet alloc] init];
arr2 = [[NSArray alloc] init];
Both objects, arr1 and arr2, are created with the same method, which is defined in the class NSArray. However, in the first case the class which is being called is FlyNetArrayOfDataSet and in the second case is NSArray. Thus, arr1 will be an object of class FlyNetArrayOfDataSet, while arr2 will be of class NSArray.
The difference can be seen in this code:
NSLog(#"%d %d",
[arr1 isKindOfClass:[FlyNetArrayOfDataSet class]]
[arr1 isKindOfClass:[NSArray class]]
);
NSLog(#"%d %d",
[arr2 isKindOfClass:[FlyNetArrayOfDataSet class]]
[arr2 isKindOfClass:[NSArray class]]
);
The output of this code is:
1 1 ( = true true)
0 1 ( = false true)
that's because FlyNetArrayOfDataSet is a SoapArray and SoapArray is not a FlyNetArrayOfDataSet.
if datasets were an instance of SoapArray, you will see "Soap" in the following example:
- (SoapRequest*)addFlyData:(id)target
action:(SEL)action
sessionid:(int)sessionid
datasets:(FlyNetArrayOfDataSet*)datasets
{
if ([datasets isKindOfClass:[FlyNetArrayOfDataSet class]]) {
NSLog(#"Fly");
}
else if ([datasets isKindOfClass:[SoapArray class]]) {
NSLog(#"Soap");
}
else {
NSLog(#"???");
}
}
it's possible that an instance of SoapArray is also FlyNetArrayOfDataSet. the other possibilities are:
a SoapArray
subclass other than FlyNetArrayOfDataSet.

Removing duplicates from array based on a property in Objective-C

I have an array with custom objects. Each array item has a field named "name". Now I want to remove duplicate entries based on this name value.
How should I go about achieving this?
I do not know of any standard way to to do this provided by the frameworks. So you will have to do it in code. Something like this should be doable:
NSArray* originalArray = ... // However you fetch it
NSMutableSet* existingNames = [NSMutableSet set];
NSMutableArray* filteredArray = [NSMutableArray array];
for (id object in originalArray) {
if (![existingNames containsObject:[object name]]) {
[existingNames addObject:[object name]];
[filteredArray addObject:object];
}
}
You might have to actually write this filtering method yourself:
#interface NSArray (CustomFiltering)
#end
#implementation NSArray (CustomFiltering)
- (NSArray *) filterObjectsByKey:(NSString *) key {
NSMutableSet *tempValues = [[NSMutableSet alloc] init];
NSMutableArray *ret = [NSMutableArray array];
for(id obj in self) {
if(! [tempValues containsObject:[obj valueForKey:key]]) {
[tempValues addObject:[obj valueForKey:key]];
[ret addObject:obj];
}
}
[tempValues release];
return ret;
}
#end
I know this is an old question but here is another possibility, depending on what you need.
Apple does provide a way to do this -- Key-Value Coding Collection Operators.
Object operators let you act on a collection. In this case, you want:
#distinctUnionOfObjects
The #distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
NSArray *distinctArray = [arrayWithDuplicates valueForKeyPath:#"#distinctUnionOfObjects.name"];
In your case, though, you want the whole object. So what you'd have to do is two-fold:
1) Use #distinctUnionOfArrays instead. E.g. If you had these custom objects coming from other collections, use #distinctUnionOfArray.myCollectionOfObjects
2) Implement isEqual: on those objects to return if their .name's are equal
I'm going to get flak for this...
You can convert your array into a dictionary. Not sure how efficient this is, depends on the implementation and comparison call, but it does use a hash map.
//Get unique entries
NSArray *myArray = #[#"Hello", #"World", #"Hello"];
NSDictionary *uniq = [NSDictionary dictionaryWithObjects:myArray forKeys:myArray];
NSLog(#"%#", uniq.allKeys);
*Note, this may change the order of your array.
If you'd like your custom NSObject subclasses to be considered equal when their names are equal you may implement isEqual: and hash. This will allow you to add of the objects to an NSSet/NSMutableSet (a set of distinct objects).
You may then easily create a sorted NSArray by using NSSet's sortedArrayUsingDescriptors:method.
MikeAsh wrote a pretty solid piece about implementing custom equality: Friday Q&A 2010-06-18: Implementing Equality and Hashing
If you are worried about the order
NSArray * newArray =
[[NSOrderedSet orderedSetWithArray:oldArray] array]; **// iOS 5.0 and later**
It is quite simple in one line
NSArray *duplicateList = ...
If you don't care about elements order then (unordered)
NSArray *withoutDUP1 = [[NSSet setWithArray:duplicateList] allObjects];
Keep the elements in order then (ordered)
NSArray *withoutDUP2 = [[NSOrderedSet orderedSetWithArray:duplicateList] array];
Implement isEqual to make your objects comparable:
#interface SomeObject (Equality)
#end
#implementation SomeObject (Equality)
- (BOOL)isEqual:(SomeObject*)other
{
return self.hash == other.hash;
}
- (NSUInteger)hash
{
return self.name;///your case
}
#end
How to use:
- (NSArray*)distinctObjectsFromArray:(NSArray*)array
{
return [array valueForKeyPath:#"#distinctUnionOfObjects.self"];
}

Passing An Array By Reference In Objective-C

I would like to pass a NSMutableArray by reference so that it can be altered by another method. What would be the correct syntax for this?
Thanks,
Objective-C objects are always passed by reference (using pointers) - you can't pass them by value.
I.e. the following is fine:
- (void)mutateArray:(NSMutableArray*)array {
// alter array ...
}
... and can be e.g. invoked like this:
NSMutableArray *array = ...;
[self mutateArray:array];
There is also the possibility of passing a pointer by reference:
- (void)newArray:(NSMutableArray **)array;
In that case array is used as an out-parameter - you pass a reference to a pointer to receive an instance:
- (void)newArray:(NSMutableArray **)array {
*array = [[NSMutableArray alloc] init];
}
... which could be called like so:
NSMutableArray *array = nil;
[self newArray:&array];
Using out-parameters is usually only seen if the return-value is already used and additional information has to be returned. An example would be error-information as dreamlax noted.
In addition to Georg Fritzche's answer, it may be worth noting that some methods expect to be given the address of an object pointer. For example:
NSError *anError; // points to garbage now
NSStringEncoding enc;
NSString *aString = [NSString stringWithContentsOfFile:#"/some/file.txt"
usedEncoding:&enc
error:&anError];
if (aString == nil)
{
// anError now points to an initialised NSError object.
}
It gets tricky because some documented methods require you to release objects obtained in this manner, and some don't (for an example of one that does require explicit releasing, see NSPropertyListSerialization).
As Georg Fritzsche said NSMutableArray passed be reference automatically, but not the NSArray. The best option is too look at the code bellow:
void mutateImmutableArray(NSArray *array);
void mutateMutableArray(NSMutableArray *array);
void mutateImmutableArrayByRef(NSArray **array);
void mutateMutableArrayByRef(NSMutableArray **array);
int main(int argc, const char * argv[]) {
#autoreleasepool {
//Change immutable array in method that expects immutable array
NSArray *immutable = #[#1,#2,#3];
mutateImmutableArray(immutable);
NSLog(#"After 1: %#",immutable); // 1,2,3
//Change mutable array in method that expects immutable array
NSMutableArray *mutable = [#[#1,#2,#3]mutableCopy];
mutateImmutableArray(mutable);
NSLog(#"After 2: %#",mutable); //1,2,3
//Change mutable array in method that expects mutable array
mutable = [#[#1,#2,#3]mutableCopy];
mutateMutableArray(mutable);
NSLog(#"After 3: %#",mutable); //1,2,3, Four
//Change immutable array in method that expects immutable array by reference
immutable = #[#1,#2,#3];
mutateImmutableArrayByRef(&immutable);
NSLog(#"After 4: %#",immutable); //4,5,6
//Change mutable array in method that expects mutable array by reference
mutable = [#[#1,#2,#3]mutableCopy];
mutateMutableArrayByRef(&mutable);
NSLog(#"After 5: %#",mutable); //1,2,3, Four
}
return 0;
}
void mutateImmutableArray(NSArray *array)
{
array = #[#4,#5,#6];
}
void mutateImmutableArrayByRef(NSArray **array)
{
*array = #[#4,#5,#6];
}
void mutateMutableArray(NSMutableArray *array)
{
[array addObject:#"Four"];
}
void mutateMutableArrayByRef(NSMutableArray **array)
{
[*array addObject:#"Four"];
}