I'm writing a category on NSArray
to add JavaScript array methods to NSArray
. In JavaScript, the splice()
method both adds/removes items to/from an array. But the number of objects added to the array may vary. So I used va_list
to allow for a more flexible input of object values.
As it stands the method requires a count input value. How could I rewrite this without one?
Interface
@interface NSArray (JavaScriptArray)
- (NSArray *)splice:(NSUInteger)index remove:(NSUInteger)remove count:(NSUInteger)count arguments:(id)firstObject,...;
@end
Implementation
@implementation NSArray (JavaScriptArray)
- (NSArray *)splice:(NSUInteger)index remove:(NSUInteger)remove count:(NSUInteger)count arguments:(id)firstObject,...
{
NSMutableArray *mSplice = [NSMutableArray arrayWithArray:self];
if (remove != 0) {
NSUInteger removeIndex = index;
for (NSUInteger i = 0; i < remove; i++) {
[mSplice removeObjectAtIndex:removeIndex];
removeIndex = removeIndex + 1;
}
}
if (count != 0) {
NSUInteger addIndex = index;
id eachObject;
va_list argumentList;
if (firstObject) {
[mSplice insertObject:firstObject atIndex:addIndex];
addIndex = addIndex + 1;
va_start(argumentList, firstObject);
eachObject = va_arg(argumentList, id);
for (NSUInteger i = 0; i < count; i++) {
[mSplice insertObject:eachObject atIndex:addIndex];
addIndex = addIndex + 1;
}
va_end(argumentList);
}
}
return [NSArray arrayWithArray:mSplice];
}
@end
Calling the Method
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *fruit = @[@"Banana", @"Orange", @"Apple", @"Mango"];
NSArray *fruitSplice = [fruit splice:2 remove:0 count:4 arguments:@"Lemon", @"Kiwi", @"Kiwi", @"Kiwi"];
NSLog(@"fruitSplice %@", fruitSplice);
}
@end
Debugger Window
fruitSplice (
Banana,
Orange,
Lemon,
Kiwi,
Kiwi,
Kiwi,
Kiwi,
Apple,
Mango
)
Removing the count
argument is no problem, because actually your "add" loop appears to be incorrect. After you've gotten the second item from the va_list
with eachObject = va_arg(argumentList, id);
, you never get another object. The only reason your example works is that all the later items are the same: @"Kiwi"
. If your test call was
NSArray *fruitSplice = [fruit splice:2
remove:0
count:4
arguments:@"Lemon", @"Albatross", @"Kiwi", @"Kiwi"];
you'd see
fruitSplice (
Banana,
Orange,
Lemon,
Albatross,
Albatross,
Albatross,
Albatross,
Apple,
Mango
)
as output.
You need to redo your unpacking of the va_list
, and you can keep your own counter while you're iterating it, but the catch is that there has to be a sentinel value: an value that can't possibly appear as a valid list value, that indicates you've come to the end. For object-type variadic arguments you would generally use nil
as the sentinel.
When you're using a va_list
you must have either a sentinel or a count. There's no other way for you to know when to stop popping arguments.
Your signature can become*:
- (NSArray *)KRSpliceAt:(NSUInteger)index
removingCount:(NSUInteger)remove
addingObjects:(id)firstObject, ... NS_REQUIRES_NIL_TERMINATION;
The NS_REQUIRES_NIL_TERMINATION
is strictly optional, but will make the compiler notify you if the method is called without a sentinel.
Then your adding loop changes:
// Insertion index starts at given splice point
NSUInteger addIndex = index;
// Initialize the va_list
va_list objs;
va_start(objs, firstObj);
// Start at the beginning
id nextObj = firstObj;
// Test for sentinel nil
while( nextObj ){
[mSplice insertObject:nextObj atIndex:addIndex];
// Update insertion point
addIndex++;
// Get next argument
nextObj = va_arg(objs, id);
}
// Signal completion of list
va_end(objs);
*Methods you add to classes you don't own should always be prefixed. It's a bit annoying, but it's good practice to prevent a catastrophic clash if you should happen to pick the same name as another method.