Подсказки и методы для разработчиков инфраструктуры
Разработчики платформ должны быть более осторожными, чем другие разработчики в том, как они пишут свой код. Много клиентских приложений могли соединиться в их платформе и из-за этого широкого воздействия, любые недостатки в платформе могли бы быть увеличены по всей системе. Следующие элементы обсуждают методы программирования, которые можно принять для обеспечения эффективности и целостности платформы.
Инициализация
Следующие предложения и рекомендации покрывают инициализацию платформы.
Инициализация класса
initialize
метод класса дает Вам место для имения некоторого кода, выполняемого один раз, лениво, прежде чем будет вызван любой другой метод класса. Это обычно используется для установки номеров версий классов (см. Управление версиями и Совместимость).
Время выполнения отправляет initialize
к каждому классу в цепочке наследования, даже если это не реализовало его; таким образом это могло бы вызвать класс initialize
метод несколько раз (если, например, подкласс не реализовал его). Обычно Вы хотите, чтобы код инициализации был выполнен только один раз. Один способ гарантировать это происходит, должен использовать dispatch_once()
:
+ (void)initialize { |
static dispatch_once_t onceToken = 0; |
dispatch_once(&onceToken, ^{ |
// the initializing code |
} |
} |
Вы никогда не должны вызывать initialize
метод явно. Если необходимо инициировать инициализацию, вызовите некоторый безопасный метод, например:
[NSImage self]; |
Определяемые инициализаторы
Определяемый инициализатор init
метод класса, вызывающего init
метод суперкласса. (Другие инициализаторы вызывают init
методы определяются классом.) Каждый общедоступный класс должен иметь один или несколько определяемые инициализаторы. Как примеры определяемых инициализаторов там NSView
initWithFrame:
и NSResponder
init
метод. Где init
методы не предназначены, чтобы быть переопределенными, как имеет место с NSString
и другие абстрактные классы, выходящие на кластеры класса, подкласс, как ожидают, реализует свое собственное.
Определяемые инициализаторы должны быть ясно идентифицированы, потому что эта информация важна для тех, кто хочет разделить Ваш класс на подклассы. Подкласс может просто переопределить определяемый инициализатор, и все другие инициализаторы будут работать, как разработано.
При реализации класса платформы часто необходимо реализовывать ее методы архивации также: initWithCoder:
и encodeWithCoder:
. Бойтесь делать вещи в пути выполнения кода инициализации, не происходящем, когда разархивирован объект. Хороший способ достигнуть этого состоит в том, чтобы вызвать общую подпрограмму от Ваших определяемых инициализаторов и initWithCoder:
(который является самим определяемым инициализатором), если Ваш класс реализует архивацию.
Обнаружение ошибок во время инициализации
Хорошо разработанный метод инициализации должен завершить следующие шаги для обеспечения надлежащего обнаружения и распространения ошибок:
Повторно присвойтесь сам путем вызова
super
определяемый инициализатор.Проверьте возвращенное значение на
nil
, который указывает, что некоторая ошибка произошла в инициализации суперкласса.Если ошибка происходит при инициализации текущего класса выпустите объект и возврат
nil
.
Перечисление 1 иллюстрирует, как Вы могли бы сделать это.
Обнаружение ошибок перечисления 1 во время инициализации
- (id)init { |
self = [super init]; // Call a designated initializer here. |
if (self != nil) { |
// Initialize object ... |
if (someError) { |
[self release]; |
self = nil; |
} |
} |
return self; |
} |
Управление версиями и совместимость
Когда Вы добавляете новые классы или методы к Вашей платформе, не обычно необходимо указать новые номера версий для каждой новой телефонной группы. Разработчики обычно выполняют (или должен выполнить), проверки на этапе выполнения Objective C такой как respondsToSelector:
определить, доступна ли функция в данной системе. Эти тесты во время выполнения являются предпочтительным и самым динамическим способом проверить на новые функции.
Однако можно использовать несколько методов, чтобы удостовериться, что каждая новая версия платформы должным образом отмечена и сделана максимально совместимая с более ранними версиями.
Версия платформы
Когда присутствие новой функции или исправления ошибки не легко обнаруживаемо с тестами во время выполнения, необходимо предоставить разработчикам некоторый способ проверить на изменение. Один способ достигнуть этого состоит в том, чтобы сохранить точный номер версии платформы и сделать это число доступным для разработчиков:
Задокументируйте изменение (в информации о версии, например) под номером версии.
Определите номер текущей версии своей платформы и обеспечьте некоторый способ сделать его глобально доступным. Вы могли бы сохранить номер версии в информационном списке свойств своей платформы (
Info.plist
) и доступ это оттуда.
Включенная архивация
Если объекты Вашей платформы должны быть записаны в файл пера, они должны быть в состоянии заархивировать себя. Также необходимо заархивировать любые документы, использующие механизмы архивации, чтобы хранить данные документа.
Необходимо рассмотреть следующие проблемы об архивации:
Если ключ будет отсутствовать в архиве, то просить его значения возвратится
nil
,NULL
,NO
, 0, или 0.0, в зависимости от типа, попросившего относительно. Тест для этого возвращаемого значения для сокращения данных, которые Вы выписываете. Кроме того, можно узнать, был ли ключ записан в архив.Оба кодировать и декодирует методы, могут сделать вещи гарантировать назад совместимость. Например, закодировать метод новой версии класса мог бы записать новые ключи использования значений, но может все еще выписать более старые поля так, чтобы более старые версии класса могли все еще понять объект. Кроме того, декодируйте методы, мог бы хотеть иметь дело с отсутствующими значениями некоторым разумным способом поддержать некоторую гибкость для будущих версий.
Рекомендуемое соглашение о присвоении имен для архивных ключей для классов платформы состоит в том, чтобы начаться с префикса, используемого для других элементов API платформы, и затем использовать имя переменной экземпляра. Просто удостоверьтесь, что имена не могут конфликтовать с именами никакого суперкласса или подкласса.
Если у Вас есть служебная функция, выписывающая тип исходных данных (другими словами, значение, которое не является объектом), несомненно, будут использовать уникальный ключ. Например, если у Вас есть «archiveRect» подпрограмма, архивирующая прямоугольник, должен взять ключевой аргумент и любое использование это; или, если это выписывает многократные значения (например, четыре плавания), это должно добавить свои собственные уникальные биты к предоставленному ключу.
Архивация битовых полей как есть может быть опасной вследствие зависимостей от порядка байтов и компилятора. Необходимо заархивировать их только, когда по причинам производительности много битов должно быть выписано много раз. Посмотрите Битовые поля для предложения.
Исключения и ошибки
Большинство методов платформы Какао не вынуждает разработчиков поймать и обработать исключения. Это вызвано тем, что исключения не повышены как нормальная часть выполнения и обычно не используются для передачи ожидаемого времени выполнения или пользовательских ошибок. Примеры этих ошибок включают:
Файл, не найденный
Никакой такой пользователь
Попытайтесь открыть неправильный тип документа в приложении
Ошибка в преобразовании строки к указанному кодированию
Однако Какао действительно повышает исключения для указания программирования или логических ошибок, таких как следующее:
Индекс массива за пределы
Попытка видоизменить неизменные объекты
Тип неверного аргумента
Ожидание состоит в том, что разработчик будет ловить эти виды ошибок во время тестирования и адресовать их прежде, чем поставить приложение; таким образом приложение не должно должно быть обрабатывать исключения во время выполнения. Если исключение повышено, и никакая часть приложения не ловит его, обработчик по умолчанию верхнего уровня обычно ловит и сообщает об исключении, и выполнение тогда продолжается. Разработчики могут принять решение заменить этого ловца исключения по умолчанию тем, предоставляющим более подробную информацию о том, что пошло не так, как надо и предлагает опцию сохранить данные и выйти из приложения.
Ошибки являются другой областью, где платформы Какао отличаются от некоторых других библиотек программного обеспечения. Методы какао обычно не возвращают коды ошибки. В случаях, где существует одна разумная или вероятная причина ошибки, методы полагаются на простой тест булевской переменной или объекта (nil
/non-nil
) возвращенное значение; причины a NO
или nil
возвращенное значение документируется. Вы не должны использовать коды ошибки для указания программных ошибок, которые будут обработаны во время выполнения, но вместо этого повышать исключения или в некоторых случаях просто регистрировать ошибку, не повышая исключение.
Например, NSDictionary
objectForKey:
метод или возвращает найденный объект или nil
если это не может найти объект. NSArray
objectAtIndex:
метод никогда не может возвращаться nil
(за исключением переопределяющего общего соглашения языка, что любое сообщение к nil
результаты в a nil
возвратитесь), потому что NSArray
объект не может сохранить nil
значения, и по определению любой за пределы доступ является программной ошибкой, которая должна привести к исключению. Многие init
возврат методов nil
когда объект не может быть инициализирован с предоставленными параметрами.
В небольшом количестве случаев, где метод имеет допустимую потребность в многократных отличных кодах ошибки, он должен указать их в параметре ссылкой, возвращающем или код ошибки, локализованную строку ошибки или некоторую другую информацию, описывающую ошибку. Например, Вы могли бы хотеть возвратить ошибку как NSError
объект; посмотрите на NSError.h
заголовочный файл в Основе для подробных данных. Этот параметр мог бы быть в дополнение к более простому BOOL
или nil
это непосредственно возвращается. Метод должен также наблюдать соглашение, что все параметры ссылкой являются дополнительными и таким образом позволяют отправителю передавать NULL
для параметра кода ошибки, если они не хотят знать об ошибке.
Данные платформы
То, как Вы обрабатываете данные платформы, имеет импликации для производительности, межплатформенной совместимости и других целей. В этом разделе рассматриваются методы, включающие данные платформы.
Постоянные данные
По причинам производительности хорошо отметить как постоянное как можно больше данных платформы, потому что выполнение так сокращает размер __DATA
сегмент Мужественного двоичного файла. Глобальные и статические данные, которые не являются const
заканчивается в __DATA
раздел __DATA
сегмент. Этот вид данных приводит память в рабочее состояние в каждом рабочем экземпляре приложения, использующего платформу. Несмотря на то, что дополнительные 500 байтов (например), не могли бы казаться настолько плохими, это могло бы вызвать инкремент в числе требуемых страниц — дополнительные четыре килобайта за приложение.
Необходимо отметить любые данные, которые являются постоянными как const
. Если существует нет char *
указатели в блоке, это заставит данные приземляться в __TEXT
сегмент (который делает его действительно постоянным); иначе это останется дома __DATA
сегмент, но не будет записан на (если предварительная привязка не будет сделана или нарушена при необходимости двигать двоичный файл во время загрузки).
Необходимо инициализировать статические переменные, чтобы гарантировать, что они объединяются в __data
раздел __DATA
сегмент в противоположность __bss
раздел. Если нет никакого очевидного значения для использования для инициализации, используйте 0, NULL
, 0.0, или независимо от того, что является надлежащим.
Битовые поля
Используя значения со знаком для битовых полей, особенно одноразрядных битовых полей, может привести к неопределенному поведению, если код предполагает, что значение является булевской переменной. Одноразрядные битовые поля должны всегда быть без знака. Поскольку единственные значения, которые могут быть сохранены в таком битовом поле, 0 и-1 (в зависимости от реализации компилятора), сравнивание этого битового поля к 1 является ложью. Например, если Вы сталкиваетесь с чем-то вроде этого в своем коде:
BOOL isAttachment:1; |
int startTracking:1; |
Необходимо изменить тип на unsigned int
.
Другая проблема с битовыми полями архивирует. В целом Вы не должны писать битовые поля в диск или архивы в форме, в которой они находятся, поскольку формат мог бы отличаться, когда они читаются снова на другой архитектуре, или на другом компиляторе.
Выделение памяти
В коде платформы лучший курс должен избежать выделять память в целом, если можно помочь ему. При необходимости во временном буфере по некоторым причинам обычно лучше использовать штабель, чем выделить буфер. Однако штабель ограничивается в размере (обычно 512 килобайтов в целом), таким образом, решение использовать штабель зависит от функции и размера буфера, Вам нужно. Обычно, если размер буфера составляет 1 000 байтов (или MAXPATHLEN
) или меньше, использование штабеля приемлемо.
Одно улучшение должно начаться с помощью штабеля, но переключиться на a malloc
’редактор буферизует, если требования размера идут вне размера буфера штабеля. Перечисление 2 представляет фрагмент кода, делающий просто что:
Выделение перечисления 2 с помощью и штабеля и буфера malloc’ed
#define STACKBUFSIZE (1000 / sizeof(YourElementType)) |
YourElementType stackBuffer[STACKBUFSIZE]; |
YourElementType *buf = stackBuffer; |
int capacity = STACKBUFSIZE; // In terms of YourElementType |
int numElements = 0; // In terms of YourElementType |
while (1) { |
if (numElements > capacity) { // Need more room |
int newCapacity = capacity * 2; // Or whatever your growth algorithm is |
if (buf == stackBuffer) { // Previously using stack; switch to allocated memory |
buf = malloc(newCapacity * sizeof(YourElementType)); |
memmove(buf, stackBuffer, capacity * sizeof(YourElementType)); |
} else { // Was already using malloc; simply realloc |
buf = realloc(buf, newCapacity * sizeof(YourElementType)); |
} |
capacity = newCapacity; |
} |
// ... use buf; increment numElements ... |
} |
// ... |
if (buf != stackBuffer) free(buf); |
Объектное сравнение
Необходимо знать о важном различии между методом сравнения родового объекта isEqual:
и методы сравнения, связанные с типом объекта, такой как isEqualToString:
. isEqual:
метод позволяет Вам передавать произвольные объекты как параметры и возвраты NO
если объекты не имеют того же класса. Методы такой как isEqualToString:
и isEqualToArray:
обычно предполагайте, что параметр имеет указанный тип (который является тем из получателя). Они поэтому не выполняют проверку типа, и следовательно они быстрее, но не как безопасные. Для значений, полученных из внешних источников, таких как информационный список свойств приложения (Info.plist
) или предпочтения, использование isEqual:
предпочтен, потому что это более безопасно; когда типы будут известны, использовать isEqualToString:
вместо этого.
Дальнейшая точка о isEqual:
его соединение с hash
метод. Один основной инвариант для объектов, помещающихся в основанный на хеше набор Какао такой как NSDictionary
или NSSet
это если [A isEqual:B] == YES
, тогда [A hash] == [B hash]
. Таким образом, если Вы переопределяете isEqual:
в Вашем классе необходимо также переопределить hash
сохранить этот инвариант. По умолчанию isEqual:
ищет равенство указателя адреса каждого объекта, и hash
возвращает значение хэш-функции на основе адреса каждого объекта, таким образом, этот инвариант содержит.