Searching an NSMutableArray of classes - objective-c

I have my own class defined as below.
#interface PersonList : NSObject
#property(nonatomic, strong)NSNumber *ID;
#property(nonatomic, strong)NSString *FirstName;
#property(nonatomic, strong)NSString *SecondName;
#end
I use it like the following method:
PersonList *P = [[PersonList alloc]init];
[P setID: ...];
[P setFirstname:...];
[P setSecondname:...];
then add it to an array.
[PersonListArray addObject:P];
What I'm trying to do is search this array for the class where ID = x.
Is it the best way?
for(int i = 0; i < PersonListArray.count; i++)
{
PersonListArray *aPersonListArray = [PersonListArray objectAtIndex:i];
if(aPersonListArray.ID == x)
{
//Do what i want here
//break;
}
}
Thanks

You can use this NSArray method that makes things a lot easier and is also very optimized:
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
Your code should then look like that:
NSInteger personIndex = [PersonListArray indexOfObjectPassingTest:^BOOL(PersonList person, NSUInteger idx, BOOL *stop) {
return [person.ID isEqualToNumber:x];
}];
PersonList personList = PersonListArray[personIndex]
Two more things:
you might consider not capitalizing your variables, to follow conventions.
If you want to compare values of objects in ObjC, use the equalTo methods, not the == sign which is for comparing pointers
Hope this helps,

There is another way, a little bit more simple:
for(PersonList *AnyPerson in PersonListArray)
{
if([AnyPerson.ID isEqualToNumber:x])
{
//do what you want
}
}

You could do this like this:
for(PersonList *person in PersonListArray){
if([person.ID isEqualToNumber: x]){
// do your job, it you want to do it for the first case only
// use break here or return depends on the case
}
}
Take a look at the way of comparing values (if you want sth more than equality consider usage of compare: method)
BTW It might be valuable for you to take a look on the possibilities of sorting and searching arrays in case of possibilities and performance, take a look at this.

