[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.
David