2015-06-15 2 views
1

Я смотрю на базе кода, которая полнаЕсть ли способ иметь константы безопасности типа в Objective-C?

NSString *const kTabChart = @"Charts"; 
NSString *const kTabNews = @"News"; 

, а затем

setSelectedTab:(NSString *)title; 
... 
someThingElse:(NSString *)title; 

Таким образом, эти слабые типизированных NSString идти далеко, и весь путь вокруг кода, и это просто раздражает глаза , Перечисления будут лучше в некоторой степени, но перечисления не будут иметь доступных имен программно, и я не хочу определять все не связанные имена вкладок из разных представлений в одном и том же перечислении {}

Интересно, есть ли лучший способ? Я мечтаю о пути, чтобы сделать что-то вроде

@interface PageTitle:NSSting; 
PageTitle kTabChart = /some kind of initializer with @"Chart"/; 
PageTitle kTabNews = /some kind of initializer with @"News"/; 

Я подозреваю, что это не будет играть хорошо со всем «постоянным не во время компиляции» ограничения, но мне интересно, если есть трюки/скороговорки/хаки для определения констант моего типа класса.

+0

Добавить методы класса в ваш PageTitle, который возвращает ваши константы. И кстати. вы не можете подклассифицировать NSString. –

+0

Возможно подкласс 'NSString', но, как правило, это плохой выбор, rarley done и нелегко. – zaph

+1

Вот для чего нужны перечисления. И эти строки для пользовательского интерфейса в любом случае не принадлежат. – Eiko

ответ

-2

Вы подумали о #define macro?

#define kTabChart @"Charts" 

На стадии предварительной обработки компиляции, компилятор будет поменять все kTabChart ж/нужные постоянные.

Если вы хотите константы вашего собственного пользовательского класса, то вам придется использовать const как говорит пользователь @JeremyP в связанном ответе

+2

Макросы не являются безопасными по типу, следует использовать и, в общем, очень плохой выбор. – zaph

+0

Хм Я этого не знал ... Я просто понял, что это безопасно, потому что компилятор не позволит вам назначить макрос. Не могли бы вы привести мне пример того, где вы можете изменить базовый тип макроса? Мне тяжело обворачивать вокруг себя –

+0

#define не имеет типа, это, по сути, просто замена текста. В этом случае компилятор может определить тип детерминации, но вообще нет. В примере '#define x 3' x и 3 можно использовать как char, short, long, float и т. Д. С' NSString * const kTabChart = @ "Charts"; 'kTabChart напрямую имеет тип. – zaph

0

Конечно, просто думаю, что подклассы. Первый наш класс, который является подклассом NSString:

@interface StringConstants : NSString 

extern StringConstants * const kOptionApple; 
extern StringConstants * const kOptionBlackberry; 

@end 

Так мы определили StringConstants и пару глобальных констант для него. Для реализации класса без каких-либо предупреждений требуется просто литье:

@implementation StringConstants 

StringConstants * const kOptionApple = (StringConstants *)@"Apple"; 
StringConstants * const kOptionBlackberry = (StringConstants *)@"Blackberry"; 

@end 

И есть наш набор констант. Давайте проверим его:

- (void) printMe:(StringConstants *)string 
{ 
    NSLog(@"string: %@", string); 
} 

- (void) test 
{ 
    [self printMe:kOptionApple]; // Code completion offers the constants 
    [self printMe:@"Rhubarb"]; // Warning: Incompatible pointer types 
    [self printMe:(StringConstants *)@"Custard"]; // OK 
} 

Вы получите только предупреждение, код будет работать, как и при других подобных ошибках.

Вы можете, конечно, повторить шаблон и создать один «класс» для каждого набора строк.

НТН


Добавление: Безопасно (Trust Me For Now) и слабая Enum

Концерн был поднят в комментариях, что приведенный выше код является по существу опасно, это нет. Однако в общем случае обоснованные опасения, здесь безопасно по дизайну.

