representing time interval in objective-C - iphone

I know that there is a data type called NSTimeInterval, but this is in seconds. I want to have an object representation that would be able to represent a time range, say Thursday 21 June 8:00 - Thursday 21 June 9:00. Later on I want to compare the current date/time and check whether it fits within this range. What is the best way to do this?

I would suggest using two NSDate objects to store the start and end dates. You can easily determine if a date is between them using the timeIntervalSinceDate: method:
- (BOOL)dateInInterval:(NSDate *)testDate {
// date1 is the instance variable containing the starting date
// date2 is the instance variable containing the ending date
return ([testDate timeIntervalSinceDate:date1] > 0 &&
[testDate timeIntervalSinceDate:date2] < 0);
}
You just need to make a class which holds two NSDate objects, making sure the first is before the second, and including this method.
FYI, NSTimeInterval is not a class, its a typedef of double.
Edit
Since you want use these as keys for a dictionary, you could use something similar to this to store and search your data:
#protocol IntervalDictionaryKey <NSObject>
// The class you use as keys for your dictionary must implement this method to determine if a object is in the interval
- (BOOL)intervalContains:(id)object;
#end
#interface IntervalDictionary : NSObject {
NSMutableArray *keys, *values;
}
- (void)addInterval:(id<IntervalDictionaryKey>)interval withObject:(id)object;
- (void)setObject:(id)object forIntervalOf:(id)intervalObject;
- (id)objectForIntervalOf:(id)object;
#end
#implementation IntervalDictionary
- (id)init {
if(self = [super init]) {
keys = [NSMutableArray new];
values = [NSMutableArray new];
}
return self;
}
- (void)dealloc {
[keys release];
[values release];
[super dealloc];
}
- (void)addInterval:(id<IntervalDictionaryKey>)interval withObject:(id)object {
[keys addObject:interval];
[values addObject:object];
}
- (void)setObject:(id)object forIntervalOf:(id)intervalObject {
id<IntervalDictionaryKey> key;
NSUInteger i = 0;
for(key in keys) {
if([key intervalContains:intervalObject]) {
[values replaceObjectAtIndex:i withObject:object];
break;
}
++i;
}
}
- (id)objectForIntervalOf:(id)object {
id<IntervalDictionaryKey> key;
NSUInteger i = 0;
for(key in keys) {
if([key intervalContains:object]) {
return [values objectAtIndex:i];
}
++i;
}
}
#end
Usage:
Example interval class:
#interface DateInterval : NSObject <IntervalDictionaryKey> {
NSDate *date1, *date2;
}
- (BOOL)intervalContains:(NSDate *)date; // this is the same as the dateInInterval method above
#end
#implementation DateInterval
// initializer which sets date1 and date2
- (BOOL)intervalContains:(NSDate *)date {
return ([date timeIntervalSinceDate:date1] > 0 &&
[date timeIntervalSinceDate:date2] < 0);
}
#end
Example usage code:
//intervalX is a DateInterval object, created elsewhere
//objectX is any object, created elsewhere
//objectInX is a NSDate which is part of intervalX, created elsewhere
IntervalDictionary *dict = [IntervalDictionary new];
[dict addInterval:interval0 withObject:object0];
[dict addInterval:interval1 withObject:object1];
[dict objectForIntervalOf:objectIn0]; // returns object0
[dict objectForIntervalOf:objectIn1]; // returns object1
[dict setObject:object2 forIntervalOf:objectIn1]; // changes the object for interval1 to object2
[dict objectForIntervalOf:objectIn1]; // now returns object2

NSDateComponents can be used to store components of time intervals as well as date components. You can add such an object to an NSDate using NSCalendar's dateByAddingComponents:toDate:options:.

