Особенности создания NSString
Статья расчитана на новичков в Objective-C и рассказывает об одном способе выстрелить себе в ногу. Мы попытаемся создать два различных объекта NSString с одинаковым текстом, исследуем реакцию на это различных компиляторов, а также узнаем, при каких условиях NSLog (@»%@», @»123456789») выведет совсем не »123456789».Объекты NSString и указателиКак вы думаете, что выведет следующий код? #import «Foundation/Foundation.h» int main (){ @autoreleasepool { NSString *a = @»123456789»; NSString *b = a; NSLog (@»%p %p», a, b); } return 0; } Естественно, указатели будут равны («объекты присваиваются по ссылке»), так что NSLog () напечатает два одинаковых адреса памяти. Никакой магии:2015–01–30 14:39:27.662 1-nsstring[13574] 0×602ea0 0×602ea0
Здесь и далее адреса объектов приводятся в качестве примера; при попытке воспроизведения фактические значения, разумеется, будут другими.
Давайте попробуем добиться того, чтобы у нас было два различных NSString с одинаковым текстом. В случае других стандартных классов, например, NSArray, мы могли бы написать так:
#import «Foundation/Foundation.h» int main (){ @autoreleasepool { NSArray *a = @[@»123456789»]; NSArray *b = @[@»123456789»]; NSLog (@»%p %p», a, b); } return 0; } Поскольку мы инициализировали NSArray по отдельности, то они были помещены в различные участки памяти и в консоли высветятся два разных адреса:2015–01–30 14:40:45.799 2-nsarray[13634] 0xa9e1b8 0xaa34e8
Однако применение такого же подхода к NSString не приведет к желаемому эффекту:
#import «Foundation/Foundation.h» int main (){ @autoreleasepool { NSString *a = @»123456789»; NSString *b = @»123456789»; NSLog (@»%p %p», a, b); } return 0; } 2015–01–30 14:41:41.898 3-nsstring[13678] 0×602ea0 0×602ea0Как видим, несмотря на раздельную инициализацию, оба указателя по-прежнему ссылаются на одну и ту же область памяти.
Использование stringWithString Немного покопавшись в NSString, мы обнаруживаем метод stringWithString, который «returns a string created by copying the characters from another given string». Так это же то, что нам надо! Попробуем следующий код: #import «Foundation/Foundation.h» int main (){ @autoreleasepool { NSString *a = @»123456789»; NSString *b = [NSString stringWithString:@»123456789»]; NSString *с = [NSString stringWithString: b]; NSLog (@»%p %p %p», a, b, с); } return 0; } Оказывается, что вывод этой программы зависит от используемой версии компилятора. Так clang под Ubuntu на LLVM 3.4 действительно создаст три различных объекта, расположенных в различных ячейках памяти. Но компиляция указанного кода в Xcode при помощи clang под Mac на LLVM 3.5 сгенерирует всего один объект и три пойнтера на него:2015–01–30 17:59:02.206 4-nsstring[670:21855] 0×100001048 0×100001048 0×100001048
Сеанс магии с разоблачением Вышеуказанные странности объясняются попытками компилятора оптимизировать строковые ресурсы. Встречая в исходном коде строковые объекты с одинаковым содержанием, он для экономии затрат на хранение и сравнение создает их только один раз. Эта оптимизация выполняется также и на этапе линковки: даже если строки с одинаковым текстом находятся в различных модулях, скорее всего они будут созданы только один раз.Поскольку тип NSString является неизменяемым (для изменяемых строк используется NSMutableString), то такая оптимизация является безопасной. До тех пор, пока мы манипулируем со строками только методами класса NSString.
Компилятор, впрочем, не всемогущ. Один из самых простых способов запутать его и действительно создать два различных NSString c одинаковым текстом — таков:
#import «Foundation/Foundation.h»
int main (){
@autoreleasepool {
NSString *a = @»123456789»;
NSString *b = [NSString stringWithFormat:@»%@», a];
NSLog (@»%p %p», a, b);
}
return 0;
}
GCC
Аналогичную оптимизацию строковых констант выполняет и gcc при компиляции кода на Си. Например,
#include
#include
#import
int main (){ @autoreleasepool { bad (); NSLog (@»%@», @»123456789»); } return 0; } В зависимости от компилятора результат может быть разным: мой Xcode под Маком печатает набор кракозябр »㈱㐳㘵㠷9䀥», а clang в Убунту выводит фрагмент из служебной информации «red: pars». В любом случае, это никак не ожидаемое »123456789». Эксперименты с другими значениями aa[8], а также aa[16], предлагаю читателю проделать самостоятельно.Хуже всего то, что функция bad () из последнего примера может находиться за хедером, например, в подключаемой библиотеке другого автора, который по своим нуждам изменял свой личный (как ему казалось) NSString. Умный компилятор все равно найдет совпадающие строковые константы и замкнет их на один пойнтер, после чего порча переменной внутри bad () повлечет превращение строки в контексте main () в иероглифы.