Xcode 13 - Count not working on NSMutableArray anymore - ios

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.

Related

How can I implement my logic properly to populate my UITableView

Sorry guys, this problem I am running into is pretty trivial. I just can't wrap my head around it so hope someone can help me. Your help is really appreciated. I am getting JSON data through NSURLConnectDelegate with a web API. I get something like this back:
(
{
id = 340
name = Vicent },
{
id = 339
name = Johny },
{
id = 338
name = Eric }
)
and I save it in a NSMutableArray as a global variable. Now, I have a NSSet of "ids". For example:
{
340, 339
}
In the numberOfRowsInSection, I return the set's count. I am trying to load only the ids in the NSSet from the array with the data saved from the webAPI, so I do something like this in cellForRowIndexPath:
for (NSNumber *num in [set allObjects]) {
NSString *newString = [[savedArray objectAtIndex:indexPath.row]
NSString *new = [num stringValue];
if ([new isEqual:newString]) {
}}
How can I just populate the ids I want?
The JSON makes it look like you have an array of dictionaries, which is a reasonable data structure to use as the data source for a table view.
It sounds like you're trying to filter your array to only include the items that are in your set. Is that right?
If so, you could write code that would create a new array containing the subset of your array elements who's ID is also in your set. There are at least a half-dozen ways to do that. One fairly simple approach would be to use the NSArray method indexesOfObjectsPassingTest. You'd pass that method a block of code that would check each array element to see if it's id object was in your set.
That would give you an NSIndexSet with the indexes of the items in your array who's ID are in your set. Then you could use the NSArray method objectsAtIndexes to get an array of only the objects that are also in the set. Something like this (Assuming that your array of dictionaries is called savedArray and your set is called allObjects:
//get the indexes of items in the array savedArray who's id appears in the set allObjects
NSIndexSet *indexes = [savedArray indexesOfObjectsPassingTest:
^(NSDictionary *obj,
NSUInteger idx,
BOOL *stop)
{
return [allObjects member: obj[#"id"]] != nil;
}
];
//Now build an (immutable) array of just the objects who's ID are in the set
NSArray *subArray = [savedArray objectsAtIndexes: indexes];
The array subArray created above is immutable. If you need a mutable array you would need to make a mutable copy, which is a one-line change.
Disclaimer: I still struggle a little with block syntax, so the above might not be exactly correct, but it gives you the general idea.

How to compare every pair of elements inside a NSArray?

I have an NSArray filled with only NSStrings
I understand that to iterate over a NSArray of n elements, all I have to do is use for (NSString *element in arrayOfElements). However, I was wondering if there is specific function that will perform a comparison between every string element in the array with each other. For example, if I have the array:
[#"apple", #"banana", #"peach", #"kiwi"],
how would I do the comparison so apple is compared to banana, peach and then kiwi; and then banana is against peach and wiki, and finally peach is against kiwi?
Try using nested for loops, ex:
for (int i = 0 ; i < array.count ; i ++) {
for (int j = i + 1 ; j < array.count ; j ++) {
// compare array[i] to array [j]
}
}
Edit: And although wottle's suggestion would work, I'd recommend mine in this case, since it won't waste iterations going over the same comparisons multiple times. What I've done in this algorithm by setting j = i + 1 is compare each element in the array only to the ones after it.
Given "the array will not have any duplicates, and every NSString will be unique" this sounds like a great case for using NSSet classes instead of NSArray. NSMutableSet provides:
minusSet:
Removes each object in another given set from the receiving
set, if present.
and
intersectSet:
Removes from the receiving set each object that isn’t a
member of another given set.
I'm not sure which operation you're looking for but it sounds like one of those should cover your exact use case.
What you're trying to do is a bit beyond what custom comparators were meant to do. Typically when you have a list and you want to run a custom comparator, you're doing it to sort the list. You seem to want to do some specific action when certain items in the list compare to others, and for that, I think a loop within a loop is your best bet. It won't be very good performance, so hopefully you are not expecting a large array:
-(void) compareArrayToSelf
{
NSArray *array=#[#"apple", #"bananna", #"peach", #"kiwi"];
for( NSString *value1 in array)
{
for( NSString *value2 in array)
{
if( ![value1 isEqualToString:value2] && [self compareArrayValue:value1 toOtherValue:value2])
{
//Do something with either value1 or value2
}
}
}
}

sortedArrayUsingSelector causing SIGABRT

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.

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). ...

NSMutableArray or NSMutableDictionary : which is best for this scenario?

I need to scroll through several thousands of words to categorize them... to determine which words have the same pattern. (this part works)
For example, a four letter word that has two m's in 2nd & 4th position represent a pattern ("-m-m"). Once I have gone through all the words, I will know how many words there are for any given pattern. I am scrolling through now, but the problem I have is 'remembering' how many words I have in any given pattern.
I was thinking of using NSMutableDictionary and have the key be the pattern ('-m-m-') and the object represent the count of that pattern. This means every time I come across a pattern, I look up that pattern in the dictionary, get the key, increment the key, and put it back in the dictionary.
I need help with both the decision and syntax for performing this task.
Thank You
The answer to your question was this part of your (given) question "I will know how many words there are for any given pattern.". I would use an array of dictionary. You use the dictionary to store key value pair: a known pattern and the count. And you use the array to store those KVP records. So the next time you detect a pattern, search for the array for that record (dictionary), if found, increment the count. If not, create new record and set the count to 1.
Added sample code:
#define kPattern #"Pattern"
#define kPatternCount #"PatternCount"
-(NSMutableDictionary *)createANewDictionaryRecord:(NSString *) newPattern
{
int count = 1;
NSMutableDictionary *myDictionary = [NSMutableDictionary dictionaryWithObjectsAndKeys:
newPattern, kPattern,
[NSString stringWithFormat:#"%i",count], kPatternCount,
nil];
return myDictionary;
}
-(void)addANewPatternToArray:(NSMutableDictionary *)newDictionary
{
// NSMutableArray *myArrayOfDictionary = [[NSMutableArray alloc]init]; // you need to define it somewhere else and use property etc.
[self.myArrayOfDictionary addObject:newDictionary]; //or [self.myArrayOfDictionary addObject:newDictionary]; if you follow the recommendation above.
}
-(BOOL)existingPatternLookup:(NSString *)pattern
{
for (NSMutableDictionary *obj in self.myArrayOfDictionary)
{
if ([[obj objectForKey:kPattern] isEqual:pattern])
{
int count = [[obj objectForKey:kPatternCount] intValue] + 1;
[obj setValue:[NSString stringWithFormat:#"%i",count] forKey:kPatternCount];
return YES;
}
}
[self.myArrayOfDictionary addObject:[self createANewDictionaryRecord:pattern]];
return NO;
}
-(void)testData
{
NSMutableDictionary *newDict = [self createANewDictionaryRecord:#"mmm"];
[self addANewPatternToArray:newDict];
}
-(void) printArray
{
for (NSMutableDictionary * obj in self.myArrayOfDictionary)
{
NSLog(#"mydictionary: %#", obj);
}
}
- (IBAction)buttonPressed:(id)sender
{
if ([self existingPatternLookup:#"abc"])
{
[self printArray];
} else
{
[self printArray];
}
}
Not being an objective C expert but solving this problem in java before, I would say a dictionary(I used a map when doing it in java) is the best way. Check if the key(pattern) already exist if so increment that count else put a new one in the dictionary.
EDIT
If you want to not just get the count of a pattern, but in fact tell which words fall under that pattern, I would use a dictionary of strings to mutable arrays. In the arrays you store the words and the key to the array is the pattern(as a string), similar code as above but instead of just incrementing the count, you have to add the new word to the array.
The only difference in NSDictionary and NSMutableDictionary is that one can have objects added to it. I think your implementation is good, but English is a complex language. It would be more efficient to parse out the string with regex than to set a key for it.
Why don't you use NSCountedSet instead:
NSCountedSet Class Reference
..which is available in iOS 2.0 and later?
Each distinct object inserted into an NSCountedSet object has a counter associated with it. NSCountedSetkeeps track of the number of times objects are inserted [...] Thus, there is only one instance of an object in an NSSet object even if the object has been added to the set multiple times. The count method defined by the superclass NSSet has special significance; it returns the number of distinct objects, not the total number of times objects are represented in the set.
Then use:
- (NSUInteger)countForObject:(id)anObject
Use a dictionary of NSMutableArrays, and check for the existence of each search key as you recommended. If the key doesn't exist, add an NSMutableSet or NSMutableArray (depending on your needs) for the searched key type "-m-m" for example, and then add to the set or array for that key.

Resources