From iOS10+ you have NSDateInterval (See https://developer.apple.com/documentation/foundation/nsdateinterval?language=objc).
Is the best data structure to represent a time interval.

Related

NSDate if Statement

I was wondering how I could use NSDate within an if statement - I want to update a UILabel depending what the date is, currently I have the following code to determine the date but don't know how to actually get this within an if statement.
NSDate* date = [NSDate date];
NSDateFormatter* formatter = [[[NSDateFormatter alloc] init] autorelease];
[formatter setDateFormat:#"dd/MM/yyyy"];
NSString *dateString = [formatter stringFromDate:date];
dateLabel.text = dateString;
if (dateString == #"25/05/2012") {
NSLog(#"It's the 25th!");
}
else {
NSLog(#"Not it's not...");
}
Thanks a lot!
If you want to compare two strings use isEqualToString:
if ([dateString isEqualToString:#"25/05/2012"]) {
NSLog(#"It's the 25th!");
}
else {
NSLog(#"Not it's not...");
}
isEqualToString: in NSString class reference
if you want to compare two NSDate use isEqualToDate:
[date1 isEqualToDate:date2]
isEqualToDate: in NSDate class reference
To compare strings you can use isEqualToString: not == (with this you're comparing the pointers).
To compare dates you can use isEqualToDate:.
Depending on what you actually want to achieve you can use next calls:
NSDate:
- (BOOL)isEqualToDate:(NSDate *)anotherDate
- (NSComparisonResult)compare:(NSDate *)anotherDate
NSString:
- (BOOL)isEqualToString:(NSString *)aString
As has been noted, == checks for pointer equality. If you want to match the contents of an NSString or NSDate, or any other class of object, check that class' documentation for isEqual... and compare... methods. (NSDate has isEqualToDate: and compare:; NSString has isEqualToString: and compare: as well as several more specialized comparison methods.
However, depending on just what your aim is in matching dates, comparing NSDates with isEqualToDate: or checking the result of compare: against NSOrderedSame might not do what you want. An NSDate doesn't represent a whole calendar day but rather a specific moment in time. So if you have an NSDate representing 5/25/2012 1:53:13 AM PDT and one representing 5/25/2012 1:55:01 AM PDT, they will be equal or compare as NSOrderedSame.
What you're going for with the NSDateFormatter and string comparison will sort of work for checking whether two NSDates represent the same calendar day (presuming you compare string contents with isEqualToString: instead of using ==), but it's sort of kludgy and will fail in certain edge cases that are more common than you think. Apple provides APIs specifically for such comparisons; there's a section in the Date and Time Programming Guide that explains them well.
NSDate provides
- (BOOL)isEqualToDate:(NSDate *)anotherDate
- (NSComparisonResult)compare:(NSDate *)anotherDate
Here is a category (found here http://webd.fr/637-comparer-deux-nsdate) that offers a neat way to compare NSDates:
#import <Foundation/Foundation.h>
#interface NSDate (Compare)
-(BOOL) isLaterThanOrEqualTo:(NSDate*)date;
-(BOOL) isEarlierThanOrEqualTo:(NSDate*)date;
-(BOOL) isLaterThan:(NSDate*)date;
-(BOOL) isEarlierThan:(NSDate*)date;
//- (BOOL)isEqualToDate:(NSDate *)date; already part of the NSDate API
#end
And the implementation:
#import "NSDate+Compare.h"
#implementation NSDate (Compare)
-(BOOL) isLaterThanOrEqualTo:(NSDate*)date {
return !([self compare:date] == NSOrderedAscending);
}
-(BOOL) isEarlierThanOrEqualTo:(NSDate*)date {
return !([self compare:date] == NSOrderedDescending);
}
-(BOOL) isLaterThan:(NSDate*)date {
return ([self compare:date] == NSOrderedDescending);
}
-(BOOL) isEarlierThan:(NSDate*)date {
return ([self compare:date] == NSOrderedAscending);
}
#end
Simple to use:
if([aDateYouWant ToCompare isEarlierThanOrEqualTo:[NSDate date]]) // [NSDate date] is now
{
// do your thing ...
}

Objective C instance variable initialization in a method

Did any body get this issue?
If I need an instance variable, not as a property, and initialize this variable in a method, then when I need it, it is already released. It happens for autoreleased objects. What is the reason for this?
Usually instance variable should have the whole lifetime of the class object. But it seems if the variable is local to a function, and its a autorelease object, it is released when the function exits.
MyClass.h
#interface MyClass:UIViewController {
NSDate * date;
}
MyClass.m
#implementation MyClass {
- (void) anInit {
date = [NSDate date];
}
- (void) useDate {
NSLog (#"%#", date);
// here date is already release, and get bad access.
}
}
You need to retain date.
An autoreleased object will be released when the autorelease pool is next drained. When this happens has nothing to do with the lifecycle of your object.
Your implementation should look like this:
#implementation MyClass {
- (void) anInit {
date = [[NSDate date] retain]; // or [[NSDate alloc] init]
}
- (void) useDate {
NSLog (#"%#", date);
}
- (void) dealloc {
[date release];
[super dealloc];
}
}
[NSDate date] is a Convenience Constructor and is autoreleased, you need to add a retain call. Also make sure anInit is only called once or you will create a memory leak without calling [date release] first.
- (void) anInit {
date = [[NSDate date] retain];
}

Deep copy of NSMutableDictionary

How can one deep-copy the contents of an NSMutableDictionary to another NSMutableDictionary?
I do not want to use initWithDictionary:copyItems: method as my dictionaries are already initialized.
This might work:
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary
This copies the Keys, But not the values, of an NSDictionary. Of course, If you need a deep copy, take a look at this:
#interface NSDictionary(rross)
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL) copyItems;
#end
#implementation NSDictionary(rross)
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL) copyItems
{
if (copyItems)
{
// get the keys
NSArray *oldKeys = [otherDictionary allKeys];
for (int i = 0; i < oldKeys.count; i++)
{
// add all the new key / value pairs
id key = [oldKeys objectAtIndex:i];
[self setObject:[[[otherDictionary objectForKey:key] copy] autorelease] forKey:[[key copy] autorelease]];
}
}
else
{
[self addEntriesFromOtherDictionary:otherDictionary];
}
}
#end

Sorting an NSMutableArray with custom objects 'overwrites' some objects

For a little iPhone application I am making, I want to sort a NSMutableArray.
I found 2 ways of doing this, but they both result in the same thing. Sorting the array will cause some objects to 'overwrite' eachother.
First off, here is my code:
AppDelegate.h
NSMutableArray* highScores;
Somewhere down that AppDelegate.h, I also make this variable a property so that I can access it from differen classes:
#property (retain, nonatomic) NSMutableArray* highScores;
When my application starts, I read the high scores from a file and import them into my NSMutableArray.
AppDelegate.m
NSMutableData* data = [NSData dataWithContentsOfFile:highScoresPath];
NSKeyedUnarchiver* decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
self.highScores = [decoder decodeObjectForKey:#"highscoresArray"];
The objects I store in this NSMutableArray are from the type HighScore.
HighScore.h
#interface HighScore : NSObject {
int score;
int roundsPlayed;
int wrongAnswers;
NSString* name;
NSDate* datetime;
}
#property int score;
#property int roundsPlayed;
#property int wrongAnswers;
#property (nonatomic, copy) NSDate* datetime;
#property (nonatomic, copy) NSString* name;
- (id) init;
- (void) update:(int)roundScore:(BOOL) correct;
#end
HighScore.m
#import "HighScore.h"
#implementation HighScore
#synthesize score, roundsPlayed, wrongAnswers, name, datetime;
- (id) init
{
self.name = #"";
self.score = 0;
self.roundsPlayed = 0;
self.wrongAnswers = 0;
self.datetime = [NSDate date];
return self;
}
- (void) update:(int)roundScore:(BOOL) correct
{
self.score += roundScore;
if (!correct)
self.wrongAnswers++;
self.roundsPlayed++;
self.datetime = [NSDate date];
}
- (id) initWithCoder:(NSCoder *) decoder
{
self.name = [[decoder decodeObjectForKey:#"name"] retain];
self.score = [decoder decodeIntForKey:#"score"];
self.roundsPlayed = [decoder decodeIntForKey:#"roundsPlayed"];
self.wrongAnswers = [decoder decodeIntForKey:#"wrongAnswers"];
self.datetime = [[decoder decodeObjectForKey:#"datetime"] retain];
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeInt:self.score forKey:#"score"];
[encoder encodeInt:self.roundsPlayed forKey:#"roundsPlayed"];
[encoder encodeInt:self.wrongAnswers forKey:#"wrongAnswers"];
[encoder encodeObject:self.datetime forKey:#"datetime"];
}
- (NSComparisonResult) compareHighscore:(HighScore*) h
{
return [[NSNumber numberWithInt:self.score] compare:[NSNumber numberWithInt:h.score]];
}
#end
Now, when I try to sort my array by using the following code:
NSArray *sortedArray;
sortedArray = [highScores sortedArrayUsingSelector:#selector(compareHighscore:)];
It somehow screws up my highScores array, I get an X amound of highscores with the same score and name.
What am I doing wrong?
I'm noticing that in your initWithCoder: method, you're not doing this:
if (self = [super initWithCoder:coder]) {
// Decode your stuff here
}
Same with your regular init method. There needs to be a call to [super init].
Also, since you defined your string properties as copy and you're using the property syntax, there's no need to retain them. They will be retained for you by the synthesized accessor.
Otherwise, your code looks fine to me. Just remember: every init method must always have a call to a super's init... method.
You're trying to sort using #selector(compare:), not #selector(compareHighscore:), which I presume was your intention.
try
sortedArray = [highScores sortedArrayUsingSelector:#selector( compareHighscore: )];
Post the actual compareHighscore: method. The most important thing is that it has to be consistent, that is if a <= b and b <= c, then a <= c and if a < b and b < c then a < c. If you managed to write a compare method that is not consistent, anything can happen.

Instantiating Custom Class from NSDictionary

I have a feeling that this is stupid question, but I'll ask anyway...
I have a collection of NSDictionary objects whose key/value pairs correspond to a custom class I've created, call it MyClass. Is there an easy or "best practice" method for me to basically do something like MyClass * instance = [map NSDictionary properties to MyClass ];? I have a feeling I need to do something with NSCoding or NSKeyedUnarchiver, but rather than stumble through it on my own, I figure someone out there might be able to point me in the right direction.
The -setValuesForKeysWithDictionary: method, along with -dictionaryWithValuesForKeys:, is what you want to use.
Example:
// In your custom class
+ (id)customClassWithProperties:(NSDictionary *)properties {
return [[[self alloc] initWithProperties:properties] autorelease];
}
- (id)initWithProperties:(NSDictionary *)properties {
if (self = [self init]) {
[self setValuesForKeysWithDictionary:properties];
}
return self;
}
// ...and to easily derive the dictionary
NSDictionary *properties = [anObject dictionaryWithValuesForKeys:[anObject allKeys]];
There is no allKeys on NSObject. You'll need to create an extra category on NSObject like below:
NSObject+PropertyArray.h
#interface NSObject (PropertyArray)
- (NSArray *) allKeys;
#end
NSObject+PropertyArray.m
#import <objc/runtime.h>
#implementation NSObject (PropertyArray)
- (NSArray *) allKeys {
Class clazz = [self class];
u_int count;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++) {
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
return [NSArray arrayWithArray:propertyArray];
}
#end
Example:
#import "NSObject+PropertyArray.h"
...
MyObject *obj = [[MyObject alloc] init];
obj.a = #"Hello A"; //setting some values to attributes
obj.b = #"Hello B";
//dictionaryWithValuesForKeys requires keys in NSArray. You can now
//construct such NSArray using `allKeys` from NSObject(PropertyArray) category
NSDictionary *objDict = [obj dictionaryWithValuesForKeys:[obj allKeys]];
//Resurrect MyObject from NSDictionary using setValuesForKeysWithDictionary
MyObject *objResur = [[MyObject alloc] init];
[objResur setValuesForKeysWithDictionary:objDict];
Assuming that your class conforms to the Key-Value Coding protocol, you could use the following: (defined as a category on NSDictionary for convenience):
// myNSDictionaryCategory.h:
#interface NSDictionary (myCategory)
- (void)mapPropertiesToObject:(id)instance
#end
// myNSDictionaryCategory.m:
- (void)mapPropertiesToObject:(id)instance
{
for (NSString * propertyKey in [self allKeys])
{
[instance setValue:[self objectForKey:propertyKey]
forKey:propertyKey];
}
}
And here's how you would use it:
#import "myNSDictionaryCategory.h"
//...
[someDictionary mapPropertiesToObject:someObject];
If your doing this sort of thing chances are your dealing with JSON and you should probably have a look at Mantle
https://github.com/Mantle/Mantle
You will then get a convenient method dictionaryValue
[anObject dictionaryValue];
Just add category for NSObject for getting dictionaryRepresentation from your custom objects (in my case using in JSON serialization only):
// NSObject+JSONSerialize.h
#import <Foundation/Foundation.h>
#interface NSObject(JSONSerialize)
- (NSDictionary *)dictionaryRepresentation;
#end
// NSObject+JSONSerialize.m
#import "NSObject+JSONSerialize.h"
#import <objc/runtime.h>
#implementation NSObject(JSONSerialize)
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary {
return [[self alloc] initWithDictionary:aDictionary];
}
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary {
aDictionary = [aDictionary clean];
self.isReady = NO;
for (NSString* propName in [self allPropertyNames]) {
[self setValue:aDictionary[propName] forKey:propName];
}
//You can add there some custom properties with wrong names like "id"
//[self setValue:aDictionary[#"id"] forKeyPath:#"objectID"];
self.isReady = YES;
return self;
}
- (NSDictionary *)dictionaryRepresentation {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self allPropertyNames];
id object;
for (NSString *key in propertyNames) {
object = [self valueForKey:key];
if (object) {
[result setObject:object forKey:key];
}
}
return result;
}
- (NSArray *)allPropertyNames {
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
//You can add there some custom properties with wrong names like "id"
//[rv addObject:#"objectID"];
//Example use inside initWithDictionary:
//[self setValue:aDictionary[#"id"] forKeyPath:#"objectID"];
free(properties);
return rv;
}
#end
Also, you can see that my solution will not work with custom objects with nested objects or arrays. For Arrays - just change the lines of code in dictionaryRepresentation method:
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
#autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
for (id item in (NSArray *)object) {
[array addObject:[item dictionaryRepresentation]];
}
[result setObject:array forKey:key];
}
} else {
[result setObject:object forKey:key];
}
}