Try this
#interface PersonList ()
#property (nonatomic, strong) NSMutableArray *persons;
#end
#implementation PersonList
-(NSMutableArray *)persons{
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
_persons=[[NSMutableArray alloc] init];
});
return _persons;
}
-(instancetype)initWithIDs:(NSArray *)personIDs FirstNames:(NSArray *)firstNames SecondNames:(NSArray *)secondNames{
if(self=[super init]){
[personIDs enumerateObjectsUsingBlock:^(id personID, NSUInteger idx, BOOL *stop) {
NSMutableDictionary *person=[[NSMutableDictionary alloc] init];
[person setObject:personID forKey:#"ID"];
[person setObject:[firstNames objectAtIndex:idx] forKey:#"FIRSTNAME"];
[person setObject:[secondNames objectAtIndex:idx] forKey:#"SECONDNAME"];
[self.persons addObject:person];
}];
}
return self;
}
-(NSDictionary *)findPersonByID:(NSString *)personID{
__block NSDictionary *dictionary=[[NSDictionary alloc] init];
[self.persons enumerateObjectsUsingBlock:^(id person, NSUInteger idx, BOOL *stop) {
if ([[person objectForKey:#"ID"] isEqualToString:personID]) {
dictionary=person;
*stop=YES;
}
}];
return dictionary;
}
#end

Related

NSDictionary enumerate second level

Consider the following NSDictionary:
When expanded, it looks like this:
I would like to create an NSObject of each of the "features" keys, with "geometry" and "type" as a property, but I can't get my head around the
[myDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
approach. Isn't there a way to indicate the entrance level by giving a key name?
I DID do a lot of searching around, but none of the stuff I found seemed to be appropriate. I hope you have some patience with me.
Here is the way I would create the feature objects:
#interface Feature : NSObject
#property (nonatomic, strong) NSString *geometry; //case is important
#property (nonatomic, strong) NSString *type; //case is important
#end
Then I would use KVC to create it. So I need to implement the method setValue:forUndefinedKey to avoid crash:
#implementation Feature
- (void) setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(#"key %# does not exist", key);
}
#end
Then I assume you want an array of feature object:
NSMutableArray *featureArray = [[NSMutableArray alloc] init];
And to fill this array:
NSMutableArray *featureArray = [[NSMutableArray alloc] init];
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
NSLog(#"key: %#", key);
if ([obj isKindOfClass:[NSArray class]])
{
NSArray *arr = (NSArray *)obj;
for (NSDictionary *childDic in arr) {
Feature *f = [[Feature alloc] init];
[childDic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull childKey, id _Nonnull childObj, BOOL * _Nonnull childStop) {
[f setValue:childObj forKey:childKey];
}];
[featureArray addObject:f];
}
}
}];
Hope this helps.
If you know the keys that are stored, you could do this:
NSMutableArray * pmaMyObjs= [NSMutableArray array];
NSArray* paFeatures= [myDict objectForKey:#"features"];
for(int i= 0; i < paFeatures.count; ++i)
{
NSDictionary * pdItem= [paFeatures objectAtIndex:i];
MyObj* pMyObj= [MyObj alloc] initWithGeo:[pdItem objectForKey:#"geometry"] properties:[pdItem objectForKey:#"properties"];
[pmaMyObjs addObject:pMyObj];
}

Duplicated custom object in NSSet

I have some problems about the NSMutableSet in Objective-C.
I learnt that the NSSet will compare the two objects' hash code to decide whether they are identical or not.
The problems is, I implemented a class that is subclass of NSObject myself. There is a property NSString *name in that class. What I want to do is when instances of this custom class has the same variable value of "name" , they should be identical, and such identical class should not be duplicated when adding to an NSMutableSet.
So I override the - (NSUInteger)hash function, and the debug shows it returns the same hash for my two instances obj1, obj2 (obj1.name == obj2.name). But when I added obj1, obj2 to an NSMutableSet, the NSMutableSet still contained both obj1, obj2 in it.
I tried two NSString which has the same value, then added them to NSMutableSet, the set will only be one NSString there.
What could be the solution? Thank you for any help!
The custom Class:
Object.h:
#import <Foundation/Foundation.h>
#interface Object : NSObject
#property (retain) NSString *name;
#end
Object.m
#implementation Object
#synthesize name;
-(BOOL)isEqualTo:(id)obj {
return [self.name isEqualToString:[(Object *)obj name]] ? true : false;
}
- (NSUInteger)hash {
return [[self name] hash];
}
#end
and main:
#import <Foundation/Foundation.h>
#import "Object.h"
int main(int argc, const char * argv[])
{
#autoreleasepool {
Object *obj1 = [[Object alloc]init];
Object *obj2 = [[Object alloc]init];
obj1.name = #"test";
obj2.name = #"test";
NSMutableSet *set = [[NSMutableSet alloc] initWithObjects:obj1, obj2, nil];
NSLog(#"%d", [obj1 isEqualTo:obj2]);
NSLog(#"%ld", [set count]);
}
return 0;
}
Instead of implementing isEqualTo: you have to implement isEqual:
- (BOOL)isEqual:(id)object {
return [object isKindOfClass:[MyObject class]] &&
[self.name isEqual:[(MyObject *)object name]];
}
This will (probably falsely) return NO if both self.name and object.name are nil. If you want to return YES if both properties are nil you should use
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[MyObject class]]) {
return (!self.name && ![(MyObject *)object name]) ||
[self.name isEqual:[(MyObject *)object name]];
}
return NO;
}

Fast Enumeration on an NSArray category for NSIntegers

Since I use NSInteger arrays frequently, I wrote a category for NSArray (and one for NSMutableArray too) that adds methods such as integerAtIndex:, arrayByAddingInteger:, etc. The methods take care of wrapping/unwrapping the NSInteger in an NSNumber object.
What I'm wondering is whether there is a way I can enhance my category so that I can do fast enumeration on the NSIntegers. I would like to be able to write:
NSArray* arrayOfIntegers;
.
.
.
for(NSInteger nextInteger in arrayOfIntegers)
{
}
….so that "nextInteger" is pulled out of the NSNumber object behind the scenes. Can I do this?
I doubt that there is a clean way of doing this with NSFastEnumeration, as it heavily depends on the nextObject method.
But, you could do it in another way, by adding a block method for it:
#interface NSArray (Integers)
-(void)eachInteger:(void(^)(NSInteger))block;
#end
#implementation NSArray (Integers)
-(void)eachInteger:(void(^)(NSInteger))block {
for (NSNumber *num in self) {
block(num.integerValue);
}
}
#end
That way, you could use it in your code in a similar way:
NSArray *arr = [NSArray arrayWithObjects:[NSNumber numberWithInt:23],
[NSNumber numberWithInt:42],
nil];
...
[arr eachInteger:^(NSInteger i) {
NSLog(#"The int is %i", i);
}];
// =>
// The int is 23
// The int is 42
Perhaps you might want to take a look at the NSArray categories on the Lumumba Framework, which happens to be written by me :D
This exactly cannot be done, but you can easily convert your NSNumber into an NSInteger and use that later on. You can even write a macro for it:
#define int_enum(var, arr, block) \
for(NSNumber *__tmp in arr) { NSInteger var = [__tmp integerValue]; block }
Use it like:
NSArray *array = // whatever;
int_enum(counter, array, {
// things you want to do with `counter' as an NSInteger
});
if you really like blocks, try this out:
#interface NSArray(blockIteration)
#property (copy, nonatomic, readonly) void (^forEachObject)(void (^block)(NSArray *, id));
#property (copy, nonatomic, readonly) void (^forEachInt)(void (^block)(NSArray *, int));
#property (copy, nonatomic, readonly) void (^forEachDouble)(void (^block)(NSArray *, double));
#end
#implementation NSArray(blockIteration)
-(void (^)(void (^)(NSArray *, id))) forEachObject
{
return [^(void (^block)(NSArray *, id)) {
block = [block copy];
for (id obj in self)
{
block(self, obj);
}
} copy];
}
-(void (^)(void (^)(NSArray *, int))) forEachInt
{
return [^(void (^block)(NSArray *, int)) {
block = [block copy];
for (NSNumber *num in self)
{
block(self, [num intValue]);
}
} copy];
}
-(void (^)(void (^)(NSArray *, double))) forEachDouble
{
return [^(void (^block)(NSArray *, double)) {
block = [block copy];
for (NSNumber *num in self)
{
block(self, [num doubleValue]);
}
} copy];
}
#end
int main()
{
NSArray *array = [NSArray arrayWithObjects:#"Hello", #"World", #"This", #"Is", #"A", #"Test", nil];
array.forEachObject(^(id arr, id obj) {
NSLog(#"%#", obj);
});
}
Note that this implementation is ARC dependent.

iOS - Storing groups of UILabels into a NSMutableArray

I'm creating UILabels dynamically in a for each loop. Every loop that is run creates 1-4 UILabels.
What I want is that I put these UILabels into my NSMutableArray and being able later to easy retrieve the data.
My original thought was to put these UILabels into a NSDictionary and use [dictGroupLabels setValue:uiLabel1 forKey:#"uiLabel1"] and then [dictGroupLabels setValue:uiLabel2 forKey:#"uiLabel2"] and so on. And then put this dictionary into my NSMutableArray for each loop. Later on I could access the values like UILabel *label = [[myArray objectAtIndex:0] valueForKey:#"uiLabel1"] BUT that unfortunately doesn't work since UILabels don't conform to the NSCopying protocol.
So with this in mind how would you solve this?
this question provided more information on what you are trying to accomplish. Since you know for a fact, the possible set of labels you are trying to create in each case, I would highly recommend using mutable dictionaries instead of arrays.
To illustrate, given the following hypothetical class definition:
#interface MyClass: NSObject {
NSMutableDictionary * _labelDict;
}
#property (nonatomic, retain) NSMutableDictionary * labelDict;
- ( void )methodA;
- ( void )methodB;
- (NSMutableDictionary *) labelsForRunLoop: (NSUInteger) loopIdx;
#end
You would have the following, hypothetical, class implementation:
#implementation MyClass
#synthesize labelDict = _labelDict;
- ( id ) init {
if( ( self = [ super init ] ) ) {
[self setLabelDict: [NSMutableDictionary dictionaryWithCapacity: 8]];
}
}
- ( void ) dealloc {
[ self.labelDict release ];
[ super dealloc ];
}
- ( void ) methodA {
for(NSUInteger i = 0; i < some index; i++) {
[self.labelDict setObject: [self labelsForRunLoop: i] forKey: [NSString stringWithFormat: #"%d", i]];
}
}
- ( void ) methodB {
// Locate the label you need to work with. Example based on this crude pseudo code
NSMutableDictionary * subDict = (NSMutableDictionary *) [self.labelDict objectForKey: #"0"];
UILabel * theLabel = (UILabel * ) [subDict objectForKey: #"UILabel.Z"];
theLabel.text = #"Label 1";
}
- (NSMutableDictionary *) labelsForRunLoop: (NSUInteger) loopIdx {
NSMutableDictionary * dictionary = [NSMutableDictionary dictionaryWithCapacity: 4] ;
[dictionary setObject: create-w-label forKey: #"UILabel.W"];
[dictionary setObject: create-x-label forKey: #"UILabel.X"];
[dictionary setObject: create-y-label forKey: #"UILabel.Y"];
[dictionary setObject: create-z-label forKey: #"UILabel.Z"];
return [dictionary retain];
}
#end
This is basically pseudo code and will not successfully compile. However it will serve as a good starting point. You probably want to store each label dictionary under some key that makes sense, instead of just using the loop's index. Hope this helps.
They don’t need to adhere to NSCopying to be added to an array. It sounds like you just need to do something like this:
NSMutableArray *mainArray = [NSMutableArray array];
for(int i = 0; i < 5; i++)
{
NSMutableArray *subArray = [[NSMutableArray alloc] initWithCapacity:5];
for(int j = 0; j < 4; j++)
{
UILabel *label = [[UILabel alloc] init];
// etc.
[subArray addObject:label];
[label release];
}
[mainArray addObject:subArray];
[subArray release];
}
// then, to get one of the labels:
UILabel *someSpecificLabel = [[mainArray objectAtIndex:2] objectAtIndex:1];

Case insensitive indexOfObject for NSArray

Is there an easy way to do a case insensitive lookup in an NSArray of NSStrings? Reference for NSArray mentions sorting case insensitively but nothing about lookup.
I can easily write my own fn to do it but would like to know if there's an easier way.
I don't know of any built-in way to do this. However, it would be trivial to write a category on NSArray which does this:
#interface NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString;
#end
#implementation NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString {
NSUInteger index = 0;
for (NSString *object in self) {
if ([object caseInsensitiveCompare:aString] == NSOrderedSame) {
return index;
}
index++;
}
return NSNotFound;
}
#end
Of course, you'd probably want to do a bit of type checking to make sure the array's items actually are NSStrings before you call -caseInsensitiveCompare:, but you get the idea.
questioner ,
it's an excellent idea of writing a category in NSArray to do this .
it helped me a lot in my app.
However there's a pretty much easier way to do the this instead
of iterating the array.
#interface NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString;
#end
#implementation NSArray (CaseInsensitiveIndexing)
- (NSUInteger)indexOfCaseInsensitiveString:(NSString *)aString
{
return [self indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL *stop)
{
return [[obj lowercaseString] isEqualToString:[aString lowercaseString]];
}];
}
#end
Note :indexOfObjectPassingTest works with IOS 4.0 only
No custom category needed:
[myArray indexOfObjectPassingTest:^(NSString *obj, NSUInteger idx, BOOL *stop){
return (BOOL)([obj caseInsensitiveCompare:term] == NSOrderedSame);
}]
I haven't tried it but you should be able to do this by filtering the array with an NSPredicate.