问题
I am simply writing the following code for testing purpose:
NSString *aStr = [[NSString alloc] initWithFormat:@"Foo"];
aStr = [aStr initWithFormat:@"Bar"];//Crashed here
I am getting the following error:
*** initialization method -initWithFormat:locale:arguments: cannot be sent to an abstract object of class __NSCFString: Create a concrete instance!
If i write the following code same thing happen
NSString *aStr = [NSString alloc];
aStr = [aStr initWithFormat:@"Foo"];
aStr = [aStr initWithFormat:@"Bar"]; //Crashed here
By google I come to know that initWithFormat
will return the NSCFString
Object.
My question is if NSCFString
is derived class of NSString
then why I cannot invoke the initWithFormat
method on NSCFString
. If it is possible to stop the visibility how can I implement in the code.
回答1:
Let's do some investigation on how NSString
class cluster works internally:
NSString *factory = [NSString alloc];
NSString *theInstance = [factory initWithString:@"I am constant"];
NSLog(@"factory class: %@, instance class: %@", [factory class], [theInstance class]);
And the output is:
factory class: NSPlaceholderString, instance class: __NSCFConstantString
As you can see, the alloc
method returns an instance of NSPlaceholderString
. It is a "factory" class which implements all the init...
methods declared in NSString
. These methods return concrete (private) subclasses of NSString
. It returns __NSCFConstantString
in this example.
If you change the first line to
NSString *factory = [NSMutableString alloc];
the output will change to:
NSPlaceholderMutableString, instance class: __NSCFString
So there are different factory classes for the mutable and immutable strings, and those factories return different subclasses.
You can even check the hierarchy of private subclasses in iOS runtime headers: here and here.
Now let's see what happens when we call initWithString:
on an instance of __NSCFConstantString
we just created.
[theInstance initWithString:@"Crash"];
As you expected - it crashes. In the stacktrace we can see that -[NSString initWithCharactersNoCopy:length:freeWhenDone:]
method is called, throwing an exception:
'NSInvalidArgumentException', reason: '*** initialization method -initWithCharactersNoCopy:length:freeWhenDone: cannot be sent to an abstract object of class __NSCFConstantString: Create a concrete instance!'
So we can guess that this initializer in NSString
class is actually an abstract method (kind of - there aren't abstract methods in Objective-C, so it throws an exception when called).
This method is implemented in the factory class NSPlaceholderString
. But it's not implemented in all the concrete subclasses, so if you call any of the init...
methods, it will call the NSString
implementation which throws the exception.
Let's put it all together and build a tiny part of the NSString
class cluster. It's really simplified and probably totally different than the real implementation, but I just wanted to show the idea.
@interface NSPlaceholderString : NSString
@end
@interface __NSCFConstantString : NSString
@end
@implementation NSString
+ (instancetype)alloc {
return [[NSPlaceholderString alloc] init];
}
- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer {
[NSException raise:NSInvalidArgumentException format:@" initialization method -initWithCharactersNoCopy:length:freeWhenDone: cannot be sent to an abstract object of class %@: Create a concrete instance!'", [self class]];
return nil;
}
- (instancetype)initWithString:(NSString *)aString {
//this method has to call the "abstract" initializer somewhere. The real implementation is probably more complex, this single line is here for simplicity
return [self initWithCharactersNoCopy:[aString UTF8String] length:[aString length] freeWhenDone:YES];
}
@end
@implementation NSPlaceholderString
- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer {
__NSCFConstantString *concreteClassInstance = ...; // create the concrete instance.
return concreteClassInstance;
}
@end
@implementation __NSCFConstantString
//implement all the needed methods here. But do NOT implement initWithCharactersNoCopy:length:freeWhenDone:
@end
回答2:
NSCFString
and NSCFConstantString
interfaces are private and you shouldn't be using them. While looking at your code, it's hard to discern why you would need to dive into the private subclasses when the public NSString
superclass does everything you need in an even simpler way:
NSString *aStr = @"Foo";
aStr = @"Bar";
Or, if you need to use the format:
NSString *aStr = [NSString stringWithFormat:@"Foo"];
回答3:
The problem is that you cannot reinitialize an NSString
since it is a not mutable class, if you want to change a NSString
after creation, you have to use NSMutableString
.
However, in your case, you can also use a NSString
, like this:
NSString *aStr = [[NSString alloc] initWithFormat:@"Foo"];
aStr = [[NSString alloc] initWithFormat:@"Bar"];
However, better would be:
NSString *aStr = @"Foo";
aStr = @"Bar";
if what you are trying to do is to append the string you would do:
NSMutableString *aStr = [[NSMutableString alloc] initWithString:@"Foo"];
[aStr appendString:@"Bar"];
回答4:
A class cluster is usually implemented with one public class and many private subclasses that derive from the public one so they have the same interface. Check NSNumber. By involving [NSNumber numberWithBool]; that method returns a instance of a subclass of NSNumber specific for bool's while [NSNumber numberWithInt]; returns a instance of a subclass of NSNumber specific for nit's. Both of them chard the same interface, the NSNumber interface.
回答5:
init methods don't have to return the same object. To implement the same behaviour, just write
- (instancetype)initWithSomeArguments
{
if ((self = [super initWithSomeArguments) != nil)
{
self = [[RelatedClass alloc] initWithSomeArguments];
}
return self;
}
来源:https://stackoverflow.com/questions/26607693/how-can-i-implement-the-behaviour-as-in-cluster-pattern-by-apple-nsstring-and-n