Примечание: это вводится непосредственно в SO.Пожалуйста, простите неизбежные орфографии & грамматических ошибок, а также вероятное отсутствие хорошей презентацию, четко определенной объяснительной дуга, недостающие биты, избыточных биты и т.д.

Сначала давайте добавим недостающие комментарии к выше коде, начните с реализация:

// The following *downcasts* strings to be StringConstants, code that 
// does this should only appear in this implementation file. Use in 
// other circumstances would effectively increase the number of "enum" 
// values in the set, which rather defeats the purpose of this class! 
// 
// In general downcasting should only be performed after type checks to 
// make sure it is safe. In this particular case *by design* it is safe. 

StringConstants * const kOptionApple = (StringConstants *)@"Apple"; 

Есть два разных вопроса здесь

  1. безопасно вообще - да дизайн, поверьте мне (на данный момент); и
  2. Добавление дополнительных «перечисление» значения

Второй охвачена второй пропавшей комментарий в тестовом коде:

[self printMe:(StringConstants *)@"Custard"]; // OK :-(- go ahead, graft 
               // in a new value and shoot 
               // yourself in the foot if 
               // you must ;-) 

Слабое enum

Работа с второй вопрос в первую очередь, неудивительно, что это «перечисление» не является пуленепробиваемым - вы можете добавить дополнительные значения «на лету». Почему неудивительно? Поскольку вы можете сделать это и в (Objective-) C, не только язык, который не сильно типизирован, то типы enum являются самыми слабыми из партии. Рассмотрим:

typedef enum { kApple, kBlackberry } PieOptions; 

Сколько действительных значений PieOptions есть? Использование Xcode/Clang: 2^32, не 2. Следующий вполне допустимо:

PieOptions po = (PieOptions)42; 

Теперь, когда вы не должны писать такие явно неверный код необходимость преобразования между целыми числами и enum значений общего - например, при сохранении значений «enum» в поле тега элементов управления пользовательского интерфейса - и, следовательно, в комнате для ошибок. Нумерация C-стиля должна использоваться с дисциплиной и использоваться таким образом, чтобы помочь правильной программе и ее удобочитаемости.

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

С простой дисциплиной, не выполняющей произвольные строки до StringConstants; то, что разрешено только в StringConstants сама реализация; этот тип дает вам абсолютно безопасное «перечисление строковых значений» с предупреждениями о компиляции при неправильном использовании.

И если вы доверяете мне, вы можете прекратить читать сейчас ...


Добавление: Копаем глубже (Просто Любопытный или мы вам не доверяем)

Чтобы понять, почему StringConstants является полностью безопасным (даже добавление дополнительных значений не очень небезопасно, хотя это может из конечно, вызывают сбои в программной логике), мы рассмотрим ряд вопросов о природе объектно-ориентированного программирования, динамической типизации и Objective-C. SOme о том, что следует, не обязательно нужно понимать, почему StringConstants безопасен, но вы кто-то с вопросительным умом, не так ли?

ссылка на объект забросы ничего не делают во время выполнения

отбрасываемой от одного объекта ссылочного типа к другому времени компиляции утверждение о том, что ссылка должна рассматриваться как к объекту из тип назначения. Он не влияет на фактический объект ссылки во время выполнения - этот объект имеет фактический тип, и он не изменяется. В объектно-ориентированной модели приведения к базовому типу, идущей от одного класса к одному из его суперкласса, всегда безопасно, понижающее приведение, идет в обратном направлении, может не (нене) быть безопасным. По этой причине снижение рейтинга должно быть защищено испытаниями в тех случаях, когда это может быть опасно. Например Дано:

NSArray *one = @[ @{ @"this": @"is", @"a" : @"dictionary" } ]; 

Код:

NSUInteger len = [one.firstObject length]; // error, meant count, but NO compiler help at all -> runtime error 

