I knows some ways to avoid this, for example
if (index >= [_data count] || index < 0) return nil;
return [_data objectAtIndex:index];
But should i aways do this? or are there any other solutions about this topic?
First, I'd like to echo #rmaddy's comment, which is spot on:
There is no general solution. Every case is different.
That said, there are other techniques you can use:
firstObject and lastObject
These methods will return the object, or nil if there is none. These methods will never throw an exception.
Fast Enumeration
You can use fast enumeration and never need to check the indices:
NSArray *myStrings = #[#"one", #"two"];
for (NSString *thisString in myStrings) {
NSLog(#"A string: %#", thisString);
}
Safe Category
You can add a category on NSArray if you find yourself doing this frequently:
- (id)safeObjectAtIndex:(NSUInteger)index {
if (index >= [self count] || index < 0) return nil;
return [self objectAtIndex:index];
}
See Customizing Existing Classes if you aren't familiar with categories.
One downside of this is it may be harder to find errors in your code.
In apple library NSArray objectAtIndex method:
objectAtIndex:
Returns the object located at the specified index.
- (id)objectAtIndex:(NSUInteger)index
Parameters
index
An index within the bounds of the array.
Return Value
The object located at index.
You can use "NSUInteger index ;" so "if (index < 0)" could be omitted.
In my opinion, if you need to provide some interface to others, you may need to add these code to avoid "beyond bounds".
But if your code just works for yourself, you need not to do the stuff because most of time what you needs is an object in the array but not the nil. If the index beyonds bound, there must be some logic error which you need to fix. Let exception goes and find your bug.
U can swizzle the objectAtIndexmethod,before call objectAtIndex, invoke method like 'custom_objectAtIndex' to check if out of bounds .
+ (void)load{
Method method1 = class_getInstanceMethod(objc_getClass("__NSArrayI"),#selector(objectAtIndex:));
Method method2 = class_getInstanceMethod(objc_getClass("__NSArrayI"),#selector(custom_objectAtIndex:));
method_exchangeImplementations(method1, method2);
}
- (id)custom_objectAtIndex:(NSUInteger)index{
NSLog(#"~~~~~~~:%ld",count);
if (index >= self.count) {
return #"out of bounds";
} else {
return [self custom_objectAtIndex:index];
}
}
Related
I want to check if a JSON object is an NSString and if it isn't, assign it a default string. My ultimate goal is to prevent crashing and assign the properties a proper value no matter what. This is an example of a data model I am using where dict is the JSON dictionary the API returns.
Data *data = [[self alloc] init];
data.name = [NSString validateString:dict[#"name"] defaultString:#""];
data.status = [NSString validateString:dict[#"status"] defaultString:#"OPEN"];
Here is the category method validateString I am using.
+ (NSString *)validateString:(NSString *)aString defaultString:(NSString *)defaultString {
if ([aString isKindOfClass:[NSString class]]) {
return aString;
}
return defaultString;
}
It makes no sense, and is very bad practice, to cast (NSString *)aString and then ask if this is in fact an NSString.
Also, what if it is nil?
All you know when you fetch from a dictionary is that you get an id. Do not assume more than that.
I would suggest writing very plainly: say what you mean, and mean what you say. That is the best practice in Objective-C. Otherwise, dynamic typing and "nil trickery" can lead you into subtle errors. You might not have any trouble in this particular case, but bad habits are bad habits, and it is best not to let them form in the first place. I'd rewrite like this:
+ (NSString *) checkType:(nullable id)obj defaultString:(NSString *)def {
if (obj == nil || ![obj isKindOfClass:[NSString class]]) {
return def;
}
return obj;
}
Like mentioned in other comments: if you want to prevent crashes, you also need to check if it's nil, specially if there is a chance to port your code to Swift in the future.
Just to clarify my last sentence, the line below works in Objective-C even if aString is nil:
if ([aString isKindOfClass:[NSString class]]) {
That's because, in the way Objective-C was made, calling a function on a nil object returns nil, so the if will be considered false, and the function will return defaultString. Yeah... that's certainly a bad idea when they created Objetive-C, since this leads to lots of errors. More details about that behaviour below:
https://stackoverflow.com/a/2696909
Anyway, it's also a good practice to only cast an object after checking its type, so I would recommend adapting your function to this:
+ (NSString *)validateString:(id)obj defaultString:(NSString *)defaultString {
if (obj != nil && [obj isKindOfClass:[NSString class]]) {
return (NSString*)obj;
}
return defaultString;
}
Every object that implements NSObject* has isKindOfClass: (and NSDictionary* only stores objects that implement NSObject*), so we don't need to check if the object responds to it. Also, even if we wanted, respondsToSelector: is also an NSObject* function.
Still, the method that you are using still works. The revised function above is just adapted to better practices and to avoid problems in case you ever need to port this code to Swift (or any other language) in the future.
EDIT: Updated code based in #matt's suggestion.
I'm not too sure if I should of made another question for this or expanded on the last question, please correct me if I wasn't meant to.
I've currently got this code working:
if ([ myArray containsObject:#"Object1" ] && [ myArray containsObject:#"Object 2"]) {
return YES;
}
else {
return NO;
}
What I'm needing to do is modify this so it iterates through an array and accesses an Objects property value. For example:
if (myArray contains obj.ID 1 & 2) {
return YES
}
else{
return NO;
}
Any suggestions on what I should look at? I've been at this for a couple of hours and tried different permutations with no luck.
Thank you!
You can use -indexOfObjectPassingTest: to check if an object with a particular attribute value is in your array. The method returns either the object's index if it is found or NSNotFound if not.
Thus, assuming your objects are e.g. NSDictionaries and they have NSNumbers as IDs, you could do something like this:
if([myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==1;
}]!=NSNotFound && [myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==2;
}]!=NSNotFound)
{
//Array contains objects
}
If you only want the first object, you can use -indexOfObjectPassingTest: as mpramat says. If you want all the objects in the array that match your criteria, use indexesOfObjectsPassingTest:.
It takes a block as a parameter. The block evaluates each object and returns YES or know to let the method know if that object should be part of the set of objects that pass the test.
I'm using sortedArrayUsingSelector to sort an array I have.
Here's me calling it:
NSArray *sortedArray;
SEL sel = #selector(intSortWithNum1:withNum2:withContext:);
sortedArray = [_myObjs sortedArrayUsingSelector:sel];
And here's the definition:
- (NSInteger) intSortWithNum1:(id)num1 withNum2:(id)num2 withContext:(void *)context {
CLLocationCoordinate2D c1 = CLLocationCoordinate2DMake([((myObj *)num1) getLat], [((myObj *)num1) getLong]);
CLLocationCoordinate2D c2 = CLLocationCoordinate2DMake([((myObj *)num2) getLat], [((myObj *)num2) getLong]);
NSUInteger v1 = [self distanceFromCurrentLocation:(c1)];
NSUInteger v2 = [self distanceFromCurrentLocation:(c2)];
if (v1 < v2)
return NSOrderedAscending;
else if (v1 > v2)
return NSOrderedDescending;
else
return NSOrderedSame;
}
I'm getting the thread1 SIGABRT error in my main when I run my app.
Any ideas? Thanks in advance.
Note: I have already tried this:
NSArray *sortedArray = [[NSArray alloc] init];
It didn't fix anything.
The selector should be implemented by the objects being compared, and should take just one argument which is another object of the same type.
For example, in NSArray, there's an example where strings are being compared using caseInsensitiveCompare. That's because NSString implements caseInsensitiveCompare.
If you think of it... how can sortedArrayUsingSelector know what to pass as parameters to the function in your example??
EDIT:
That means that the function you use as the 'sorting selector' must be a function defined by the objects in your array. Suppose if your array contains Persons, your array must be sorted like this:
sortedArray = [_myObjs sortedArrayUsingSelector:#selector(comparePerson:)];
The comparePerson message will be sent to the objects in your array (Persons), so in your Person's class you must have a function called comparePerson:
- (NSComparisonResult)comparePerson:(Person *)person
{
if (self.age == person.age)
return NSOrderedSame;
}
In this example, comparePerson compares itself (self) with the argument (person), and considers two persons equal if they have the same age. As you can see, this way of comparing and sorting can be very powerful, provided that you code the proper logic.
In my application, I want to compare 2 core data instances of the entity "Workout". I want to check if the 2 objects have identical attribute values for all of their attributes. Essentially if the two objects are the same minus the relationship, whosWorkout. Is there any way to do this without manually checking every single attribute? I know I could do:
if(object1.intAttr == object2.intAttr){
NSLog(#"This attribute is the same");
}
else{
return;
}
repeat with different attributes...
Is there any core data method to make this a bit less tedious?
First I would create an isEqual method in the Workout subclass like this...
-(BOOL)isEqualToWorkout:(Workout*)otherWorkout
{
return [self.attribute1 isEqual:otherWorkout.attribute1]
&& [self.attribute2 isEqual:otherWorkout.attribute2]
&& [self.attribute3 isEqual:otherWorkout.attribute3]
&& [self.attribute4 isEqual:otherWorkout.attribute4]
...;
}
Then whenever you want to compare to Workout objects just use...
BOOL equal = [workout1 isEqualToWorkout:workout2];
You can iterate through the attributes by name.
for (NSString *attribute in object.entity.attributesByName) {
if ([[object valueForKey:attribute] intValue] !=
[[object2 valueForKey:attribute] intValue]) {
return NO;
}
}
return YES;
This assumes all integer attributes. You could do a switch statement to check for the class with the class method and deal with different data types as well.
If you need to compare whether one object represents a greater or lesser value than another object, you can’t use the standard C comparison operators > and <. Instead, the basic Foundation types, like NSNumber, NSString and NSDate, provide a compare: method:
if ([someDate compare:anotherDate] == NSOrderedAscending) {
// someDate is earlier than anotherDate
}
I ended up doing the following:
-(BOOL)areEqual:(Workout *)firstWorkout secondWorkout:(Workout *)secondWorkout{
NSArray *allAttributeKeys = [[[firstWorkout entity] attributesByName] allKeys];
if([[firstWorkout entity] isEqual:[secondWorkout entity]]
&& [[firstWorkout committedValuesForKeys:allAttributeKeys] isEqual:[secondWorkout committedValuesForKeys:allAttributeKeys]]) {
return YES;
}
else{
return NO;
}
}
From an array, I want to make a new mutable array holding all the items that meet a certain criteria. That's not the problem. The problem is checking if the array is empty.
if (!theDates]) {/*do something*/}
it comes back positive regardless, because the mutable array was created. But
if (![theDates objectAtIndex:0])
{
/* nothing got added to the array, so account for that */
}
else
{
/* do something with the array */
}
It crashes.
This crashes because although you have allocated your array, it's still empty. The line [theDates objectAtIndex:0] is trying to access the first object of an array, and due your array has none yet, it will crash.
To check the integrity of your array, just do this:
if (theDates.count > 0)
{
//Do something
id object = theDates[0]; //this for sure won't crash
}
Use [theDates count] > 0. You're accessing a possibly non-existent element, so it will crash when it's empty.
Or you could use the lastObject method which will return nil if the array is empty
if (![theDates lastObject]) { do something }
if( (theDates!=nil) && (theDates.count > 0))
{
//We have and existing array and something in there..
}
if (theDates != nil){
if (theDates.count > 0){
'do something'
}
}
This should check null array then check empty array which prevents nullpointerexception arises
This will work fine..
if (!URarray || !URarray.count){
// write code here
}