sortedArrayUsingSelector causing SIGABRT - ios

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.

Related

Xcode 13 - Count not working on NSMutableArray anymore

I have something like this in my code which worked fine for many years
NSMutableArray* DeviceList = [NSMutableArray alloc] init]; // Multi-dimensional (n x m) mutable Array filled throughout the code before arriving here:
if ([[DeviceList objectAtIndex:i] count] == 9))
{
[DeviceList removeObjectAtIndex:i];
}
I haven't touched the project for a while and now upgraded to Xcode 13 and get the following error:
Multiple methods named 'count' found with mismatched result, parameter type or attributes
The error list on the left pane shows 12 options where count has been declared.
So what has changed that the simple
someNSUInteger = [[someArray] count];
creates an error and what would be a good way to fix it in 100+ instances without casting every instance with (NSMutableArray*)?
Thanks a lot!
objectAtIndex returns "id" (objects of any type). Before it used to be permissive, and you were allowed to call any method on such (such as "count").
A better way to write this code would be to declare an intermediate variable:
NSArray *deviceRow = [DeviceList objectAtIndex:i];
if ([deviceRow count] == 9) {
or if you know the type if the items inside the array, let's say you have strings:
NSArray<NSString *> *deviceRow = [DeviceList objectAtIndex:i];
if ([deviceRow count] == 9) {
and even better:
NSArray<NSString *> *deviceRow = DeviceList[i];
if (deviceRow.count == 9) {
Another problem is that there's too many instances of that, and you'd rather not touch it everywhere.
One way to fix most of the 100 occurences is to define a wrapper class for DeviceList instead of using a "naked" NSMutableArray:
#interface MyDeviceList
- (nonnull instancetype)init;
- (nullable NSArray *)objectAtIndex:(NSUInteger)index;
... any other NSMutableArray methods used in the code ...
#end
and replace the array instantiation with:
MyDeviceList *DeviceList = [MyDeviceList new];
Hopefully you have much less instantiations than unsafe usages.
You could also have some helper methods in MyDeviceList like "countAtIndex" if it happens that you need it a lot.

How to sort NSSet subset in same order as full set NSArray, using NSPredicate?

My self.allArray contains all my objects. Then self.enabledSet contains a subset of these objects.
To create a sortedEnabledArray I currently do this:
NSArray* enabledArray = [self.enabledSet allObjects];
NSArray* sortedEnabledArray;
sortedEnabledArray = [enabledArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b)
{
int indexA = [self.allArray indexOfObject:a];
int indexB = [self.allArray indexOfObject:b];
if (indexA < indexB)
{
return NSOrderedAscending;
}
else if (indexA > indexB)
{
return NSOrderedDescending;
}
else
{
return NSOrderedSame;
}
}];
but I am wondering if this can't be done in a smarter/shorter way, using an NSPredicate for example. Any ideas?
EDIT:
One idea to shorten is:
int difference = indexA - indexB;
// Convert difference to NSOrderedAscending (-1), NSOrderedSame (0), or NSOrderedDescending (1).
return (difference != 0) ? (difference / abs(difference)) : 0;
It depends on number of items in your set and array, but you can do this way:
NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings)
{
return [self.enabledSet containsObject:object];
}];
NSArray* sortedEnabledArray = [self.allArray filteredArrayUsingPredicate:predicate];
My suggestion is to use a different approach. There is a companion to NSArray called NSIndexSet (and it's mutable counterpart, NSMutableIndexSet). It is an object that is specifically intended to keep track of subsets of an array that meet a given criteria.
NSArray includes methods like indexesOfObjectsPassingTest (and other variants that include additional parameters.) that let you add the indexes of some members of an array to an index set.
Once you have an index set that represents a subset of your allArray, you could use a method like objectsAtIndexes to get an array of just the selected objects.
Store in NSSet not objects, but indexes from allArray array.

How for in loop works internally - Objective C - Foundation

I found this answer:
https://stackoverflow.com/a/5163334/1364174
Which presents how for in loop is implemented.
NSFastEnumerationState __enumState = {0};
id __objects[MAX_STACKBUFF_SIZE];
NSUInteger __count;
while ((__count = [myArray countByEnumeratingWithState:&__enumState objects:__objects count:MAX_STACKBUFF_SIZE]) > 0) {
for (NSUInteger i = 0; i < __count; i++) {
id obj = __objects[i];
[obj doSomething];
}
}
The problem is that, I found it wrong.
First of all, when you have Automatic Reference Counting (ARC) turned on, you got an error
Sending '__strong id *' to parameter of type '__unsafe_unretained_id*' changes retain/release properties of pointer
But even when I turn ARC off I found out that I __object array seems to behave strangely :
This is actual Code (I assumed MAX_STACKBUFF_SIZE to be 40):
#autoreleasepool {
NSArray *myArray = #[#"a", #"b", #"c", #"d", #"e", #"f", #"g"];
int MAX_STACKBUFF_SIZE = 40;
NSFastEnumerationState __enumState = {0};
id __objects[MAX_STACKBUFF_SIZE];
NSUInteger __count;
while ((__count = [myArray countByEnumeratingWithState:&__enumState objects:__objects count:MAX_STACKBUFF_SIZE]) > 0) {
for (NSUInteger i = 0; i < __count; i++) {
id obj = __objects[i];
__enumState.itemsPtr
NSLog(#" Object from __objects ! %#", obj); // on screenshot different message
}
}
}
return 0;
I got EXC_BAD_ACESS when I try to get the contents of the __object array.
I also found out that when you try to iterate through __enumState.itemsPtr it actually works.
Could you explain me what is going on here ? Why my __objects seems to be "shrunken down". And why it doesn't contains desired object? And why is that error when ARC is turned on.
Thank you very very much in advance for your time and effort! (I provided screenshot for better understanding what causes an error)
First of all, strong pointers cannot be used in C-structures, as explained in the "Transitioning to ARC Release Notes", therefore the objects array has be be declared
as
__unsafe_unretained id __objects[MAX_STACKBUFF_SIZE];
if you compile with ARC.
Now it is not obvious (to me) from the NSFastEnumeration documentation, but it is
explained in Cocoa With Love:Implementing countByEnumeratingWithState:objects:count:
that the implementation need not fill the supplied objects array, but can just set
__enumState.itemsPtr to an existing array (e.g. some internal storage). In that case, the contents of the
__objects array is undefined, which causes the crash.
Replacing
id obj = __objects[i];
by
id obj = __enumState.itemsPtr[i];
gives the expected result, which is what you observed.
Another reference can be found in the "FastEnumerationSample" sample code:
You have two choices when implementing this method:
1) Use the stack
based array provided by stackbuf. If you do this, then you must
respect the value of 'len'.
2) Return your own array of objects. If
you do this, return the full length of the array returned until you
run out of objects, then return 0. For example, a linked-array
implementation may return each array in order until you iterate
through all arrays.
In either case, state->itemsPtr MUST be a valid
array (non-nil). ...

