4

I added a category to NSArray with a helper method for sorting. My unit tests all pass, but when running the app in the simulator it blows up. Could this be because of the NSMutableArray / NSCFArray class cluster stuff?

Here is the error: 'NSInvalidArgumentException', reason: '*** -[NSCFArray sortBySequenceAsc]: unrecognized selector sent to instance 0x489c1f0'

Anyway, what is the proper way to add a category to NSArray and NSMutableArray?

@interface NSArray (Util) 
- (NSArray *)sortBySequenceAsc;
@end 

@implementation NSArray (Util)
- (NSArray *)sortBySequenceAsc {
    //my custom sort code here
}
@end
Community
  • 1
  • 1
Tony Eichelberger
  • 7,044
  • 7
  • 37
  • 46

5 Answers5

10

I've used categories on NSArray many times with no problems, so I'm guessing your problem lies elsewhere (since your category looks correct).

Since you're getting an "unrecognized selector" error, that means the runtime doesn't know about your category, which means it wasn't linked into your binary. I would check and make sure your category's .m file is included in the appropriate target, clean, and build again.

EDIT: for example, this blog post shows how to create a category for NSArray for creating a shuffled copy of the original array.

EDIT #2: Apple's documentation on categories uses the specific example of extending the functionality of NSArray, so I find it difficult to believe that the "recommended" approach would be to wrap the array in a helper object. Citation (search the page for the five "NSArray" references)

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • I tried adding a category once and got the same problem as the OP. I read on some iPhone dev site that creating a new object containing an `NSArray` is recommended by Apple in this case; perhaps you're just more careful with your code. ;) – Chris Long Dec 28 '09 at 17:26
  • @Chris - Do you have a link to support the wrapper recommendation? I've never heard of that before and I'd be interested to learn more. :) – Dave DeLong Dec 28 '09 at 17:32
  • Sorry, it was a long time ago. All I know is that it worked for my app. – Chris Long Dec 28 '09 at 17:53
  • @Dave, your edits prompted me to look over my app's code. I realize that my issue was subclassing, not categories. Sorry about that! If you ever have a problem with subclassing, though, there you go. :) – Chris Long Dec 28 '09 at 18:20
  • 1
    @Chris aha, that makes sense. Yes, I totally agree. Don't ever subclass `NSArray`. That's asking for a world of hurt. =) – Dave DeLong Dec 28 '09 at 18:21
  • 1
    Thanks for the update. I switched to use a class method on a SortUtil class for now. If I go back and figure out the other issue, I will post about what I was doing wrong. – Tony Eichelberger Dec 28 '09 at 20:52
2

The recommended way is to create a new object containing an NSArray. Underneath NSArray there's a lot of gunk that Apple doesn't tell us about; when you add new methods, you're unable to access that stuff, leading to errors.

Basically, do this:

@interface MySortingArray : NSObject {
     NSArray *theArray
}

- (int)count;
- (NSArray *)sortBySequenceAsc;

@end

@implementation MySortingArray

- (int)count {
    return [theArray count];
}

- (NSArray *)sortBySequenceAsc {
   // your code
}

@end
Chris Long
  • 3,007
  • 4
  • 30
  • 37
  • Alright. I will stay away from adding methods directly to NSArray. – Tony Eichelberger Dec 28 '09 at 15:47
  • 1
    How on earth would creating a category on NSArray make the object "unable to access" this "lot of gunk"? – Chuck Dec 28 '09 at 17:12
  • Well, I admit I did simplify it a lot. I meant that the *OP* would have difficulty knowing the intricacies of `NSCFArray` and co., since Apple gives you no documentation of it. It would really be more trouble than it's worth. – Chris Long Dec 28 '09 at 17:21
  • While this approach will work, I do not believe this is the *correct* approach for extending `NSArray` functionality. For more information, see my answer. – Dave DeLong Dec 28 '09 at 17:40
  • One problem with this approach is that you can't pass a `MySortingArray` object to any method that requires an `NSArray` as a parameter. – Dave DeLong Dec 28 '09 at 17:40
  • If that's necessary, you can always write `[myObject arrayMethod: sortingArray.theArray];` to gain access to the array within (assuming you made a `@property`, of course). – Chris Long Dec 28 '09 at 17:48
2

I want to chime in with something, even though it's late to the party.

Sometimes I'm lazy when I make methods which return an NSArray. I actually return the NSMutableArray I build up in the method. I figure, "What could possibly go wrong?". I use it as a pattern to return an immutable version of the mutable instance I'm working with.

Except this is exactly an instance of where things goes wrong. Apparently Apple also, lazily, return an NSMutableArray from an interface which states it's an NSArray. And if you add a category on NSArray, it won't get added to NSMutableArray. Because an NSMutableArray is an NSArray, but an NSArray isn't an NSMutableArray.

Hans Sjunnesson
  • 21,745
  • 17
  • 54
  • 63
  • returning an NSMutableArray from an interface which states it's an NSArray isn't lazy, it's correct. If you need an immutable array, use the copy method. http://stackoverflow.com/a/1769017/190135 – AlexChaffee May 19 '13 at 23:17
1

it is a bad idea to put a sorting category on an NSArray... which is supposed to be un-modifiable... that type of functionality is the purview of a NSMutableArray unless you plan to extract all the elements and return an entirely different NSArray instance, that is confusing behavior.

ericm666
  • 11
  • 1
  • it is ok to add sorting messages to an array — if they return arrays, as shown in the example. But the naming is not good indeed. it should be `sortedArrayBySequenceAsc` or similar. at the tme this question was asked, this was a elegant way to define context-aware sorting. today I'd prefer comparator blocks. – vikingosegundo Jan 22 '14 at 20:53
0

Indeed, it's because of the class cluster. You created a category on NSArray, but you're calling the method on a private class __NSCFArray which doesn't have your method. I found a workaround: create explicitly a new NSArray with your existing array. The new array's class (the object's isa pointer) will point to the correct class where the category is.

So, instead of doing this:

NSArray *array = [someObject someMethod];
// "array" may be a private class with the same methods as "NSArray", but not yours.

[array yourMethod];

Do this:

NSArray *array = [NSArray arrayWithArray: [someObject someMethod]];
// "array" is now explicitly a "NSArray"

[array yourMethod];

It works, but I hope that remains so. It's pretty unpredictable. That's why I created my own array implementation using open source code over here. I left out methods that I don't need and I added new ones that I may need (like shuffle). I'm testing it thoroughly now.

Constantino Tsarouhas
  • 6,846
  • 6
  • 43
  • 54