11

I want to create a subclass of NSMutableArray and need to override the -initWithObjects: method.

But How to call [super xxx];?

- (id) initWithObjects:(id)firstObj, ... {
    [super initWithObjects:firstObj]; // Error: Missing sentinel in method dispatch
    // Error: The result of a delegate init call must be immediately returned or assigned to "self"

}

Thanks.

vikingosegundo
  • 51,126
  • 14
  • 131
  • 172
Ben Lu
  • 2,875
  • 4
  • 28
  • 51

5 Answers5

47

Then "missing sentinel" message refers to the missing nil termination. In fact, according to font-of-all-knowledge-Wikipedia:

The name of the nil that terminates a variable length list of parameters in Objective-C

also: Sentinel node, an object to represent the end of a data structure also: Sentinel value, a value used to terminate a loop also: In network protocols such as Bisync, sentinel values indicate where frames start and end

Mark
  • 626
  • 4
  • 5
10

You can't. As discussed in the documentation for NSArray:

You might want to implement an initializer for your subclass that is suited to the backing store that the subclass is managing. The NSArray class does not have a designated initializer, so your initializer need only invoke the init method of super. The NSArray class adopts the NSCopying, NSMutableCopying, and NSCoding protocols; if you want instances of your own custom subclass created from copying or coding, override the methods in these protocols.

So you can assign self = [super init]; and add the objects from your initialiser to the resulting object. Indeed, because of the way that NSArray is implemented, calling any -initWith… method is likely to return an instance of a different NSArray subclass.

Notice that the documentation also discusses alternatives to subclassing NSArray that may be easier, more reliable or better in some other way.

2

Subclassing NSArray/NSMutableArray doesn't work like subclassing most classes. NSArray is a class cluster, please see subclassing notes from the NSArray documentation.

Now, for your specific question, subclassing va_list methods is a bit tricky, there are a number of ways to handle this. The 'safest' would be to process your va_list into an NSArray and pass that into another method that dealt with whatever you wanted. The other, slightly less portable, slightly hackier way is to create a new va_list list on the stack to pass through.

    id __unsafe_unretained * stack = (typeof(stack))calloc(numOfObjects, sizeof(id));
    //filloutStack
    [super initWithObjects:*stack, nil];
    free(stack);
Joshua Weinberg
  • 28,320
  • 2
  • 95
  • 90
2

Subclassing Apples Collection classes isn't that difficult — if you use a tiny trick (see also: cocoawithlove).

A subclass is a "is-a" relationship in object-orientated Design. But there are also "has-a" relationships, i.e. wrappers.

If you would try to create a subclass of NSArray by using a pure is-a relationship, I guess, it would be kind of hard, as you would have to do C-level memory management.

But if you add a has-a relationship — or: create a wrapper — at the same time, you can the subcalssing quite easily: Just make your custom array class have a member of a regular NSArray. Now override its method by forwarding the calls to the member object. I showed this in this post, where I just add objects, that pass a certain test.

But you will see, that I didn't implement the method you talked about correctly, but I raise a error. The reason is: that method is a variadic methods, that has a variable number of objects you can pass in — and to handle this, you have to to a bit of work. cocoawithlove has an great article about it.

For you — if using that has-a trick — it could look like

- (id) initWithObjects:(id)firstObj, ... {

    if (self = [super init]) {
        _realArray = [[NSMutableArray alloc] initWithCapacity:1];
    }

    va_list args;
    va_start(args, firstObj);
    for (id obj = firstObj; obj != nil; obj = va_arg(args, id))
    {
        [self.realArray addObject:obj];
    }
    va_end(args);
    return self;
}
Community
  • 1
  • 1
vikingosegundo
  • 51,126
  • 14
  • 131
  • 172
-1

Try

self = [super initWithObjects:firstObj,nil];
Manlio
  • 10,328
  • 9
  • 44
  • 76
  • you solved the delegate problem but there is still a warning of missing sentinel... – Ben Lu Jan 02 '12 at 17:16
  • I tried `self = [super initWithObjects:firstObj, nil];` and warning disappeared, but will the object inside array missing when this subclass is called? – Ben Lu Jan 02 '12 at 17:17
  • Yes, sorry you must add nil at the end. Sorry but I don't understand your question... – Manlio Jan 02 '12 at 17:22
  • 1
    @BenLu: See jlehr's answer and my comment on it. That will work, but it will only (try to) initialize the array with the first object, not any others. At best, you will end up with an array containing no more than one object, regardless of how many objects were passed. – Peter Hosey Jan 02 '12 at 19:57
  • See my answer for a va_list-aware implementation. – vikingosegundo Jan 02 '12 at 21:09