What is the perfect way to avoid "beyond bounds" when using NSArray

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];
}
}

"Incompatible pointer type.." when trying to sort using `sortedArrayUsingFunction`

I am trying to sort an NSMutableArray of NSMutableDictionarys basing on a price field.
NSString* priceComparator(NSMutableDictionary *obj1, NSMutableDictionary *obj2, void *context){
return #"just for test for the moment";
}
//In other function
arrayProduct = (NSMutableArray*)[arrayProduct sortedArrayUsingFunction:priceComparator context:nil];//arrayProduct is NSMutableArray containing NSDictionarys
On the statement above, I am getting the following warning which I want to fix:
Incompatible pointer types sending 'NSString*(NSMutableDictionary *__strong,NSMutableDictionary *__strong,void*)' to parameter of type 'NSInteger (*)(__strong id, __strong id, void*)'
As the error states, your priceComparator function needs to be declared as returning NSInteger, not NSString *:
NSInteger priceComparator(NSMutableDictionary *obj1, NSMutableDictionary *obj2, void *context){
if (/* obj1 should sort before obj2 */)
return NSOrderedAscending;
else if (/* obj1 should sort after obj2 */)
return NSOrderedDescending;
else
return NSOrderedSame;
}
Better yet, you could use NSSortDescriptors if the price you need to sort by is a simple numeric value that's always at a given key in these dictionaries. I think this is the syntax:
id descriptor = [NSSortDescriptor sortDescriptorWithKey:#"price" ascending:YES];
NSArray *sortedProducts = [arrayProduct sortedArrayUsingDescriptors:#[descriptor]];
Also note that all of the sortedArray... methods return a new, plain NSArray object, not a NSMutableArray. Thus the sortedProducts declaration in the sample code above. If you really do need your sorted array to still be mutable, you could use the sortUsingFunction:context: or sortUsingDescriptors: method of NSMutableArray to sort the array in-place. Note that these methods return void, so you wouldn't assign the result to any variable, it would modify your arrayProduct object in-place.

Resources