【问题标题】:How can i implement the behaviour as in cluster pattern by apple (NSString and NSCFString)我如何实现苹果集群模式中的行为(NSString 和 NSCFString)
【发布时间】:2014-12-23 19:40:42
【问题描述】:

我只是为了测试目的编写以下代码:

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 方法。如果可以停止可见性,我该如何在代码中实现。

【问题讨论】:

  • 在以这种方式(或任何其他方式)初始化后调用initWithFormat: on a string which is already initialized? You can't modify a NSString` 想要达到什么目的。为此目的使用NSMutableStringsetString 方法。
  • @MichałCiuba 甚至 NSMutable String 都崩溃了。请仔细阅读关于集群模式实现的问题。 Not NsSsting Init 方法是否被调用了两次。
  • 好的,我明白了。您的示例有点令人困惑,因为它立即让任何 Objective-C 开发人员想知道:“为什么有人想要调用 init 两次?” :) 似乎没有人理解这个问题,如果我理解正确的话,“我怎样才能从不响应这个initWith... 方法的initWith... 方法返回一个对象”。

标签: ios objective-c nsstring


【解决方案1】:

让我们来研究一下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

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

您甚至可以检查 iOS 运行时标头中私有子类的层次结构:herehere


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

[theInstance initWithString:@"Crash"];

如您所料 - 它崩溃了。在stacktrace中我们可以看到-[NSString initWithCharactersNoCopy:length:freeWhenDone:]方法被调用,抛出异常:

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

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

这个方法在工厂类NSPlaceholderString中实现。但它没有在所有具体子类中实现,因此如果您调用任何init... 方法,它将调用引发异常的NSString 实现。


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

@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】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

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

        但是,在您的情况下,您也可以使用NSString,如下所示:

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

        但是,更好的是:

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

        如果你想要做的是附加你会做的字符串:

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

        【讨论】:

        • 感谢您的回复。我的问题不是关于 NSSTring init 方法调用我问的是如何实现这样的集群模式,以便 NSCFString 不能调用 NSString 方法。
        • 集群模式没有完成,所以NSCFString 不能调用NSString 方法,它已经完成,所以你可以像使用NSCFString 一样使用NSCFString
        【解决方案5】:

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

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

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

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

        【讨论】:

        • 感谢您的回复。我正在询问如何实现这样的集群模式,以便 NSCFString 无法调用 NSString 方法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-26
        • 1970-01-01
        • 2014-08-29
        相关资源
        最近更新 更多