7

Just wondering:

In NSString there is a static method called +stringWithString:. This is not redeclared/overridden in NSMutableString so we cannot assume that this will return an NSMutableString. In fact even in the NSString class the return type is defined as id and the doc states:

Return Value
A string created by copying the characters from aString.

What part of objective C am I missing in my knowledge to understand why this works and returns an NSMutableString? Especially because the base class NSString is not aware that we want a mutable string in return.

One could say that internally [Class alloc] is called which will generate an object of type NSMutableString, but even this is pure guesswork as we do not have the source code and stringWithString: could do whatever it wants internally.

Are all those class methods reimplemented in the subclass? And if yes why isn't this documented?

Joris Mans
  • 6,024
  • 6
  • 42
  • 69

4 Answers4

6

In NSString there is a static method called +stringWithString:.

more approriately, it's a class method.

This is not redeclared/overridden in NSMutableString

In cocoa, the subclass does not need to redeclare the method. In fact, it would just produce a lot of noise (IMO). It only needs to redefine the method to provide its custom implementation.

so we cannot assume that this will return an NSMutableString.

We must assume that it will return a mutable string. A subclass can redefine its initializers and convenience constructors as needed in order to meet the required contracts without publicly redeclaring the method -- it only needs to define the method when the base's implementation is insufficient.

What part of objective C am I missing in my knowledge to understand why this works and returns an NSMutableString? Especially because the base class NSString is not aware that we want a mutable string in return.

It 'knows' because you have written [NSMutableString stringWithString:@"bah"] rather than [NSString stringWithString:@"bah"]. Like instance methods, class methods have an implicit self which allows them to pass the type through class methods. Therefore, class methods may be redefined/overridden as needed. The class methods may also use self to determine or message their type (example shortly).

One could say that internally [Class alloc] is called which will generate an object of type NSMutableString, but even this is pure guesswork as we do not have the source code and stringWithString: could do whatever it wants internally.

It should not be guesswork. In this case, you should file a bug if you were returned an immutable string. Otherwise, it works as advertised, regardless of whether they used one or more definitions.

Are all those class methods reimplemented in the subclass?

In the case of convenience constructors, it's more common to go through one of the designated initializers:

such an implementation could take the form:

@implementation NSString
+ (id)stringWithString:(NSString *)arg
{
    // self is either +NSString or +NSMutableString
    return [[[self alloc] initWithString:arg] autorelease];
}

although exceptions can often be made, and is often the case with optimized immutable/mutable types:

@implementation NSString
+ (id)stringWithString:(NSString *)arg
{
  return [arg imp_isMutable] ? [[[self alloc] initWithString:arg] autorelease] : arg;
}
...
@implementation NSMutableString
+ (id)stringWithString:(NSString *)arg
{
  return [[[self alloc] initWithString:arg] autorelease];
}
...

And if yes why isn't this documented?

They should not be redeclared or redocumented when the only difference is the class type which you have requested, unless they have some deviation from the base class or special note -- even in that case, it would be better to create a method with another name.

justin
  • 104,054
  • 14
  • 179
  • 226
  • I think you repeated parts of my assumptions (the part about "self"), but what it boils down to is that all class methods MUST be reimplemented by the subclass in order to return a consistent result otherwise the inheritance tree is "broken". So while it is technically possible that the base class method returns an NSString because it is not implemented it should not be the case as this voids the assumption that the subclass is a complete implementation of the superclass with extra functionality, and it would be considered a bug. Thanks! – Joris Mans Dec 01 '11 at 22:49
  • 1
    @Joris Not quite; the subclass doesn't have to override any of the class methods, if the class methods are implemented with the typical pattern. I.e. if `+stringWithString:` is implemented as `return [[[self alloc] initWithString:...] autorelease];` then there is no need for NSMutableString to override that method (though it'll need to override the `-initWithString:` instance method instead). – bbum Dec 02 '11 at 00:21
  • Yes, if you know how the baseclass is implemented and it remains consistent you obviously do not need to reimplement it. That makes sense. – Joris Mans Dec 02 '11 at 13:32
3

NSMutableString is a subclass of NSString thus any method called on NSMutableString can be expected to work appropriately, and return an NSMutableString when it makes sense. The only method that comes to mind that doesn't follow that though is copy which by convention returns an immutable instance.

This is why your initializer methods return id rather than a concrete instance, and why all class methods should use [self alloc] instead of [MYActualClass alloc].

Joshua Weinberg
  • 28,598
  • 2
  • 97
  • 90
3

You can write the initializer a bit like this:

+ (id) stringWithString: (NSString*) foo
{
    return [[self alloc] initWithString:foo];
}

Now when you call this initializer on NSString, [self alloc] returns NSString and you get an instance of NSString back. But when you call [NSMutableString stringWithString:@"…"], the result of the [self alloc] message is NSMutableString, and therefore the initializer returns a mutable string.

jscs
  • 63,694
  • 13
  • 151
  • 195
zoul
  • 102,279
  • 44
  • 260
  • 354
  • This would likely produce a memory leak. The method name `stringWithString:` [implies](https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/MemoryManagement.html#//apple_ref/doc/uid/TP40008195-CH27-SW3) that ownership of the returned object is not transfered to the receiver. – Ethan Reesor Nov 18 '12 at 08:35
  • I think this was written before ARC, so it was a leak indeed, but today with ARC it works as expected. – zoul Nov 18 '12 at 09:33
  • It’s not possible to add the `autorelease` call under ARC, the code practices good memory management as it is. – zoul Nov 18 '12 at 10:30
3

(What Justin said, but a clarification)

In NSString there is a static method called +stringWithString:. This is not redeclared/overridden in NSMutableString so we cannot assume that this will return an NSMutableString. In fact even in the NSString class the return type is defined as id ...

First and foremost, Objective-C does not have static methods. Objective-C has class methods. Class methods behave exactly like instance methods (where the class is an instance of a metaclass) and can be inherited, overridden, etc...

Thus, exactly as NSMutableString inherits characterAtIndex: and can override it to do something special, if needed, NSMutableString can do the same for class methods.

Note also that there is no need for a class to declare a method in the header that it overrides from its superclass. The classes in the framework generally do not declare overridden methods explicitly because the overridden method should behave exactly as the parent, but is overridden to work within the context of the subclass's requirements.

bbum
  • 162,346
  • 23
  • 271
  • 359