How can i implement the behaviour as in cluster pattern by apple (NSString and NSCFString)

不想你离开。 提交于 2020-01-10 20:16:12

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!