потерпит неудачу во время выполнения. Тип результата firstObject - id, что означает любой тип объекта, и компилятор позволит вам вызвать любой метод на ссылках, набранных как id. Ошибки здесь состоят в том, чтобы не проверять границы массива и что полученная ссылка на самом деле является словарем. Более пуленепробиваемый подход:

if (one.count > 0) 
{ 
    id first = one.firstObject; 
    if ([first isKindOfClass:[NSDictionary class]]) 
    { 
     NSDictionary *firstDict = first; // *downcast* to improve compile time checking 
     NSLog(@"The count of the first item is %lu", firstDict.count); 
    } 
    else 
     NSLog(@"The first item is not a dictionary"); 
} 
else 
    NSLog(@"The array his empty"); 

(невидимое) литые совершенно безопасен, защищен, как это с помощью теста isKindOf:. Случайно введите firstDict.length в приведенном выше фрагменте кода и вы получите получить ошибку времени компиляции.

Однако вам нужно сделать это только в том случае, если downcast может быть недействительным, если он не может быть недействительным, никаких тестов не требуется.

Почему вы можете назвать любой метод для типов ссылок как id?

Это то, что ищет динамический просмотр сообщений Objective-C в режиме реального времени. Компилятор проверяет, насколько это возможно, ошибки типа во время компиляции. Затем во время выполнения выполняется другая проверка - поддерживает ли ссылочный объект вызываемый метод? Если это не ошибка , то возникает ошибка - как это происходит с приведенным выше примером length. Когда ссылка на объект вводится как id, это инструкция для компилятора, чтобы вообще не выполнять проверку времени компиляции и оставлять все это для проверок времени выполнения.

Проверка времени выполнения не проверяя тип ссылочного объекта, но поддерживает вместо того, что запрошенный метод, который приводит нас к ...

Утки, NSProxy, наследственное, и др. и др.

Утки ?!

В динамической типизации есть поговорка:

Если это выглядит как утка, плавает как утка и крякает как утка, то это утка.

В терминах Objective-C это означает, что во время выполнения, если объект ссылки поддерживает набор методов типа А, то это эффективно объект типа A, независимо от того, что это реально тип.

Эта функция используется во многих местах в Objective-C, яркий пример является NSProxy:

NSProxy является абстрактным суперклассом определения API для объектов, которые выступают в качестве дублеров для других объектов или для объектов которые еще не существуют. Как правило, сообщение прокси пересылается реальному объекту или заставляет прокси загружать (или преобразовывать себя) в реальный объект. Подклассы NSProxy могут использоваться для реализации прозрачного распределенного обмена сообщениями (например, NSDistantObject) или для ленивого создания объектов, которые дороги для создания.

С NSProxy вы можете думать, у вас есть, скажем, NSDictionary - то, что «выглядит, плавает и шарлатаны», как в словаре - но на самом деле вы не получили один на всех. Важными моментами являются:

  1. Не имеет значения; и
  2. Это совершенно безопасно (по модулю кодирования ошибки, если, конечно)

Вы можете просмотреть эту возможность заменить один объект на другой, как обобщение наследования - с тем позже вы всегда можете использовать подкласс экземпляра вместо суперкласса, с первым вы можете использовать любой объект вместо другого, если он «выглядит, плавает и крякает», как объект, в котором он стоит.

Мы фактически пошли дальше, чем нам нужно, утки на самом деле не требуется, чтобы понять StringConstants, так что давайте на:

Когда строка экземпляр NSString?

Возможно никогда ...

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

Кроме того, если вы считаете, что у вас есть экземпляр NSString, у вас может быть экземпляр NSProxy ... Но это не имеет значения. (ну это может повлиять на производительность, но это не влияет на безопасность или правильность.)

StringConstants является подклассом NSString, так что, конечно, является NSString, за исключением того, что NSString случаи, вероятно, не существует - каждая строка фактически экземпляр какого-либо другого класса из кластера, который может или не может быть подклассом NSString. Но это не имеет значения!

Пока экземпляры StringConstants шарлатан, как NSString с должны тогда они являютсяNSString s - и все экземпляры, которые мы определяли в осуществлении делать, поскольку они являются строки (некоторого типа, вероятно, __NSCFConstantString) ,

