[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Issues subclassing NSMutableArray

From: David Chisnall
Subject: Re: Issues subclassing NSMutableArray
Date: Mon, 24 Feb 2020 11:30:29 +0000
User-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Thunderbird/68.5.0

On 24/02/2020 07:38, address@hidden wrote:
Thanks for being patient with me: I am an expert C++ programmer, but with very few experience in Obj-C, even for a number of doubts like that one above.

The key difference is that constructors and operator::new in C++ are special in the language. In Objective-C, +alloc and -init are just conventions with some API contracts.

[SomeClass alloc] is expected to return a new (uninitialised) instance of SomeClass, roughly analogous to calling operator::new, but it is not required to do so. The only constraints on +alloc are that the returned object must implement the -init-family methods that SomeClass defines.

The memory management contract on +alloc is that it returns an owning reference.

Similarly, the constraints on -init are much weaker than those on a C++ constructor. The -init family must return an object that is an instance of the class that was the result of the +alloc call or a subclass, or nil. It does not have to be the receiver (though it is in the common case), and it is possible to report construction failure by returning nil.

The memory management contract on -init is that it consumes one reference on the receiver and returns an owning reference on the result. In the common case, this is a no-op: if the receiver is the return value, there is no refcount manipulation. Most init methods look roughly like this:

- (instancetype)init
        if ((self = [super init]) == nil)
                return nil;
        // init code goes here
        return self;

Note that this is respecting the -init contract on the superclass: when the subclass calls [super init], the superclass may substitute a different instance or may fail and return nil (consuming the reference to the receiver).

For class clusters, it's very common for the superclass to return a singleton from +alloc, which then selects a custom subclass based on which of the +init methods is called. For example, in NSArray, you may get a singleton for a 0-element array, a special case for a one-element array, and have a subclass that allocates space at the end of it for all n-element arrays. For something adaptive such as NSMutableArray, you may get a different initial hashing policy or data structure depending on how you initially fill the array, but this may change over time.

In particular, note that Objective-C classes can call object_setClass() to pivot their type dynamically, so if you have a class cluster for NSMutableArray it is completely valid to get back an instance of a subclass optimised for handing a handful of values and have that pivot to an instance that uses a completely different data structure when you add a load of elements. In C++, you'd use double-dispatch for this (typically with static dispatch for the outer call), in Objective-C you're paying the price of dynamic dispatch all of the time and so you don't need to pay it twice.

When you want to create a subclass of a class cluster, you must provide the implementation of its primitive methods (e.g. -count, -addObject:, -objectAtIndex:). You may choose to provide more efficient implementations of the others, but the superclass guarantees via an informal contract that it will implement everything in terms of these.

The checks for the explicit class in +alloc and -init-family methods in the class cluster implementation are to ensure that subclasses can call these methods and to forward them down to the root class (NSObject), without invoking the special-case behaviour.

In C++, you'd implement something equivalent via a combination of two classes:

1. An implementation of the shareable behaviour using the curiously recursive template pattern so that subclasses of the concrete type would inherit generic implementations where they didn't have an explicit override. 2. A facade class that would use the pImpl pattern to hide which implementation was in use and allow the current implementation to be replaced dynamically.


reply via email to

[Prev in Thread] Current Thread [Next in Thread]