What is the value of NSEnumerator? - objective-c

when for . . . in . . . is available?
Specifically, when we can write:
NSArray *array;
// array allocated and initialized here.
for (id obj in array) {
// do something to the object here
}
Why would we ever use an NSEnumerator?

NSEnumerator was created before fast enumeration (for/in loop) was available. Think of it as backward-compatibility if you like.
But with NSEnumerator you can enumerate the collection in customized order, e.g. backwards:
NSEnumerator* enu = [array reverseObjectEnumerator];
id object;
while ((object = [enu nextObject])) {
...
}
(Of course, since NSEnumerator also supports for/in loop you can use a better way:
for (id object in [array reverseObjectEnumerator]) {
...
}
)
or define your own iterator class by subclassing NSEnumerator, e.g.
#import <Foundation/Foundation.h>
#interface RangeEnumerator : NSEnumerator {
int cur, len;
}
+(RangeEnumerator*)enumeratorWithLength:(int)length;
-(id)initWithLength:(int)length;
-(id)nextObject;
#end
#implementation RangeEnumerator
-(id)initWithLength:(int)length {
if ((self = [super init]))
len = length;
return self;
}
+(RangeEnumerator*)enumeratorWithLength:(int)length {
return [[(RangeEnumerator*)[self alloc] initWithLength:length] autorelease];
}
-(id)nextObject {
if (cur < len)
return [NSNumber numberWithInt:cur++];
else
return nil;
}
#end
int main () {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
for (NSNumber* num in [RangeEnumerator enumeratorWithLength:12])
printf("%d\n", [num intValue]);
[pool drain];
return 0;
}

Related

Archiving objects within objects in objective-c

