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.
Related
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
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;
}
This is code from an addition calculator that does operations by entering the two operands first and then the operation; like "5 enter 2 enter +" would result in "7". When the user taps on a number a double will be sent to pushOperand: When a user taps on the addition button the string #"+" will be sent like to performOperation:. My question is what is the point of making those copies in program and runProgram: if they're all shallow copies and their elements all end up pointing to the same elements of NSNumber and NSString objects as _programStack, program, and stack?
#import <Foundation/Foundation.h>
#interface CalculatorBrain : NSObject
#property (nonatomic, readonly) id program;
+(double)runProgram:(id)program;
-(double)performOperation:(NSString *)operation;
#end
#import "CalculatorBrain.h"
#interface CalculatorBrain ()
#property (nonatomic, strong) NSMutableArray *programStack;
#end
#implementation CalculatorBrain
#synthesize programStack = _programStack;
-(NSMutableArray *) programStack {
if (!_programStack)
_programStack = [[NSMutableArray alloc] init];
return _programStack;
}
-(void)pushOperand:(double)operand {
[self.programStack addObject: [NSNumber numberWithDouble: operand]];
}
-(double)performOperation:(NSString *)operation {
[self.programStack addObject: operation];
double result = [CalculatorBrain runProgram: self.program];
return result;
}
-(id)program {
return [self.programStack copy];
}
+(double)runProgram:(id)program {
NSMutableArray *stack;
if ([program isKindOfClass: [NSArray class]])
stack = [program mutableCopy];
return [self popOperandOffProgramStack: stack];
}
+(double)popOperandOffProgramStack:(NSMutableArray *)stack {
double result = 0;
id topOfStack = [stack lastObject];
if (topOfStack)
[stack removeLastObject];
if ([topOfStack isMemberOfClass: [NSNumber class]])
result = [topOfStack doubleValue];
if ([topOfStack isKindOfClass: [NSString class]]) {
NSString *operation = topOfStack;
if ([operation isEqualToString: #"+"]) {
result = [self popOperandOffProgramStack: stack] + [self popOperandOffProgramStack: stack];
}
return result;
}
#end
NSNumber and NSString are immutable so making a shallow copy of a collection of objects that can't changee is safe.
In program it is important to return a copy of programStack rather than the the actual mutable array. This is because programStack is a private internal property declared in a class extension so it is not externally visible. If you returned programStack directly an external user could change it since it is an NSMutableArray. The program method returns an NSArray since copies of mutable objects are immutable, which has the right semantics. You want to give the external user a snapshot of the programStack array, not access to your class internals.
In runProgram the situation is different. The external user passes CalculatorBrain an NSArray to process and the class' internal logic requires that the stack have elements popped off the array as it is processed. Thus you need to make a mutableCopy so that it can be mutated for processing.
I need to save and load the contents of an array of structs, but I know that Objective C is very particular about which data types you can read/write with.
Here is my struct:
struct SCourse
{
NSMutableArray* holes; // holds integers (pars)
NSString* name;
int size;
BOOL inUse;
};
#interface CoursesManager : NSObject
{
struct SCourse courses[5];
}
What are the data types I'll need to use? Do they each have different methods needed in order to read/write? I'm just looking for a non-complex way to get all the data I need to and from a file. I could do this quite easily in a language I'm more familiar with (C++), but some of the particulars of Objective-c are still lost on me.
EDIT: Solution (thanks for the help, everyone)
-(void)applicationWillResignActive:(UIApplication *)application {
// save the courses
NSMutableArray* totalWriteArray = [[NSMutableArray alloc] initWithCapacity:MAX_COURSES];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse saveCourse = [coursesManager GetCourseAtIndex:i];
NSNumber* nInUse = [NSNumber numberWithBool:saveCourse.inUse];
NSNumber* nSize = [NSNumber numberWithInt:saveCourse.size];
NSMutableArray* writeArray = [[NSMutableArray alloc] initWithCapacity:4];
[writeArray addObject:nInUse];
[writeArray addObject:nSize];
[writeArray addObject:saveCourse.name];
[writeArray addObject:saveCourse.holes];
[totalWriteArray addObject:writeArray];
}
[totalWriteArray writeToFile:[self saveFilePath] atomically:YES];
}
And for the loading back in...
-(void)loadFile {
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists) {
NSMutableArray* totalReadArray = [[NSMutableArray alloc] initWithContentsOfFile:[self saveFilePath]];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse loadCourse = [coursesManager GetCourseAtIndex:i];
NSMutableArray* loadArray = [totalReadArray objectAtIndex:i];
NSNumber* nInUse = [loadArray objectAtIndex:0];
loadCourse.inUse = [nInUse boolValue];
NSNumber* nSize = [loadArray objectAtIndex:1];
loadCourse.size = [nSize integerValue];
NSString* inName = [loadArray objectAtIndex:2];
loadCourse.name = inName;
NSMutableArray* inHoles = [loadArray objectAtIndex:3];
loadCourse.holes = inHoles;
[coursesManager ReplaceCourseAtIndex:i With:loadCourse];
}
}
}
First thing first. You shouldn't use plain old C structures. The ARC memory management will not appreciate.
If you are familiar with C++, you should maybe use a C++ class instead, which will please the compiler and runtime. Depends on what you want to do.
Array. Use either NSArray or std::vector but please, no plain C arrays. Not sure how ARC will handle this but I suppose it will not appreciate much. Objective-C and C++ both provides all the tools you need to handle collections of whatever.
Serialization. You have several possibilities, one of them is NSCoder.
Last word, with the so called modern syntax, converting things into ObjC objects is quite easy.
BOOL b = YES;
int i = 10;
double d = 3.14;
char* s = "Pouf pouf";
You get the ObjC equivalents with the boxin' thingy:
NSNumber* bo = #( b );
NSNumber* io = #( i );
NSNumber* do = #( d );
NSString* so = #( s );
NSArray* ao = #[ #( i ), do ];
NSDictionary* = #{ #"num" : io, #"str" : #( s ) };
To write something in a file, in one gracious step:
[#{ #"bool" : bo, #"array" : #[ #"string", #10, #( 10 + 20 ) ] }
writeToFile: #"path.plist" atomically: YES];
But the question remains, what are you trying to accomplish?
One easy approach is to store these arrays in an NSMutableDictionary object and use the method:
[mutableDict writeToFile:#"path/to/file" atomically:YES];
To store the data and:
NSMutableDictionary *anotherDict = [NSMutableDictionary dictionaryWithContentsOfFile:#"path/to/file"];
To read the contents back in.
Here's what I'd suggest:
Make a custom class with the properties you want (.h file):
#import <Foundation/Foundation.h>
#interface CustomHolder : NSObject {
NSString *last;
NSString *first;
NSString *middle;
}
#property (strong, nonatomic) NSString *last;
#property (strong, nonatomic) NSString *first;
#property (strong, nonatomic) NSString *middle;
#end
And then set the .m file up so that you can encode/decode the object
#import "CustomHolder.h"
#implementation CustomHolder
#synthesize last, first, middle;
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:first forKey:#"first"];
[encoder encodeObject:last forKey:#"last"];
[encoder encodeObject:middle forKey:#"middle"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
self.first = [decoder decodeObjectForKey:#"first"];
self.last = [decoder decodeObjectForKey:#"last"];
self.middle = [decoder decodeObjectForKey:#"middle"];
}
return self;
}
#end
Then you can just
[NSKeyedArchiver archiveRootObject:obj toFile:[self saveFilePath]] to save and
[NSKeyedUnarchiver unarchiveObjectWithFile:[self saveFilePath]] to load
That's probably the most similar to using C-structs (especially because ARC doesn't let you use structs).
Alright guys, I'm quite confused. So, I have an NSDictionary which is populated by a JSON string which looks like:
{"Success":true,"Devices":[{"UDId":"...","User":"...","Latitude":0.0,"Longitude":0.0}]}
Now, I know how to check if Success is true, but I need to loop through the array of Devices (JSON object) and create an internal array of Devices (internal app object) and I have no idea how to do that. Can someone please explain how to do it?
Here's my Device.m/h:
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#interface Device : NSObject {
NSString *udId;
NSString *name;
NSNumber *latitude;
NSNumber *longitude;
}
#property (nonatomic, retain) NSString *udId;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#pragma mark -
#pragma mark MKAnnotation Properties
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
----
#import "Device.h"
#implementation Device
#synthesize udId, name, latitude, longitude;
- (CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D internalCoordinate;
internalCoordinate.latitude = [self.latitude doubleValue];
internalCoordinate.longitude = [self.longitude doubleValue];
return internalCoordinate;
}
- (void)dealloc {
[udId release];
udId = nil;
[name release];
name = nil;
[latitude release];
latitude = nil;
[longitude release];
longitude = nil;
[super dealloc];
}
#end
And here's the methods where I should be reading the response and converting it to objects I can use:
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
I'd really appreciate some help on this. I just can't seem to wrap my head around it...
Thanks in advance!
You are almost there. In your code where you say:
// READ "DEVICES" AND CONVERT TO OBJECTS
do this:
NSArray * devices = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary * deviceInfo in devices) {
Device * d = [[[Device alloc] init] autorelease];
[d setLatitude:[deviceInfo objectForKey:#"Latitude"]];
[d setLongitude:[deviceInfo objectForKey:#"Longitude"]];
[d setName:[deviceInfo objectForKey:#"User"]];
[d setUdId:[deviceInfo objectForKey:#"UDId"]];
// do some stuff with d
}
What's going on here: I didn't see what JSON library you are using to convert, but presuming it works like TouchJSON or SBJSON, the JSON array is automatically turned into an NSArray instance, while the inner hashes of the NSArray are NSDictionary objects. At the point that you have deserialized that JSON string, everything you're dealing with will be instances of NSString, NSNumber, NSArray and NSDictionary (and depending on the library, NSNull to represent null values).
First you need to define your initializer/constructor for your Device class.
Device.h
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon;
Device.m
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon {
if (self = [super init]) {
self.udid = udid;
self.name = name;
self.latitude = lat;
self.longitude = lon;
}
return self;
}
Then you can initialize a new object like:
Device *dev = [[Device alloc] initWithUdid:#"a udid" name:#"the name" latitude:latNum longitude:lonNum];
So, you should be able to iterate the array and build your Device objects like so:
NSArray *devicesArray = [dict objectForKey:#"Devices"];
for (NSDictionary *d in devicesArray) {
Device *dev = [[Device alloc] initWithUdid:[d objectForKey:#"UDId"]
name:[d objectForKey:#"User"]
latitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]
longitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]];
}
You want to access the array of device dictionaries from the top-level dictionary just as you did the Success value. Then iterating over the dictionaries you can use each's -keyEnumerator method to iterate over its keys.
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
NSArray* deviceArray = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary* dict in deviceArray)
{
for(NSString* key in [dict keyEnumerator])
{
NSLog(#"%# -> %#", key, [dict objectForKey:key]);
}
}
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
Sounds like you need to reuse your line:
[jsonDictionary objectForKey:#"Success"]
try having a look at
[jsonDictionary objectForKey:#"Devices"]
You really need to figure out what type it returns.
If you're lucky, it returns an NSDictionary, or alternately something that you can easily turn into an NSDictionary.