Каков наилучший способ определения строковых констант в протоколе objective-c?
Я определил протокол, который должны реализовать все мои плагины. Мне также хотелось бы, чтобы все плагины использовали определенные строки, например MyPluginErrorDomain. С целыми числами это довольно легко достигается в перечислении, но я не могу понять, как сделать то же самое со строками. Обычно в классах я определял бы
extern NSString * const MyPluginErrorDomain;
в файле .h и в файле .m:
NSString * const MyPluginErrorDomain = @"MyPluginErrorDomain";
но это не очень хорошо работает в протоколе, потому что тогда каждый плагин должен будет предоставить свою собственную реализацию, которая побеждает цель иметь константу.
Затем я попробовал
#define MYPLUGIN_ERROR_DOMAIN @"MyPluginErrorDomain"
но классы реализации в подключаемом модуле, похоже, не видят #define. Кто знает хорошее решение?
Ответы
Ответ 1
Вы можете объявить их в заголовке с протоколом (но вне самого интерфейса протокола), а затем определить их в файле реализации для протокола (очевидно, что он не будет иметь раздел @implementation
- только ваш NSString
определения).
Или иметь отдельную пару .h/.m, которая предназначена только для строковых констант (заголовок протокола может импортировать заголовок строковых констант).
Ответ 2
Вы сохраняете определение .h:
extern NSString * const MyPluginErrorDomain;
но поместите эту часть в отдельный файл .m, который входит в вашу структуру:
NSString * const MyPluginErrorDomain = @"MyPluginErrorDomain";
Так что плагины все еще могут реализовать интерфейс, но при компиляции они свяжутся или компилируются в другом файле .m, поэтому они будут видеть значение MyPluginErrorDomain
.
Ответ 3
В С++ я объявляю их в заголовке следующим образом:
const char * const MYPLUGIN_ERROR_DOMAIN = "MyPluginErrorDomain";
const char * const MYPLUGIN_FOO_DOMAIN = "MyPluginFooDomain";
Обратите внимание, что в качестве указателей const
они будут локальными для единиц перевода, в заголовок которых включен #, и поэтому не нужно использовать extern
для предотвращения множественных ошибок определения.
Ответ 4
Вы должны реализовать его как внешние строки, как в вашем примере:
extern NSString * const MyPluginErrorDomain;
или предоставить функции extern, которые возвращают данные статического хранилища. Например:
/* h */
extern NSString * MyPluginErrorDomain();
/* m */
NSString * MyPluginErrorDomain() {
static NSString * const s = @"MyPluginErrorDomain";
return s;
}
Причина в том, что строки и ключи часто используются и сравниваются по значению указателя или хеш-значению, а не по сравнению с истинным сопоставлением строк (isEqualToString:).
На уровне реализации существует большая разница между:
В коде это означает, что когда сравниваемые строки определены в нескольких двоичных файлах:
Скажите, что "MyPluginErrorDomain" и "key" имеют одинаковые строковые значения, но определены в разных двоичных файлах (например, на хосте плагина, в плагине).
/////// Pointer comparison (NSString)
BOOL a = [MyPluginErrorDomain isEqualToString:key];
BOOL b = MyPluginErrorDomain == key;
// c may be false because a may be true, in that they represent the same character sequence, but do not point to the same object
BOOL c = a == b;
/////// Hash use (NSString)
// This is true
BOOL d = [MyPluginErrorDomain hash] == [key hash];
// This is indicative if true
BOOL e = [MyPluginErrorDomain hash] == [someOtherStringKey hash];
// because
BOOL f = [MyPluginErrorDomain isEqualToString:someOtherStringKey];
// g may be false (though the hash code is 'generally' correct)
BOOL g = e == f;
Поэтому во многих случаях необходимо предоставить ключи. Это может показаться тривиальной точкой, но трудно диагностировать некоторые проблемы, связанные с различием.
Хэш-коды и сравнения указателей используются во всех Foundation и других технологиях objc во внутренних словах хранения словарей, кодировании ключевых значений... Если ваш словарь идет прямо в xml, это одна вещь, но использование во время выполнения - другое, и там это несколько предостережений в деталях реализации и выполнения.