Thankyou for reading,
PS: I am a beginner so I am not too good at this unfortunetaly, but any help would be very appreciated
So basically I want to archive a big array which contains Account objects, which they themselves contain:
1.a username in form of a NSString,
2.an encrypted password array filled with NSNumbers, and
3.a data array filled with service data objects.
The service data objects have the following:
encrypted serviceType (NSArray filled with NSNumbers) (whatever service the username and password is for)
encrypted username (NSArray filled with NSNumbers)
encrypted password (NSArray filled with NSNumbers)
Now weirdly when trying to archive and save this, I get two errors. One time it won't let me add service data objects to the data array in the Account class anymore, with the following error message (or at least they dont show up in the NSTableView I have, however it does say they exsist):
[<ServiceData 0x60000023bfa0> valueForUndefinedKey:]: this class is not key value
coding-compliant for the key service.
and two, when I try to login in the the username and password from the Account class, it retrieves the username and the first couple and last couple NSNumbers of my password correctly, but the rest of the NSNumbers for the password are in the trillions or something, so I'm wondering what is going wrong, any help would be greatly appreciated.
Here is the code for my instance variables, how I used the NSKeyedArchiver and unarchiver, and how I went about saving and loading the files. Again, please help me, I have been stuck on this for a while and this is kind-of my last resort. I have no idea what is happening!
ACCOUNT CLASS:
H file:
#interface Account : NSObject <NSCoding>
{
#private
NSString *username;
NSMutableArray *password;
NSMutableArray *accData;
}
#property NSString *username;
#property NSArray *password;
FULL M file:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
SERVICEDATA CLASS:
H file:
#interface ServiceData : NSObject <NSCoding>
{
#private
NSArray* serviceData;
NSArray* usernameData;
NSArray* passwordData;
}
#property NSArray* serviceData;
#property NSArray* usernameData;
#property NSArray* passwordData;
M file:
#import "Account.h"
#import "Crypt.h"
NSMutableArray *accounts;
NSInteger accountNumber = -1;
#implementation Account
#synthesize username;
#synthesize password;
- (id)initWithUsername:(NSString *)name withPassword:(NSMutableArray *)key
{
self = [super init];
if (self)
{
username = name;
password = key;
accData = [[NSMutableArray alloc]init];
}
return self;
}
/*
setters and getters
*/
-(NSString*)getUsername;
{
return username;
}
-(NSArray*)getPassword;
{
return password;
}
-(void)changePassword:(NSMutableArray*)newPassword;
{
NSInteger sizeOldPass = [password count];
NSInteger sizeNewPass = [newPassword count];
int changeXObjects = (int)(sizeNewPass - sizeOldPass);
int changeSize = abs(changeXObjects);
//adjusts size differences
if (changeXObjects < 0)
{
for(int i = 0; i < changeSize; i++)
{
[password removeLastObject];
}
}
else if (changeXObjects > 0)
{
for(int i = 0; i < changeSize; i++)
{
NSNumber *value = [NSNumber numberWithInt:0];
[password addObject:value];
}
}
//change password
NSInteger sizePass = [password count];
for (int k = 0; k < sizePass; k++)
{
[password replaceObjectAtIndex:k withObject:newPassword[k]];
}
}
-(NSMutableArray*)getAccData;
{
return accData;
}
-(void)setAccData:(NSMutableArray*)input
{
[input setArray: accData];
}
+(NSMutableArray*)getAccounts
{
return accounts;
}
+(NSInteger)getAccountNumber
{
return accountNumber;
}
+(void)setAccounts:(id)accs
{
accounts = accs;
}
+(void)setAccountNumber:(NSInteger)number
{
accountNumber = number;
}
/*
other methods
*/
+(void)addAccount:(id)acc
{
[accounts addObject:acc];
}
+(void)deleteAccount:(NSInteger)index
{
[accounts removeObjectAtIndex:index];
}
-(void)addAccData:(id)input
{
[accData addObject:input];
}
-(void)deleteAccDataAt:(NSInteger)index
{
[accData removeObjectAtIndex:index];
}
+(bool)checkPassword:(NSString*)passwordIn accountNumber:(NSInteger)index
{
NSMutableArray *passwordInputCrypt = [Crypt encrypt:passwordIn];
NSMutableArray *passwordCrypt = [accounts[index] getPassword];
NSInteger lengthPassword = [passwordInputCrypt count];
bool correctPassword = true;
if([passwordCrypt count] == [passwordInputCrypt count])
{
for(int i = 0; i < lengthPassword; i++)
{
if(passwordCrypt[i]!=passwordInputCrypt[i])
correctPassword = false;
}
}
else
{
correctPassword = false;
}
if(correctPassword == true)
{
return true;
}
return false;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if(self)
{
username = [aDecoder decodeObjectForKey:#"username"];
password = [aDecoder decodeObjectForKey:#"password"];
accData = [aDecoder decodeObjectForKey:#"data"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:username forKey:#"username"];
[aCoder encodeObject:password forKey:#"password"];
[aCoder encodeObject:accData forKey:#"data"];
}
#end
LOADING FILE(filePath is given):
NSData *data = [[NSFileManager defaultManager] contentsAtPath:filePath];
if(data != nil)
{
NSArray *arrayFromData = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSMutableArray *initArray = [NSMutableArray arrayWithArray:arrayFromData];
[Account setAccounts:initArray];
}
else
{
NSMutableArray *accountsInit = [[NSMutableArray alloc] init];
[Account setAccounts:accountsInit];
}
SAVING FILE:
NSArray *accounts = [Account getAccounts];
NSString *filePath = [AppController getFilePath];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:accounts];
[data writeToFile:filePath atomically:YES];
A few things:
You should not be archiving password data to disk (even if you are encrypting it). That's what the keychain is for. Have a look at SSKeychain for a good wrapper class.
The Key-value coding error you are getting suggests you are trying to reference your serviceData array as just "service" somewhere. Check your valueForKey and setValueForKey statements.
Can you post the rest of the Account class? That method setAccounts looks like it might be relevant.
Also is there a reason you are using Keyed Archiving instead of Core Data?

NSMutableArray subclass init

I'm trying to make a function for a NSMutableArray subclass that only uses integer, but I don't want to use "count." How do I do this?
-(NSMutableArrayWithIntegers*)initWithCount:(NSInteger)count numbers:(NSInteger)firstInt, ...
{
self = [super init];
if (self) {
va_list args;
va_start(args, firstInt);
NSInteger arg = firstInt;
for (int i = 0; i < count; i++)
{
arg = va_arg(args, NSInteger);
[self addObject: [NSNumber numberWithInteger:arg]];
}
va_end(args);
}
return self;
}
I know this doesn't answer your question but it's important to let you know. Don't ever subclass NSMutableAnything. Use a category and thank me later:
#interface NSMutableArray (ListOfIntegers)
+(NSMutableArray)mutableArrayWithIntegers:(NSInteger)i, ... {
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:whatever];
// do your thing
return array;
}
#end
First of all, the approach you currently have is just fine. Don't try getting rid of the count. There are alternatives, but they are only worse.
For example, you may use a sentinel value (which may not be inserted into the array) as the last argument, but in this case, you will have to make sure that you are not actually trying to insert this value to the array at all:
- (id)initWithIntegers:(NSInteger)first, ...
{
if (!(self = [super init])) return nil;
va_list args;
va_start(args, first);
NSInteger n;
if (first != NSIntegerMax) {
[self addObject:#(first)];
while ((n = va_arg(args, NSInteger)) != NSIntegerMax) {
[self addObject:#(n)];
}
}
va_end(args);
return self;
}
But really, this unnecessarily narrows the range of values that can be added - using that count argument is not a big deal.

Is it possible to use a wildcard in KVC?

I'm trying to use wildcard in KVC like this.
Is it possible?
Or Is there other ways to use a wildcard to indicate a member variable?
#interface MyClass : NSObject
#property(nonatomic, retain) NSNumber *test1;
#property(nonatomic, retain) NSNumber *test2;
#end
#implementation MyClass{
NSNumber * test1;
NSNumber * test2;
}
#synthesize test1;
#synthesize test2;
#end
using wildcard
MyClass *testClass = [[[MyClass alloc] init] autorelease];
testClass.test1 = #50;
NSLog(#"test value : %#", [testClass valueForKey:#"*1"]);
For detail codes.
A real reason i wanted is to indicate a member variable of instance by value of integer or nsnumber type.
If possible, it is easier to set values and read values of any instance.
For example of property part copy.
MyClass *testClass = [[[MyClass alloc] init] autorelease];
testClass.year_1 = #2012;
testClass.quarter_2 = #3;
testClass.month_3 = #8;
testClass.day_4 = #20;
testClass.week_5 = #4;
// copy propertys to other instance.
// Normal way
MyClass *testClassCopy = [[[MyClass alloc] init] autorelease];
testClassCopy.year_1 = testClass.year_1;
testClassCopy.quarter_2 = testClass.quarter_2;
testClassCopy.month_3 = testClass.month_3;
testClassCopy.day_4 = testClass.day_4;
// copy propertys by using wildcard
for (int j = 0; j < 4; j++) {
NSString *indicate = [NSString stringWithFormat:#"*%#", [NSNumber numberWithInteger:j + 1]];
NSNumber *sourceProperty = [testClass valueForKey:indicate];
[testClassCopy setValue:sourceProperty forKey:indicate];
}
I'll raise your wildcards by adding Regex, and by using categories:
To read about how regex works with this, please read the NSRegularExpression Class Reference.
Features:
Uses regex, for matching of a wide variety of keys
Uses a category that works on any instance
Caches key lists per class
Full KVC support (not just properties, but accessor methods & iVars too!)
Integrates flawlessly with current KVC methods (only uses the regex if the key wasn't found, improving performance)
Subclassing doesn't mess it up, like #JamesWebster's solution
Doesn't needlessly pollute the list of keys with NSObject's methods
Returns a NSDictionary of matched keys & values
Cons:
Uses regex, which is slower and more complex to understand
Slow initial lookup for a class (must iterate through all methods & iVars)
Automatically overwrites the -valueForUndefinedKey: method, so it's possible that this could break some existing code (move it to it's own method to fix).
Currently doesn't support setting of values (by design, that's a whole other bag of cats).
Can have duplicate keyPaths in the result (not the biggest of issues, but stems from the fact that KVC matching is complex, and I have to implement all of the rules)
Uses NSRegularExpression, which is only available in iOS 4 and later (not the largest of issues).
Version History:
1.0: Initial Release
So, here is the code:
NSObject+KVCRegex.h:
//
// NSObject+KVCRegex.h
// TestProj
//
// Created by Richard Ross on 8/20/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface NSObject (KVCRegex)
// custom implemenation
-(id) valueForUndefinedKey:(NSString *)key;
#end
NSObject+KVCRegex.m:
//
// NSObject+KVCRegex.m
// TestProj
//
// Created by Richard Ross on 8/20/12.
// Copyright (c) 2012 Ultimate Computer Services, Inc. All rights reserved.
//
#import "NSObject+KVCRegex.h"
#import <objc/runtime.h>
#implementation NSObject (KVCRegex)
static NSSet *keyPathsForClass(Class cls)
{
NSMutableSet *keys = [NSMutableSet set];
do
{
if (cls == [NSObject class])
{
// nothing good can come from trying to use KVC on NSObject methods
break;
}
unsigned count = 0;
Method *methods = class_copyMethodList(cls, &count);
for (int i = 0; i < count; i++) {
// make sure that the method returns a value
const char *methodName = sel_getName(method_getName(methods[i]));
char returnType[64];
method_getReturnType(methods[i], returnType, 64);
if (strcmp(returnType, "v") == 0)
continue;
// make sure that the method takes no args (except for self & _cmd)
if (method_getNumberOfArguments(methods[i]) == 2)
{
// add a duplicate entry for ones matching 'is'
if (strstr(methodName, "is") == methodName)
{
char *newStr = strdup(methodName + 2);
newStr[0] = tolower(newStr[0]);
[keys addObject:[NSString stringWithUTF8String:newStr]];
free(newStr);
}
[keys addObject:[NSString stringWithUTF8String:methodName]];
}
}
free(methods);
// now copy iVars
count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i < count; i++)
{
const char *ivarName = ivar_getName(ivars[i]);
if (strstr(ivarName, "_") == ivarName)
[keys addObject:[NSString stringWithUTF8String:ivarName + 1]]; // iVar name starting with _<key>
[keys addObject:[NSString stringWithUTF8String:ivarName]];
}
free(ivars);
} while ((cls = [cls superclass]));
return [NSSet setWithSet:keys];
}
// returns a dictionary based on 'key' as a regex
-(id) valueForUndefinedKey:(NSString *)key
{
// lookup for later use
static NSMutableDictionary *keyClassPairs;
if (!keyClassPairs)
keyClassPairs = [NSMutableDictionary dictionary];
if (!keyClassPairs[[self class]])
{
keyClassPairs[(id<NSCopying>)[self class]] = keyPathsForClass([self class]);
}
NSSet *keyPaths = keyClassPairs[[self class]];
// assume 'key' is a regex
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:key options:0 error:nil];
NSMutableArray *matches = [NSMutableArray array];
for (NSString *keyPath in keyPaths)
{
NSRange matchRange = [regex rangeOfFirstMatchInString:keyPath options:0 range:(NSRange) { 0, keyPath.length }];
if (matchRange.length == keyPath.length)
{
// we have a match
[matches addObject:keyPath];
}
}
if (matches.count)
return [self dictionaryWithValuesForKeys:matches];
else
[NSException raise:NSUndefinedKeyException format:#"Could not find a key that matches the regex in %#", key];
return nil;
}
#end
Example:
#interface MyObject : NSObject
{
#public
int normalIvar;
id _underscoreIvar;
}
#property id someProp;
#property BOOL isProperty;
#property int nativeProp;
-(void) notAKey;
-(id) aKey;
#end
#implementation MyObject
#synthesize someProp, isProperty, nativeProp;
-(void) notAKey
{
NSLog(#"Not a key!");
}
-(id) aKey
{
return #"Value";
}
#end
int main()
{
#autoreleasepool {
MyObject *obj = [MyObject new];
obj.someProp = #"a property";
obj.nativeProp = 15;
obj.isProperty = YES;
obj->normalIvar = 172;
obj->_underscoreIvar = #"Ivar";
NSString *regex = #"[a|s].*"; // match a key starting with 'a' or 's', then matching anything else after
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ aKey = 'Value', someProp = 'a property' }"
regex = #"_.*"; // match a key starting with '_', and then match anything else after
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ _underscoreIvar = 'Ivar' }"
regex = #".*"; // match any key declared for this object
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ "_underscoreIvar" = Ivar; aKey = Value; isProperty = 1; nativeProp = 15; normalIvar = 172; property = 1; someProp = "a property"; underscoreIvar = Ivar; }"
regex = #"(?i)[A-J].*"; // match (case insensitive) a key starting with A - J
NSLog(#"%#", [obj valueForKey:regex]); // prints "{ aKey = value; isProperty = 1; }"
}
}
Though I couldn't find a way to support wildcards using the syntax you were attempting. I found this roundabout method using the Objective-C runtime!
First we get all of the properties of the class you'd like to use
#import <objc/runtime.h>
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList([MyClass class], &outCount);
NSMutableArray *array = [NSMutableArray arrayWithCapacity:outCount];
for (int i = 0; i < outCount; i++)
{
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName)
{
NSString *propertyName = [NSString stringWithUTF8String:propName];
[array addObject:propertyName];
}
}
free(properties);
Then filter out the ones you actually want
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF ENDSWITH '1'"];
[array filterUsingPredicate:predicate];
Then actually use them
for (NSString *key in array)
NSLog(#"%#", [testClass valueForKey:key]);

Custom single KeyValuePair class vs NSMutableDictionary

I came into a situation where I had to write a loop with a good amount if iterations and in this loop I had a NSData object that I had to associate with a key. This lead me to search for a simple objective-c _KeyValuePair_ class but coulnt not find one so I wrote my own. Now I'm curious to see if there is any benefit over just using an NSMutableDictinoary holding just 1 key and value. After trying both throughout my project I can't tell much difference on the App UI side or with Instruments Time Profiler.
So my questions are:
Could a single kvpair class be more efficient than a NSMutableDictionary
Does a NSMutableDict allocate any larger amount of space by default then this does
Is there actually a standard single key value pair class that I just missed
Some code:
for (int i = 0, count = [photoUrls count]; i < count; ++i) {
// Example usage of the kvp class
NSMutableDictionary *imageRequest = [[NSMutableDictionary alloc] init];
JHKeyValuePair *kvPair = [[JHKeyValuePair alloc] initWithKey:#"DAILY" andValue:[NSNumber numberWithInt:i];
[imageRequest setObject:self forKey:#"delegate"];
[imageRequest setObject:kvPair forKey:#"userInfo"];
[kvPair release];
[imageRequest setObject:[dailySpecialData objectForKey:#"IMAGE_URL"] forKey:#"url"];
[imageDownloader addDownloadRequestToQueue:imageRequest];
[imageRequest release];
}
JHKeyValuePair.h
#interface JHKeyValuePair : NSObject {
id key;
id value;
}
#property (nonatomic, retain) id key;
#property (nonatomic, retain) id value;
- (id)initWithKey:(id)aKey andValue:(id)aValue;
#end
JHKeyValuePair.m
#import "JHKeyValuePair.h"
#implementation JHKeyValuePair
#synthesize key;
#synthesize value;
- (id)initWithKey:(id)aKey andValue:(id)aValue {
if ((self = [super init])) {
key = [aKey retain];
value = [aValue retain];
}
return self;
}
- (void)dealloc {
[key release], key = nil;
[value release], value = nil;
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone {
JHKeyValuePair *copy = [[JHKeyValuePair allocWithZone:zone] init];
[copy setKey:self.key];
[copy setValue:self.value];
return copy;
}
- (BOOL)isEqual:(id)anObject {
BOOL ret;
if (self == anObject) {
ret = YES;
} else if (![anObject isKindOfClass:[JHKeyValuePair class]]) {
ret = NO;
} else {
ret = [key isEqual:((JHKeyValuePair *)anObject).key] && [value isEqual:((JHKeyValuePair *)anObject).value];
}
return ret;
}
#end
Edit to fix the initial explanation. Seems I got sidetracked mid-sentance and never came back to finish it.
If you really want to get speed you are doing a lot of unnecessary retain releases that probably aren't necessary every time you set your key/values. If you use a struct and some basic c code you can achieve something a little quicker but you sacrifice the simple and consistent memory management you get from doing it the objective c way.
typedef struct {
id key;
id value;
} Pair;
BOOL isEqual(Pair a, Pair b); //...
// You will need to clean up after yourself though:
void PairRelease(Pair p) {
[p.key release];
[p.value release];
}

Get n random objects (for example 4) from nsarray

I have a large NSArray of names, I need to get random 4 records (names) from that array, how can I do that?
#include <stdlib.h>
NSArray* names = ...;
NSMutableArray* pickedNames = [NSMutableArray new];
int remaining = 4;
if (names.count >= remaining) {
while (remaining > 0) {
id name = names[arc4random_uniform(names.count)];
if (![pickedNames containsObject:name]) {
[pickedNames addObject:name];
remaining--;
}
}
}
I made a caregory called NSArray+RandomSelection. Just import this category into a project, and then just use
NSArray *things = ...
...
NSArray *randomThings = [things randomSelectionWithCount:4];
Here's the implementation:
NSArray+RandomSelection.h
#interface NSArray (RandomSelection)
- (NSArray *)randomSelectionWithCount:(NSUInteger)count;
#end
NSArray+RandomSelection.m
#implementation NSArray (RandomSelection)
- (NSArray *)randomSelectionWithCount:(NSUInteger)count {
if ([self count] < count) {
return nil;
} else if ([self count] == count) {
return self;
}
NSMutableSet* selection = [[NSMutableSet alloc] init];
while ([selection count] < count) {
id randomObject = [self objectAtIndex: arc4random() % [self count]];
[selection addObject:randomObject];
}
return [selection allObjects];
}
#end
If you prefer a Swift Framework that also has some more handy features feel free to checkout HandySwift. You can add it to your project via Carthage then use it like this:
import HandySwift
let names = ["Harry", "Hermione", "Ron", "Albus", "Severus"]
names.sample() // => "Hermione"
There is also an option to get multiple random elements at once:
names.sample(size: 3) // => ["Ron", "Albus", "Harry"]
I hope this helps!