我怎样才能通过苹果(NSString和NSCFString)集群模式实现行为?

我只是简单地写下面的代码来进行testing:

NSString *aStr = [[NSString alloc] initWithFormat:@"Foo"]; aStr = [aStr initWithFormat:@"Bar"];//Crashed here 

我收到以下错误:

 *** initialization method -initWithFormat:locale:arguments: cannot be sent to an abstract object of class __NSCFString: Create a concrete instance! 

如果我写下面的代码同样的事情发生

 NSString *aStr = [NSString alloc]; aStr = [aStr initWithFormat:@"Foo"]; aStr = [aStr initWithFormat:@"Bar"]; //Crashed here 

通过谷歌我来知道, initWithFormat将返回NSCFString对象。 我的问题是,如果NSCFStringNSString派生类,那么为什么我不能调用NSCFString上的initWithFormat方法。 如果有可能停止可见性我怎么能在代码中实现。

让我们来做一些关于NSString类集群如何在内部工作的调查:

 NSString *factory = [NSString alloc]; NSString *theInstance = [factory initWithString:@"I am constant"]; NSLog(@"factory class: %@, instance class: %@", [factory class], [theInstance class]); 

输出是:

 factory class: NSPlaceholderString, instance class: __NSCFConstantString 

如你所见, alloc方法返回一个NSPlaceholderString的实例。 它是一个“工厂”类,它实现了在NSString声明的所有init...方法。 这些方法返回NSString具体(私有)子类。 在这个例子中它返回__NSCFConstantString

如果您将第一行更改为

 NSString *factory = [NSMutableString alloc]; 

输出将变为:

NSPlaceholderMutableString,实例类:__NSCFString

所以可变和不可变string有不同的工厂类,这些工厂返回不同的子类。

你甚至可以检查iOS运行时头文件中的私有子类的层次结构: 这里和这里 。


现在让我们看看在我们刚刚创build的__NSCFConstantString一个实例上调用initWithString:时会发生什么。

 [theInstance initWithString:@"Crash"]; 

如你所料 – 它崩溃。 在stacktrace中,我们可以看到-[NSString initWithCharactersNoCopy:length:freeWhenDone:]方法被调用,抛出一个exception:

'NSInvalidArgumentException',原因:'***初始化方法-initWithCharactersNoCopy:length:freeWhenDone:不能被发送到类的抽象对象__NSCFConstantString:创build一个具体的实例!

所以我们可以猜测,这个NSString类中的初始化实际上是一个抽象方法(Objective-C中没有抽象方法,所以在调用的时候抛出exception)。

该方法在工厂类NSPlaceholderString 。 但是它并没有在所有具体的子类中实现,所以如果你调用任何init...方法,它会调用抛出exception的NSString实现。


我们把它放在一起,构buildNSString类集群的一小部分。 这真的很简单,可能完全不同于真正的实现,但我只是想显示这个想法。

 @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 

NSCFStringNSCFConstantString接口是私有的,你不应该使用它们。 在查看你的代码时,很难判断为什么当公共NSString超类以更简单的方式完成你需要的一切时,你需要深入私有子类:

 NSString *aStr = @"Foo"; aStr = @"Bar"; 

或者,如果您需要使用格式:

 NSString *aStr = [NSString stringWithFormat:@"Foo"]; 

问题是你不能重新初始化一个NSString因为它是一个不可变的类,如果你想在创build后改变一个NSString ,你必须使用NSMutableString

但是,在你的情况下,你也可以使用一个NSString ,就像这样:

 NSString *aStr = [[NSString alloc] initWithFormat:@"Foo"]; aStr = [[NSString alloc] initWithFormat:@"Bar"]; 

但是,更好的是:

 NSString *aStr = @"Foo"; aStr = @"Bar"; 

如果你想要做的是追加string,你会这样做:

 NSMutableString *aStr = [[NSMutableString alloc] initWithString:@"Foo"]; [aStr appendString:@"Bar"]; 

类集群通常实现一个公共类和许多从公共类派生的私有子类,因此它们具有相同的接口。 检查NSNumber。 通过涉及[NSNumber numberWithBool]; 该方法返回一个NSNumber特定于bool的子类的实例,而[NSNumber numberWithInt]; 返回一个特定于nit的NSNumber子类的实例。 他们两个chard相同的接口,NSNumber接口。

init方法不必返回相同的对象。 要实现相同的行为,只需编写

 - (instancetype)initWithSomeArguments { if ((self = [super initWithSomeArguments) != nil) { self = [[RelatedClass alloc] initWithSomeArguments]; } return self; }