Что оставляет нас с вопросом, является ли определение звука констант StringConstants? Какой именно вопрос:

Когда Даунинг известен как всегда безопасный?

Первый пример, когда это не так:

Если указываемый классифицирован как NSDictionary * то не известно, чтобы быть безопасным, чтобы привести его к NSMutableDictionary *без первого тестирования ли ссылка к изменяемому словарю.

Компилятор всегда позволит вам сделать бросок, вы можете при компиляции совершать вызовы методам мутации без ошибок, но во время выполнения будут ошибки. В этом случае вы должны протестировать до кастинга.

Обратите внимание, что стандартный тест, isKindOf:, эффективно консервативные из-за всех этих уток. Фактически у вас может быть ссылка на объект, который совершает кражу как NSMutableDictionary, но не является его экземпляром, поэтому бросок будет безопасным, но тест не удастся.

Что делает этот бросок небезопасным вообще?

Простой, не известно, отвечает ли ссылка на объект с методами, что NSMutableDictionary делает ...

Ergo, если вы знали, что ссылка необходимо реагировать на все методы из тип, который вы отливаете, тогда литье всегда безопасно и никаких испытаний не требуется.

Как вы знаете, Ссылка должна отвечать всем методам целевого типа?

Ну один случай прост: Если у вас есть ссылка классифицирована как T вы можете это ссылка типа S безопасно без каких-либо проверок whatsever если:

  1. S является подклассом T - так крякает как T; и
  2. S не добавляет состояния экземпляра (переменных) в T; и
  3. S не добавляет поведения экземпляра (новые методы, переопределения и т. Д.) В T; и
  4. S переопределяет нет поведения класса

Sможет методы добавить класса нового класса (не Переопределяет) и глобальные переменные/постоянные, не нарушая эти требования.

Другими словами S определяется как:

@interface S : T 

// zero or more new class methods 

// zero or more global variables or constants 

@end 

@implementation S 

// implementation of any added class methods, etc. 

@end 

И мы сделали это ...

Или же мы, кто еще читает?

  1. StringConstants является дизайн построена так, что струнные экземпляры могут быть поданы на него. Это должно быть сделано в реализации, прокрадываясь в дополнительные константы «enum» в другом месте, идет вразрез с целью этого класса.
  2. Это безопасно, на самом деле его даже не страшно :-)
  3. Нет реальных экземпляров StringConstants не когда-либо созданные, каждая константа является экземпляром некоторого класса строки безопасно во время компиляции маскировки как StringConstants например ,
  4. Он обеспечивает время компиляции, проверяя, что строковая константа из заданного набора значений, это фактически «перечисление по строкам».

Еще один Дополнение: поддержания дисциплины

Вы не можете полностью автоматически следить за соблюдением дисциплины, необходимые для безопасного кода в Objective-C.

В частности, вы не можете компилятор запретить программистам отличать произвольные целочисленные значения до enum. Действительно, из-за использования, такого как поля тегов элементов управления пользовательского интерфейса, такие приведения должны быть разрешены при определенных обстоятельствах - они не могут быть полностью запрещены.

В случае StringConstants мы не можем иметь компилятор предотвратить бросание из строки всюду, за исключением реализации самого класса, так же, как с enum дополнительных литералы «перечисления» могут быть привиты. Это правило требует дисциплины.

Однако, если дисциплина отсутствует, компилятор может помочь предотвратить все способы, отличные от литья, которые могут использоваться для создания значений NSString и, следовательно, значений StringConstant, поскольку это подкласс. Другими словами, все варианты initX, stringX и т. Д. Могут быть отмечены как непригодные для использования на StringConstant. Это делается просто перечисляя их в @interface и добавление NS_UNAVAILABLE

Вы не нужно, чтобы сделать это, и ответ выше не происходит, но если вам нужна эта помощь вашей дисциплине вы можете добавить объявления ниже - этот список был создан путем простого копирования с NSString.h и быстрого поиска & заменить.

