Почему NSString отвечает на appendString?

Я играл с методом responsesToSelector в Objective-C на MacOS-X 10.6.7 и Xcode 4.0.2, чтобы определить, будет ли объект отвечать на определенные сообщения. Согласно руководствам, NSString не должен отвечать на appendString: в то время как NSMutableString должен. Вот фрагмент кода, который его проверяет:

int main (int argc, const char * argv[])
{

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *myString = [[NSString alloc] init];

    if ([myString respondsToSelector:@selector(appendString:)]) {
        NSLog(@"myString responds to appendString:");
    } else {
        NSLog(@"myString doesn't respond to appendString:");
    }

    // do stuff with myString

    [myString release];
    [pool drain];
    return 0;
}

и здесь вывод:

Class02[10241:903] myString responds to appendString:

Я бы хотел, чтобы все было наоборот. Как объект NSString отвечает на appendString:? Что происходит здесь, что мне не хватает?

Ответы

Ответ 1

Короткий ответ: эта строка имеет тип NSCFString, класс, который наследует от NSMutableString, поэтому он отвечает селекторам для методов, объявленных в NSMutableString, включая суперклассы.

Не так короткий ответ: Строковые строки беспошлины соединяются с строками Core Foundation. Разработчики используют непрозрачные типы CFStringRef (привязанные к NSString) и CFMutableStringRef (привязанные к NSMutableString), чтобы ссылаться на эти строки, поэтому на первый взгляд существуют два разных типа строк: неизменяемые и изменяемые.

С точки зрения внутренней реализации Core Foundation, это частный тип, называемый struct __CFString. Этот частный тип хранит поле бит, которое хранит, помимо прочего, информацию о том, является ли строка изменчивой или неизменяемой. Наличие одного типа упрощает реализацию, поскольку многие функции разделяются как неизменяемыми, так и изменяемыми строками.

Всякий раз, когда вызывается функция Core Foundation, которая работает с изменяемыми строками, она сначала считывает это поле бит и проверяет, является ли строка изменчивой или неизменяемой. Если аргумент должен быть изменчивой строкой, но на самом деле isnt, функция возвращает ошибку (например, _CFStringErrNotMutable) или не выполняет утверждение (например, __CFAssertIsStringAndMutable(cf)).

Во всяком случае, это детали реализации, и они могут измениться в будущем. Тот факт, что NSString не объявляет -appendString:, не означает, что каждый экземпляр NSString не отвечает на соответствующий селектор - думаю substitutability. Такая же ситуация применима и к другим изменяемым/неизменяемым классам, таким как NSArray и NSMutableArray. С точки зрения разработчика важно то, что возвращаемый объект имеет тип, который соответствует типу возврата - это может быть сам тип или любой подтип этого типа. Кластеры кластера делают это еще более запутанным, но ситуация не ограничивается кластерами классов как таковыми.

Таким образом, вы можете только ожидать, что метод возвращает объект, тип которого принадлежит иерархии (то есть либо самого типа, либо подтипа) для типа возвращаемого значения. К сожалению, это означает, что вы не можете проверить, является ли объект Foundation изменчивым или нет. Но опять же, вам действительно нужна эта проверка?


Вы можете использовать функцию CFShowStr() для получения информации из строки. В примере в вашем вопросе добавьте

CFShowStr((CFStringRef)myString);

Вы должны получить результат, похожий на:

Length 0
IsEightBit 1
HasLengthByte 0
HasNullByte 1
InlineContents 0
Allocator SystemDefault
Mutable 0
Contents 0x0

где

Mutable 0

означает, что строка фактически неизменяема.

Ответ 2

Это, вероятно, связано с реализацией. NSString - кластер классов, что означает, что NSString - это просто открытый интерфейс, а фактический класс реализации отличается (см., что дает сообщение class)).

И в то же время NSString также является бесплатным для CFString, что означает, что вы можете переключаться перед этими двумя типами свободно, просто используя кастинг:

NSString *one = @"foo";
CFStringRef two = (CFStringRef)one; // valid cast

Когда вы создаете новую строку, вы действительно получаете обратно NSCFString назад, тонкую обертку вокруг CFString. И дело в том, что при создании новой изменяемой строки вы также получаете экземпляр NSCFString.

Class one = [[NSString string] class]; // NSCFString
Class two = [[NSMutableString string] class]; // NSCFString

Я предполагаю, что это было удобно с точки зрения реализации - оба NSString и NSMutableString могут поддерживаться общим классом (= меньше дублирования кода), и этот класс гарантирует, что вы не нарушите неизменность:

// "Attempt to mutate immutable object with appendString:"
[[NSString string] appendString:@"foo"];

Theres много угадывают работу в этом ответе, и я действительно не понимаю вещи, давайте надеяться, что кто-то знает лучше.

Ответ 3

Вы не должны делать предположений о том, что метод не существует. Этот метод может использоваться внутри страны или по какой-либо причине. Технически это просто частный API.

У вас есть только контракт для публичных объявлений (docs), и они не показывают это сообщение. Поэтому будьте готовы быстро попасть в проблему, если используете другие функции.