objective-ccocoansarraymonkeypatchingswizzling

Is there any way to monkey-patch or swizzle an NSArray (or other Class Cluster)?


Today I was working on a project in which I wanted to "alias" an alternative method for all instances of NSArray, and didn't think it would be too difficult with some good old-fashioned method swizzling.

I broke out JRSwizzle and…

[NSArray jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(objectAtIndex_accordingToMe:) error:nil];

To be clear, I paired this with the appropriate category on NSArray, an instance method called objectAtIndex_accordingToMe:.

However, I was just getting that same old object, at that same old index. Sigh. Ultimately, I figured out that despite not throwing any errors - I'm not going to achieve these results due to the fact that NSArray is a class cluster

I guess my question is more of an unwillingness to accept that "this" is really the end of the road trying to override NSArray methods. I mean, come on this is NSArray.. people must wanna muck around with it, no? One would think that Apple's foundation classes would be a prime target for swizzlers, everywhere!

So, is there a way to alter, alias, monkey-patch, override, or otherwise have your way with… an NSArray, etc. (without subclassing)?


Solution

  • Presumably you have a particular array for which you'd like this behavior. You can get that instance's class object, no matter what it is, and swizzle that quite easily:

    [[myArray class] jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(objectAtIndex_accordingToMe:) error:nil];
    

    There's also only a few concrete subclasses of NSArray:

    NSArray * trees = [NSArray array];
    NSArray * birds = [NSArray arrayWithObjects:@"Albatross", @"Budgerigar", @"Cardinal", nil];
    NSMutableArray * dogs = [NSMutableArray arrayWithObjects:@"Airedale", @"Beagle", @"Collie", nil];
    
    NSLog(@"%@ %@ %@", [trees class], [birds class], [dogs class]);
    

    We get __NSArrayI for the first two and __NSArrayM for the third, so potentially (this is very fragile) you could use a runtime function to grab the class object by name:

    [objc_getClass("__NSArrayI") jr_swizzleMethod:@selector(objectAtIndex:) withMethod:@selector(objectAtIndex_accordingToMe:) error:nil];