+ (instancetype) new NS_UNAVAILABLE; 
+ (instancetype) alloc NS_UNAVAILABLE; 
+ (instancetype) allocWithZone:(NSZone *)zone NS_UNAVAILABLE; 

- (instancetype) init NS_UNAVAILABLE; 

- (instancetype) copy NS_UNAVAILABLE; 
- (instancetype) copyWithZone:(NSZone *)zone NS_UNAVAILABLE; 

- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer NS_UNAVAILABLE; 
- (instancetype)initWithCharacters:(const unichar *)characters length:(NSUInteger)length NS_UNAVAILABLE; 
- (instancetype)initWithUTF8String:(const char *)nullTerminatedCString NS_UNAVAILABLE; 
- (instancetype)initWithString:(NSString *)aString NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0) NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format locale:(id)locale, ... NS_FORMAT_FUNCTION(1,3) NS_UNAVAILABLE; 
- (instancetype)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList NS_FORMAT_FUNCTION(1,0) NS_UNAVAILABLE; 
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding NS_UNAVAILABLE; 
- (instancetype)initWithBytes:(const void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding NS_UNAVAILABLE; 
- (instancetype)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding freeWhenDone:(BOOL)freeBuffer NS_UNAVAILABLE; 

+ (instancetype)string NS_UNAVAILABLE; 
+ (instancetype)stringWithString:(NSString *)string NS_UNAVAILABLE; 
+ (instancetype)stringWithCharacters:(const unichar *)characters length:(NSUInteger)length NS_UNAVAILABLE; 
+ (instancetype)stringWithUTF8String:(const char *)nullTerminatedCString NS_UNAVAILABLE; 
+ (instancetype)stringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) NS_UNAVAILABLE; 
+ (instancetype)localizedStringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2) NS_UNAVAILABLE; 

- (instancetype)initWithCString:(const char *)nullTerminatedCString encoding:(NSStringEncoding)encoding NS_UNAVAILABLE; 
+ (instancetype)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc NS_UNAVAILABLE; 

- (instancetype)initWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 
- (instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error NS_UNAVAILABLE; 

- (instancetype)initWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
- (instancetype)initWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfURL:(NSURL *)url usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
+ (instancetype)stringWithContentsOfFile:(NSString *)path usedEncoding:(NSStringEncoding *)enc error:(NSError **)error NS_UNAVAILABLE; 
+2

Хммм ... подклассификация NSString - трудная задача, даже для этой «простой» задачи. Кастинг NSString для вашего подкласса не делает его одним, поэтому он теперь распространяет ложь о его содержании, что открывает хорошую возможность червей на будущее. Кроме того, NSString - кластер классов, что делает его еще сложнее для подкласса. Я думаю, что этот подход, хотя «быстрое исправление», делает вещи намного хуже в долгосрочной перспективе, представляет собой серьезное злоупотребление языком и должен считаться большим NO-NO с большим жирным красным флагом. – Eiko

+0

@Eiko - В этом случае вам не нужно беспокоиться. Никогда не существует экземпляра 'StringConstant', все они просто« NSString »(или какой-либо фактический класс из кластера классов). Попросите один из них тип, и он не скажет 'StringConstant' - его указатель больше не говорит о« ложь », чем« NSString * ». Целый Objective-C работает так (в частности, думает 'id' и кластер классов). Это дает вам предупреждения о компиляции без изменения динамической типизации во время выполнения. – CRD

+0

@Eiko - BTW Если вы действительно хотите заблокировать его, добавьте '-init',' + new', '+ alloc'' -copy' и все другие методы {init, copy, alloc, string} * помечены 'NS_UNAVAILABLE', поэтому экземпляры не могут быть созданы. (Просто скопируйте их из 'NSString', добавьте' NS_UNAVAILABLE' и поместите в заголовок - никаких реализаций не требуется. – CRD

Смежные вопросы