Каковы лучшие практики, которые вы используете при написании Objective-C и какао? [закрыто]

346

Я знаю о HIG (что очень удобно!), Но какие методы программирования вы используете при написании Objective-C и, более конкретно, при использовании Cocoa (или CocoaTouch).

пикселей
источник
смотрите этот пост в блоге, очень приятно. ironwolf.dangerousgames.com/blog/archives/913
user392412

Ответы:

398

Я начал делать несколько вещей, которые я не считаю стандартными:

1) С появлением свойств я больше не использую «_» для префикса «закрытых» переменных класса. В конце концов, если переменная может быть доступна другим классам, не должно ли быть свойство для нее? Мне всегда не нравился префикс "_" для того, чтобы сделать код более уродливым, и теперь я могу его опустить.

2) Говоря о личных вещах, я предпочитаю размещать определения частных методов в файле .m в расширении класса следующим образом:

#import "MyClass.h"

@interface MyClass ()
- (void) someMethod;
- (void) someOtherMethod;
@end

@implementation MyClass

Почему беспорядок в файле .h с вещами посторонним не должен волновать? Empty () работает для закрытых категорий в файле .m и выдает предупреждения компиляции, если вы не реализуете объявленные методы.

3) Я решил поместить dealloc в верхнюю часть файла .m, чуть ниже директив @synthesize. Разве то, что вы делаете, не должно быть на вершине списка вещей, о которых вы хотите думать в классе? Это особенно верно в такой среде, как iPhone.

3.5) В ячейках таблицы сделайте каждый элемент (включая саму ячейку) непрозрачным для производительности. Это означает, что нужно установить соответствующий цвет фона во всем.

3.6) При использовании NSURLConnection, как правило, вы можете захотеть реализовать метод делегата:

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
      return nil;
}

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

Также представляют интерес некоторые полезные советы для iPhone от Джозефа Маттиелло (полученные в списке рассылки iPhone). Есть и другие, но они были наиболее полезными, как мне показалось (обратите внимание, что несколько битов теперь немного отредактированы от оригинала, чтобы включить детали, предложенные в ответах):

4) Используйте двойную точность только при необходимости, например, при работе с CoreLocation. Удостоверьтесь, что вы заканчиваете свои константы в 'f', чтобы gcc сохранял их как float.

float val = someFloat * 2.2f;

Это особенно важно, когда на someFloatсамом деле может быть двойной, вам не нужна смешанная математика, так как вы теряете точность в 'val' при хранении. Хотя числа с плавающей запятой поддерживаются аппаратно на iPhone, для выполнения арифметики с двойной точностью может потребоваться больше времени, чем с одинарной точностью. Ссылки:

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

5) Установите ваши свойства как nonatomic. Они atomicпо умолчанию, и после синтеза будет создан код семафора, чтобы предотвратить проблемы многопоточности. 99% из вас, вероятно, не должны беспокоиться об этом, и код становится гораздо менее раздутым и более эффективным в использовании памяти, если установлен неатомарный.

6) SQLite может быть очень и очень быстрым способом кеширования больших наборов данных. Например, приложение карты может кэшировать свои плитки в файлы SQLite. Самая дорогая часть - это дисковый ввод-вывод. Избегайте множества маленьких записей, отправляя BEGIN;и COMMIT;между большими блоками. Например, мы используем 2-секундный таймер, который сбрасывается при каждой новой отправке. Когда он истекает, мы отправляем COMMIT; , что заставляет все ваши записи идти в один большой кусок. SQLite хранит данные транзакций на диске, и такое оборачивание начала / конца позволяет избежать создания множества файлов транзакций, группируя все транзакции в один файл.

Кроме того, SQL заблокирует ваш графический интерфейс, если он находится в вашем основном потоке. Если у вас очень длинный запрос, рекомендуется хранить ваши запросы в виде статических объектов и запускать SQL в отдельном потоке. Обязательно оберните все, что изменяет базу данных для строк запроса в @synchronize() {}блоках. Для коротких запросов просто оставьте вещи в главном потоке для удобства.

Дополнительные советы по оптимизации SQLite здесь, хотя документ выглядит устаревшим, многие из пунктов, вероятно, все еще хороши;

http://web.utk.edu/~jplyon/sqlite/SQLite_optimization_FAQ.html

Kendall Helmstetter Gelner
источник
3
Хороший отзыв о двойной арифметике.
Адам Эрнст
8
Расширения классов теперь являются предпочтительным способом для частных методов: developer.apple.com/Mac/library/documentation/Cocoa/Conceptual/…
Casebash
9
Ваш совет насчет двойников на iPhone устарел stackoverflow.com/questions/1622729/…
Casebash
3
Не устарела; Совершенно неверно: оригинальный iPhone поддерживает плавающие и удваивается в оборудовании примерно с той же скоростью. SQLite также не хранит транзакции в памяти; они записываются на диск. Только длинные запросы блокируют ваш пользовательский интерфейс; менее запутанно запускать все в главном потоке и использовать более быстрые запросы.
тк.
1
@tc: я исправил элемент SQL о транзакциях, обратите внимание, что я сам не писал эти последние четыре или около того элемента. Я также прояснил часть о переносе запросов в фоновом режиме только для очень длинных запросов (иногда вы просто не можете их сократить). Но назвать все это «неправильным» из-за нескольких моментов - я чувствую себя довольно экстремально. Кроме того, в ответе выше уже говорилось: «Предположительно, на старых телефонах вычисления выполняются с одинаковой скоростью», но обратите внимание на то, что большее количество регистров одинарной точности делает их по-прежнему предпочтительными.
Кендалл Хельмштеттер Гелнер
109

Не используйте неизвестные строки как строки формата

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

Например, при регистрации строк заманчиво передать строковую переменную в качестве единственного аргумента NSLog:

    NSString *aString = // get a string from somewhere;
    NSLog(aString);

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

    NSLog(@"%@", aString);
mmalc
источник
4
Я был укушен этим раньше.
Адам Эрнст
Это хороший совет для любого языка программирования
Том Фобер
107

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

Примеры того, что нужно делать, а что нет:

  • Не объявляйте id m_something;в интерфейсе объекта и не называйте его переменной или полем члена ; используйте somethingили _somethingдля его имени и назовите его переменной экземпляра .
  • Не называйте получателя -getSomething; правильное имя Какао просто -something.
  • Не называйте сеттера -something:; так должно быть-setSomething:
  • Имя метода перемежается с аргументами и включает двоеточия; это -[NSObject performSelector:withObject:]не NSObject::performSelector.
  • Используйте заглавные буквы (CamelCase) в именах методов, параметрах, переменных, именах классов и т. Д., А не в чертах (подчеркивания).
  • Имена классов начинаются с заглавной буквы, имена переменных и методов начинаются со строчных.

Что бы вы ни делали, не используйте венгерские обозначения в стиле Win16 / Win32. Даже Microsoft отказалась от этого, перейдя на платформу .NET.

Крис Хансон
источник
5
Я бы сказал, не используйте setSomething: / что-то вообще - вместо этого используйте свойства. На данный момент есть немного людей, которым действительно нужно нацелиться на Тигра (единственная причина не использовать свойства)
Кендалл Хелмштеттер Гелнер
18
Свойства по-прежнему генерируют методы доступа для вас, а атрибуты getter = / setter = для свойства позволяют указывать имена методов. Кроме того, вы можете использовать синтаксис [foothing] вместо синтаксиса foo.something со свойствами. Таким образом, присвоение имен по-прежнему актуально.
Крис Хэнсон
3
Это отличный справочник для тех, кто приходит из C ++, где я делал большинство вещей, против которых вы советуетесь.
Клинтон Блэкмор
4
Сеттер не должен вызывать сохранение чего-либо в базе данных. Есть причина, по которой в Core Data есть метод -save: для NSManagedObjectContext вместо того, чтобы сеттеры генерировали немедленные обновления.
Крис Хэнсон
2
Я сомневаюсь, что это не вариант, однако, возможно, потребовалось пересмотреть архитектуру вашего приложения. (Для ясности: я не говорю «Вы должны были использовать базовые данные». Я говорю «Сеттеры не должны сохранять в базу данных».) Наличие контекста для управления графом объектов, а не сохранение отдельных объектов в нем. , практически всегда и возможно, и лучшее решение.
Крис Хэнсон
106

IBOutlets

Исторически сложилось, что управление памятью торговых точек было плохим. В настоящее время рекомендуется объявлять торговые точки как свойства:

@interface MyClass :NSObject {
    NSTextField *textField;
}
@property (nonatomic, retain) IBOutlet NSTextField *textField;
@end

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

оборота ммальк
источник
1
не будет ли загрузка пера удерживать его дважды? (один раз в кончике, второй при назначении собственности). Должен ли я выпустить их в Deloloc?
Корнель
6
Вы должны обнулить выходы в viewDidUnload (iPhone OS 3.0+) или в пользовательском методе setView: чтобы избежать утечек. Очевидно, вы должны также выпустить в dealloc.
Франк Щерба
2
Имейте в виду, что не все согласны с этим стилем: weblog.bignerdranch.com/?p=95
Майкл
Так Apple тоже работает. «Начало разработки iPhone 3» упоминает об этом изменении по сравнению с предыдущими версиями.
ustun
Я упомянул об этом в другом комментарии, но должен был разместить его здесь: как только динамический синтез ivar начнет происходить для приложений iOS (если / когда?), Вы будете рады, что поместили IBOutlet в свойство вместо ivar!
Джо д'Андреа
97

Используйте статический анализатор LLVM / Clang

ПРИМЕЧАНИЕ. В Xcode 4 это теперь встроено в IDE.

Вы используете Clang Static Analyzer, что неудивительно, для анализа кода C и Objective-C (пока нет C ++) в Mac OS X 10.5. Установить и использовать тривиально:

  1. Загрузите последнюю версию с этой страницы .
  2. Из командной строки, cdв каталог вашего проекта.
  3. Выполнить scan-build -k -V xcodebuild.

(Существуют некоторые дополнительные ограничения и т. Д., В частности, вам следует проанализировать проект в его конфигурации «Отладка» - подробности см. Http://clang.llvm.org/StaticAnalysisUsage.html), но это более или менее к чему это сводится.)

Затем анализатор создает для вас набор веб-страниц, на которых показано вероятное управление памятью и другие основные проблемы, которые компилятор не может обнаружить.

mmalc
источник
1
У меня были некоторые проблемы с тем, чтобы заставить это работать, пока я не следовал этим инструкциям: oiledmachine.com/posts/2009/01/06/…
bbrown
15
В XCode 3.2.1 на Snow Leopard он уже встроен. Вы можете запустить его вручную, используя Run -> Build and Analyze , или включить его для всех сборок с помощью параметра сборки «Run Static Analyzer». Обратите внимание, что этот инструмент в настоящее время поддерживает только C и Objective-C, но не C ++ / Objective-C ++.
oefe
94

Это тонкий, но удобный. Если вы передаете себя как делегат другому объекту, сбросьте делегат этого объекта перед вами dealloc.

- (void)dealloc
{
self.someObject.delegate = NULL;
self.someObject = NULL;
//
[super dealloc];
}

Делая это, вы гарантируете, что больше методов делегата не будут отправлены. По мере того, как вы собираетесь deallocисчезнуть в эфире, вы хотите убедиться, что ничто не сможет послать вам больше сообщений случайно. Помните, что self.someObject может быть сохранен другим объектом (это может быть одноэлементный объект или пул автоматического выпуска или что-то еще), и пока вы не скажете ему «прекратить посылать мне сообщения!», Он думает, что ваш объект «только для того, чтобы быть освобожденным» это честная игра.

Попадание в эту привычку избавит вас от множества странных сбоев, которые затрудняют отладку.

Тот же принцип применим и к наблюдению значения ключа, и к NSNotifications.

Редактировать:

Еще более оборонительные изменения

self.someObject.delegate = NULL;

в:

if (self.someObject.delegate == self)
    self.someObject.delegate = NULL;
Schwa
источник
8
В этом нет ничего тонкого, в документации четко сказано, что вы обязаны это сделать. От Memory Management Programming Guide for Cocoa: Additional cases of weak references in Cocoa include, but are not restricted to, table data sources, outline view items, notification observers, and miscellaneous targets and delegates. In most cases, the weak-referenced object is aware of the other object’s weak reference to it, as is the case for circular references, and is responsible for notifying the other object when it deallocates.
Джон
Лучше использовать nil вместо NULL, потому что NULL не освободит память.
Навин Шан
@NaveenShan nil == NULL. Они точно такие же, за исключением того, что nilесть idи NULLесть void *. Ваше утверждение не соответствует действительности.
@WTP да, nil == NULL, но использование nil, безусловно, является предпочтительным способом, если вы просматриваете фрагменты кода с примерами яблок, они везде используют nil, и, как вы сказали, nil - это id, что делает его предпочтительным по сравнению с void * в случаях, когда вы отправляете идентификаторы, то есть.
Ахти
1
@ Ахти точно, и Nil(в верхнем регистре) имеет тип Class*. Даже при том, что они все равны, использование неправильного может привести к неприятным небольшим ошибкам, особенно в Objective-C ++.
86

@kendell

Вместо того:

@interface MyClass (private)
- (void) someMethod
- (void) someOtherMethod
@end

Использование:

@interface MyClass ()
- (void) someMethod
- (void) someOtherMethod
@end

Новое в Objective-C 2.0.

Расширения классов описаны в Apple Objective-C 2.0 Reference.

«Расширения классов позволяют объявлять дополнительный необходимый API для класса в местах, отличных от основного блока @interface class»

Таким образом, они являются частью реального класса - и НЕ (частной) категории в дополнение к классу. Тонкое, но важное отличие.

schwa
источник
Вы могли бы сделать это, но мне нравится явно обозначать его как «частный» раздел (больше документации, чем функциональный), хотя, конечно, это уже достаточно очевидно из того, что он находится в файле .m ...
Кендалл Хельмштеттер Гелнер
2
За исключение там есть разница между частными категориями и расширениями класса: «Расширение класса позволяет объявить дополнительный необходимый API для одного класса в других , чем в блоке основного класса @interface мест, как показано в следующем примере:» Смотрите ссылку на редактировании.
Шва
Я согласен, что есть разница, когда компилятор будет предупреждать вас, когда вы не внедрили методы CE, - но я не считаю этот аспект очень важным, когда все методы находятся в одном файле и все закрытые. Я по-прежнему предпочитаю аспект
удобства обслуживания при пометке
3
Я действительно не вижу (Личное) более приемлемым, чем (). Если вы так обеспокоены, то может помочь хорошая доза комментариев. Но, очевидно, живи и дай жить другим. YMMV и т.д.
Schwa
17
()Вместо (Private)(или какого-либо другого имени категории) есть довольно важное преимущество : вы можете повторно объявить свойства как readwrite, в то время как для публики они доступны только для чтения. :)
Паскаль
75

Избегайте автоматического выпуска

Поскольку вы обычно (1) не имеете прямого контроля над их временем жизни, автоматически выпущенные объекты могут сохраняться в течение сравнительно долгого времени и излишне увеличивать объем памяти вашего приложения. Хотя на настольном компьютере это может не иметь большого значения, на более ограниченных платформах это может быть серьезной проблемой. Поэтому на всех платформах, и особенно на более ограниченных платформах, рекомендуется избегать использования методов, которые могут привести к автоматическому освобождению объектов, и вместо этого рекомендуется использовать шаблон alloc / init.

Таким образом, а не:

aVariable = [AClass convenienceMethod];

где возможно, вы должны вместо этого использовать:

aVariable = [[AClass alloc] init];
// do things with aVariable
[aVariable release];

Когда вы пишете свои собственные методы, которые возвращают вновь созданный объект, вы можете воспользоваться соглашением об именах Cocoa, чтобы указать получателю, что он должен быть освобожден, добавив имя метода с помощью «new».

Таким образом, вместо:

- (MyClass *)convenienceMethod {
    MyClass *instance = [[[self alloc] init] autorelease];
    // configure instance
    return instance;
}

Вы могли бы написать:

- (MyClass *)newInstance {
    MyClass *instance = [[self alloc] init];
    // configure instance
    return instance;
}

Поскольку имя метода начинается с «new», потребители вашего API знают, что они ответственны за освобождение полученного объекта (см., Например, метод NSObjectControllernewObject ).

(1) Вы можете получить контроль, используя свои собственные локальные пулы автоматического выпуска. Для получения дополнительной информации об этом см. Автозапуск пулов .

mmalc
источник
6
Я считаю , что преимущества не используя autorelease перевешивают затраты (т.е. больше ошибок утечки памяти). В любом случае код в главном потоке должен быть довольно коротким (иначе вы заморозите пользовательский интерфейс), а для более продолжительного, интенсивно использующего память фонового кода вы всегда можете обернуть участки, интенсивно использующие память, в локальные пулы автоматического выпуска.
Адиб
56
Я не согласен. Вы должны использовать автоматически выпущенные объекты, когда это возможно. Если они увеличивают объем памяти, вы должны использовать другой NSAutoreleasePool. Но только после того, как вы подтвердили, что это действительно проблема. Преждевременная оптимизация и все такое ...
Свен
3
Я провожу менее 40 сек. день набирал [someObject release] и читал «лишнюю строку» при создании экземпляра нового объекта, но однажды я прожег 17 часов, чтобы найти ошибку автоматического выпуска, которая появлялась только в особых случаях и не давала последовательной ошибки в консоли. Поэтому я согласен с Адибом, когда он говорит: «Я считаю, что преимущества от неиспользования автоматического выпуска превышают его стоимость».
RickiG
7
Я согласен со Свеном. Основная цель должна заключаться в ясности кода и снижении количества ошибок кодирования, а оптимизация памяти - только там, где это необходимо. Распечатка [[Foo alloc] init] autorelease] выполняется быстро, и вы сразу же сталкиваетесь с проблемой освобождения этого нового объекта. При чтении кода вам не нужно искать соответствующий релиз, чтобы убедиться, что он не пропущен.
Майк Веллер
3
Жизненный цикл автоматически выпущенных объектов четко определен и определяется на достаточном уровне.
Eonil
69

Некоторые из них уже упоминались, но вот что я могу придумать:

  • Следуйте правилам именования KVO. Даже если вы не используете KVO сейчас, по моему опыту часто это все еще полезно в будущем. И если вы используете KVO или привязки, вы должны знать, что все будет работать так, как они должны. Это относится не только к методам доступа и переменным экземпляра, но и ко многим отношениям, валидации, авто-уведомлению зависимых ключей и так далее.
  • Поместите частные методы в категорию. Не только интерфейс, но и реализация. Хорошо иметь концептуальную дистанцию ​​между частными и не приватными методами. Я включаю все в мой файл .m.
  • Поместите фоновые методы потока в категорию. То же, что и выше. Я обнаружил, что хорошо сохранять четкий концептуальный барьер, когда вы думаете о том, что находится в главном потоке, а что нет.
  • Использование #pragma mark [section]. Обычно я группирую свои собственные методы, переопределения каждого подкласса и любую информацию или формальные протоколы. Это значительно облегчает переход к тому, что я ищу. По той же теме, сгруппируйте сходные методы (например, методы делегатов табличного представления) вместе, а не просто их где-то склеивайте.
  • Префикс частных методов & ivars с _. Мне нравится, как это выглядит, и я реже использую ивар, когда имею в виду недвижимость случайно.
  • Не используйте методы / свойства мутатора в init & dealloc. У меня никогда не было ничего плохого из-за этого, но я вижу логику, если вы измените метод, чтобы сделать что-то, что зависит от состояния вашего объекта.
  • Положите IBOutlets в свойствах. Я на самом деле только что прочитал это здесь, но я собираюсь начать делать это. Независимо от каких-либо преимуществ памяти, это кажется лучше стилистически (по крайней мере для меня).
  • Избегайте написания кода, который вам совершенно не нужен. Это действительно охватывает много вещей, таких как создание иваров по #defineжеланию или кэширование массива вместо сортировки каждый раз, когда нужны данные. Я могу многое сказать по этому поводу, но суть в том, что не пишите код, пока он вам не понадобится, или профилировщик не скажет вам об этом. Это делает вещи намного легче поддерживать в долгосрочной перспективе.
  • Завершите то, что вы начали. Имея много недоделанного, глючного кода - это самый быстрый способ убить мертвый проект. Если вам нужен метод-заглушка, который подойдет, просто укажите его, вставив NSLog( @"stub" )внутрь, или как хотите, чтобы отслеживать вещи.
Марк Шарбонно
источник
3
Я бы посоветовал вам поместить приватные методы в продолжение класса. (т.е. @interface MyClass () ... @end в вашем .m)
Джейсон Медейрос
3
Вместо #PRAGMA вы можете использовать комментарий // Mark: [Section], который более переносим и работает идентично.
aleemb
Если нет специального синтаксиса, который я пропускаю, // Mark: не добавляет метку в выпадающее меню функций Xcode, что на самом деле является половиной причины его использования.
Марк Шарбонно
6
Вам нужно использовать заглавные буквы "// MARK: ...", чтобы они отображались в раскрывающемся списке.
Rhult
3
Что касается Finish what you startвы также можете использовать // TODO:для маркировки кода для завершения, который будет отображаться в раскрывающемся списке.
iwasrobbed
56

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

У вас также нет видимости открытых и защищенных и закрытых методов, мешающих написанию тестов для ваших внутренних объектов.

Крис Хансон
источник
Какие тестовые рамки вы рекомендуете?
Мельфар
13
Xcode включает в себя OCUnit, инфраструктуру модульного тестирования Objective-C и поддержку для запуска пакетов модульных тестов в рамках процесса сборки.
Крис Хансон
55

Золотое правило: если ты, allocто ты release!

ОБНОВЛЕНИЕ: если вы не используете ARC

logancautrell
источник
26
Кроме того, если вы copy, mutableCopy, newили retain.
Свен
54

Не пишите Objective-C, как если бы это был Java / C # / C ++ / и т. Д.

Однажды я видел, как команда разработчиков веб-приложений на Java EE пыталась написать настольное приложение Cocoa. Как будто это было веб-приложение Java EE. Было много AbstractFooFactory и FooFactory, а также IFoo и Foo, которые летали, когда все, что им действительно нужно, это класс Foo и, возможно, протокол Fooable.

Частью обеспечения того, что вы этого не делаете, является истинное понимание различий в языке. Например, вам не нужны абстрактные фабрика и фабричные классы, описанные выше, потому что методы класса Objective C отправляются так же динамически, как методы экземпляра, и могут быть переопределены в подклассах.

Крис Хансон
источник
10
Как Java-разработчик, который написал абстрактную фабрику в Objective-C, я нахожу это интригующим. Не могли бы вы объяснить немного больше, как это работает - возможно, на примере?
Чайник
2
Вы все еще верите, что нам не нужны абстрактные фабричные классы после того, как вы оставили этот ответ?
kirk.burleson
50

Убедитесь, что вы добавили в закладки страницу « Отладка магии» . Это должно быть вашей первой остановкой, когда вы будете биться головой о стену, пытаясь найти источник ошибки Какао.

Например, он расскажет вам, как найти метод, в котором вы сначала выделили память, которая позже вызывает сбои (например, во время завершения приложения).

mj1531
источник
1
Теперь есть специальная версия страницы отладки Magic для iOS .
Джиту
38

Старайтесь избегать того, что я сейчас решил назвать Newbiecategoryaholism. Когда новички в Objective-C обнаруживают категории, они часто становятся дикими, добавляя полезные маленькие категории к каждому существующему классу ( «Что? Я могу добавить метод для преобразования числа в римские цифры в NSNumber»! ).

Не делай этого.

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

Большую часть времени, когда вы действительно думаете, что вам нужен метод категории, чтобы помочь упростить некоторый код, вы обнаружите, что вы никогда не будете использовать метод повторно.

Есть и другие опасности, если только вы не используете пространство имен для методов вашей категории (и кто, кроме совершенно безумного ддрибина?), Есть вероятность, что Apple, или плагин, или что-то еще, работающее в вашем адресном пространстве, также определит ту же категорию. метод с тем же именем с немного другим побочным эффектом ....

ХОРОШО. Теперь, когда вы были предупреждены, игнорируйте «не делайте эту часть». Но проявляйте крайнюю сдержанность.

schwa
источник
Мне нравится ваш ответ, мой совет не будет использовать категорию для хранения служебного кода, если вы не собираетесь копировать некоторый код в более чем одном месте, и код явно принадлежит классу, который вы собираетесь классифицировать ...
Кендалл Хелмштеттер Гелнер
Я просто хотел бы передать и озвучить свою поддержку методов категории пространства имен. Это просто кажется правильным.
Майкл Бакли
+1 если только для римских цифр. Я бы полностью это сделал!
Брайан Постоу
14
Контрапункт: в течение последних полутора лет я придерживался совершенно противоположной политики: «Если это можно реализовать в категории, сделайте это». В результате мой код становится намного более кратким, более выразительным и более легким для чтения, чем подробный пример кода, который предоставляет Apple. Я потерял в общей сложности около 10 минут из-за одного конфликта пространства имен и, вероятно, выиграл человеко-месяцы благодаря эффективности, которую я создал для себя. Каждому свое, но я принял эту политику, зная риски, и я очень рад, что сделал.
cduhn
7
Я не согласна Если это будет функция и она применяется к объекту Foundation, и вы можете придумать хорошее имя, вставьте его в категорию. Ваш код будет более читабельным. Я думаю, что действительно важный момент здесь: делать все в меру.
mxcl
37

Сопротивляйтесь подклассификации мира. В Какао многое делается с помощью делегирования и использования базовой среды выполнения, которая в других средах выполняется с помощью подклассов.

Например, в Java вы часто используете экземпляры анонимных *Listenerподклассов, а в .NET вы часто используете ваши EventArgsподклассы. В Какао вы тоже этого не делаете - вместо этого используется цель-действие.

Крис Хансон
источник
6
В противном случае известен как «Композиция над наследством».
Эндрю Эблинг
37

Сортировка строк по желанию пользователя

Когда вы сортируете строки для представления пользователю, вы не должны использовать простой compare:метод. Вместо этого вы всегда должны использовать локализованные методы сравнения, такие как localizedCompare:илиlocalizedCaseInsensitiveCompare: .

Для получения дополнительной информации см. Поиск, сравнение и сортировка строк .

mmalc
источник
31

Объявленные свойства

Обычно вы должны использовать функцию объявленных свойств Objective-C 2.0 для всех ваших свойств. Если они не являются публичными, добавьте их в расширение класса. Использование объявленных свойств позволяет сразу же очистить семантику управления памятью и упростить вам проверку вашего метода dealloc - если вы сгруппируете объявления своих свойств вместе, вы можете быстро отсканировать их и сравнить с реализацией вашего метода dealloc.

Вы должны хорошо подумать, прежде чем помечать свойства как «неатомные». Как отмечается в Руководстве по языку программирования Objective C , свойства по умолчанию являются атомарными и требуют значительных затрат. Более того, простое превращение всех ваших свойств в атомарные не делает ваше приложение поточно-ориентированным. Также обратите внимание, что, если вы не указываете «неатомные» и реализуете свои собственные методы доступа (а не синтезируете их), вы должны реализовать их атомарным способом.

mmalc
источник
26

Подумайте о нулевых значениях

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

оборота ммальк
источник
У меня есть: #define SXRelease(o); o = nilи то же самое для CFReleaseи free. Это все упрощает.
26

Используйте NSAssert и друзей. Я все время использую nil в качестве допустимого объекта ... особенно отправка сообщений в nil совершенно допустима в Obj-C. Однако, если я действительно хочу убедиться в состоянии переменной, я использую NSAssert и NSParameterAssert, которые помогают легко отследить проблемы.

NikWest
источник
1
Более подробная информация здесь: developer.apple.com/mac/library/documentation/Cocoa/Reference/… А здесь: stackoverflow.com/questions/2521275/what-is-nsparameterassert
Дейв Галлахер
23

Простой, но часто забытый. Согласно спецификации:

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

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

@interface FooInt:NSObject{}
-(int) print;
@end

@implementation FooInt
-(int) print{
    return 5;
}
@end

@interface FooFloat:NSObject{}
-(float) print;
@end

@implementation FooFloat
-(float) print{
    return 3.3;
}
@end

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

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];    
    id f1=[[FooFloat alloc]init];
    //prints 0, runtime considers [f1 print] to return int, as f1's type is "id" and FooInt precedes FooBar
    NSLog(@"%f",[f1 print]);

    FooFloat* f2=[[FooFloat alloc]init];
    //prints 3.3 expectedly as the static type is FooFloat
    NSLog(@"%f",[f2 print]);

    [f1 release];
    [f2 release]
    [pool drain];

    return 0;
}   
Comptrol
источник
это легко забыть. Важный тем не менее
Брок Вульф
3
Это только проблема при воздержании от статической типизации. Если компилятор знает тип, аргумент и возвращаемый тип могут отличаться без проблем. Лично я считаю, что это не часто бывает проблемой. У Apple также есть много методов, которые имеют одинаковые имена, но различаются по типам возвращаемых данных. Наконец, есть флаг компилятора, чтобы предупредить вас в неоднозначных случаях.
Николай Рюэ
Если мы будем следовать директиве Apple об именовании, такой ситуации не будет :)
Eonil
22

Если вы используете Leopard (Mac OS X 10.5) или более позднюю версию, вы можете использовать приложение Instruments для поиска и отслеживания утечек памяти. После сборки вашей программы в XCode выберите «Выполнить»> «Начать с Performance Tool»> «Утечки».

Даже если ваше приложение не показывает утечек, вы можете хранить объекты слишком долго. В Инструментах вы можете использовать инструмент ObjectAlloc для этого. Выберите инструмент ObjectAlloc в документе «Инструменты» и откройте сведения об инструменте (если он еще не отображается), выбрав «Вид»> «Детали» (рядом с ним должна стоять галочка). В разделе «Срок действия выделения» в подробностях ObjectAlloc убедитесь, что вы выбрали переключатель рядом с «Создан и еще жив».

Теперь, когда вы прекратите запись своего приложения, выбор инструмента ObjectAlloc покажет вам, сколько ссылок есть на каждый еще живущий объект в вашем приложении в столбце "# Net". Убедитесь, что вы смотрите не только на свои собственные классы, но и на классы объектов верхнего уровня ваших файлов NIB. Например, если у вас нет окон на экране, и вы видите ссылки на еще живое NSWindow, возможно, вы не выпустили его в своем коде.

mj1531
источник
21

Убирайся в деилок.

Это одна из самых простых вещей, которую нужно забыть - особенно. при кодировании на 150 миль в час. Всегда, всегда, всегда очищайте ваши атрибуты / переменные-члены в dealloc.

Мне нравится использовать атрибуты Objc 2 - с новой нотацией точек - так что это делает очистку безболезненной. Часто так просто, как:

- (void)dealloc
{
    self.someAttribute = NULL;
    [super dealloc];
}

Это позаботится о выпуске и установит для атрибута значение NULL (что я считаю защитным программированием - в случае, если другой метод, расположенный ниже в dealloc, снова обращается к переменной-члену - редко, но может случиться).

С включенным в 10.5 GC это больше не нужно, но вам все равно может понадобиться очистить другие ресурсы, которые вы создаете, вы можете сделать это в методе finalize.

schwa
источник
12
В общем, вы не должны использовать методы доступа в dealloc (или init).
Mmalc
1
Помимо соображений производительности (средства доступа немного медленнее, чем прямой доступ), почему я не должен использовать средства доступа в dealloc или init?
Schwa
1
(a) Причины производительности совершенно адекватны сами по себе (особенно, если ваши аксессуары атомарные). (b) Вам следует избегать любых побочных эффектов, которые могут иметь средства доступа. Последнее особенно важно, если ваш класс может быть подклассом.
Mmalc
3
Я отмечу, что если вы работаете в современной среде выполнения с синтезированными иварами, вы должны использовать средства доступа в dealloc. Большая часть современного кода выполнения - это GC, но не все.
Луи Гербарг
1
Более расширенное представление о том, использовать или нет методы доступа / методы доступа -initи -deallocметоды, можно найти здесь: mikeash.com/?page=pyblog/…
Йохан Кул
17

Все эти комментарии великолепны, но я действительно удивлен, что никто не упомянул руководство по стилю Google Objective-C, которое было опубликовано некоторое время назад. Я думаю, что они проделали очень тщательную работу.

SLF
источник
7
Хм, первый пример уже полон дерьма. Никогда не документируйте языковые идиомы. Если бы я нашел такие комментарии в заголовочном файле, я бы не стал читать дальше.
Стефан Эггермонт
5
О, мои глаза !!!!! Я не могу поверить в то, что я видел.
Eonil
13

Не забывайте, что NSWindowController и NSViewController будут освобождать объекты верхнего уровня файлов NIB, которыми они управляют.

Если вы вручную загружаете файл NIB, вы несете ответственность за освобождение объектов верхнего уровня NIB, когда вы закончите с ними.

mj1531
источник
12

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

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

iWasRobbed
источник
Вы даже можете установить Tab для отступа, а затем сделать Cmd-A и Tab.
Plumenator
10

Я знаю, что упустил это из виду, когда впервые попал в программирование Какао.

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

mj1531
источник
6
Это неправда. Ответственность за освобождение объектов верхнего уровня зависит от того, от какого класса вы наследуетесь и какую платформу используете. См. Developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/… среди других.
mmalc
10

Включите все предупреждения GCC, а затем отключите те, которые регулярно вызываются заголовками Apple, для уменьшения шума.

Также часто запускайте статический анализ Clang; Вы можете включить его для всех сборок с помощью параметра сборки «Запустить статический анализатор».

Напишите модульные тесты и запускайте их с каждой сборкой.

oefe
источник
И, если возможно, включите «Обрабатывать предупреждения как ошибки». Не позволяйте предупреждению существовать.
Питер Хоси
2
Удобный скрипт для настройки вашего проекта с рекомендуемыми предупреждениями доступен здесь: rentzsch.tumblr.com/post/237349423/hoseyifyxcodewarnings-scpt
Йохан Кул
10

Переменные и свойства

1 / Поддержание чистоты ваших заголовков, скрытие реализации
Не включайте переменные экземпляра в заголовок. Закрытые переменные помещаются в продолжение класса как свойства. Публичные переменные объявляются как открытые свойства в вашем заголовке. Если это должно быть только чтение, объявите это как только для чтения и переписайте это как readwrite в продолжении класса. В основном я не использую переменные вообще, только свойства.

2 / Дайте вашим свойствам имя переменной не по умолчанию, например:


@synthesize property = property_;

Причина 1: вы будете ловить ошибки, вызванные забыванием «я». при назначении имущества. Причина 2: из моих экспериментов, Leak Analyzer в инструментах имеет проблемы с обнаружением утечки свойства с именем по умолчанию.

3 / Никогда не используйте retain или release непосредственно в свойствах (или только в очень исключительных ситуациях). В вашем коллеже просто присвойте им ноль. Свойства сохранения предназначены для обработки сохранения / освобождения сами по себе. Вы никогда не знаете, если сеттер не добавляет, например, наблюдателей. Вы должны использовать переменную непосредственно только внутри ее установщика и получателя.

Просмотры

1 / Поместите каждое определение представления в xib, если можете (исключение обычно составляют параметры динамического содержимого и слоя). Это экономит время (это проще, чем написание кода), его легко изменить, и он сохраняет ваш код в чистоте.

2 / Не пытайтесь оптимизировать просмотры, уменьшая количество просмотров. Не создавайте UIImageView в вашем коде вместо xib только потому, что вы хотите добавить в него подпредставления. Вместо этого используйте UIImageView в качестве фона. Каркас представления может обрабатывать сотни представлений без проблем.

3 / IBOutlets не всегда должны быть сохранены (или сильны). Обратите внимание, что большинство ваших IBOutlets являются частью вашей иерархии представлений и, следовательно, неявно сохраняются.

4 / Отпустить все IBOutlets в viewDidUnload

5 / Вызовите viewDidUnload из вашего метода dealloc. Это не косвенно называется.

объем памяти

1 / Автоселение объектов при их создании. Многие ошибки вызваны перемещением вашего вызова освобождения в одну ветвь if-else или после оператора return. Release вместо autorelease следует использовать только в исключительных ситуациях - например, когда вы ожидаете цикл запуска и не хотите, чтобы ваш объект был автоматически освобожден слишком рано.

2 / Даже если вы используете автоматический подсчет ссылок, вы должны прекрасно понимать, как работают методы фиксированного выпуска. Использование retain-release вручную не сложнее, чем ARC, в обоих случаях вам приходится иметь дело с утечками и циклами сохранения. Подумайте об использовании retain-release вручную для больших проектов или сложных иерархий объектов.

Комментарии

1 / Сделайте ваш код с автодокументацией. Каждое имя переменной и имя метода должны сообщать, что они делают. Если код написан правильно (вам нужно много практиковаться в этом), вам не понадобятся комментарии к коду (не то же самое, что комментарии к документации). Алгоритмы могут быть сложными, но код должен быть всегда простым.

2 / Иногда вам понадобится комментарий. Обычно для описания неочевидного поведения кода или взлома. Если вы чувствуете, что должны написать комментарий, сначала попробуйте переписать код, чтобы он был проще и без комментариев.

вдавливание

1 / Не увеличивайте отступ слишком сильно. Большая часть кода вашего метода должна иметь отступ на уровне метода. Вложенные блоки (если и т. Д.) Снижают читабельность. Если у вас есть три вложенных блока, вы должны попытаться поместить внутренние блоки в отдельный метод. Четыре или более вложенных блока никогда не должны использоваться. Если большая часть кода вашего метода находится внутри условия if, отмените условие if, например:


if (self) {
   //... long initialization code ...
}

return self;

if (!self) {
   return nil;
}

//... long initialization code ...

return self;

Понимать код C, в основном структуры C

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


view.frame = CGRectMake(view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height + 20);

такой же как:


CGRect frame = view.frame;
frame.size.height += 20;
view.frame = frame;

И многое другое

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

В настоящее время наши стандарты кодирования содержат около 20 страниц, смесь стандартов кодирования Java, стандартов Google Obj-C / C ++ и собственных дополнений. Документируйте свой код, используйте стандартные стандартные отступы, пробелы и пустые строки в нужных местах и ​​т. Д.

оборота Sulthan
источник
9

Будь более функциональным .

Objective-C является объектно-ориентированным языком, но в функциональном стиле фреймворка Cocoa и во многих случаях разработан функциональный стиль.

  1. Существует разделение изменчивости. Используйте неизменяемые классы в качестве основного и изменяемый объект в качестве дополнительного. Например, используйте в первую очередь NSArray, и используйте NSMutableArray только тогда, когда вам нужно.

  2. Есть чистые функции. Не так много, покупайте многие из API-интерфейсов, разработанных как чистая функция. Посмотрите на такие функции, как CGRectMake()или CGAffineTransformMake(). Очевидно, форма указателя выглядит более эффективно. Однако косвенный аргумент с указателями не может предложить без побочных эффектов. Проектирование конструкций чисто как можно больше. Отделяйте даже государственные объекты. Используйте -copyвместо -retainпередачи значения другому объекту. Потому что разделяемое состояние может молча влиять на изменение значения в другом объекте. Так что не может быть без побочных эффектов. Если у вас есть значение из внешнего объекта, скопируйте его. Поэтому также важно, чтобы общее состояние было минимальным.

Однако не бойтесь использовать нечистые функции тоже.

  1. Есть ленивая оценка. Смотрите что-то вроде -[UIViewController view]собственности. Представление не будет создано при создании объекта. Он будет создан при чтении viewсвойства вызывающего абонента в первый раз. UIImageне будет загружен, пока не будет нарисован. Есть много реализаций, подобных этой конструкции. Подобные конструкции очень полезны для управления ресурсами, но если вы не знаете концепцию отложенной оценки, понять их поведение непросто.

  2. Есть закрытие. Используйте C-блоки как можно больше. Это значительно упростит вашу жизнь. Но прочитайте еще раз о блок-памяти управления, прежде чем использовать его.

  3. Есть полуавтоматический GC. NSAutoreleasePool. Используйте -autoreleaseпервичный. Используйте ручное -retain/-releaseсреднее, когда вам действительно нужно. (например: оптимизация памяти, явное удаление ресурса)

оборота Эонил
источник
2
Что касается 3) Я предложу противоположный подход: используйте ручное удержание / разблокирование везде, где это возможно! Кто знает, как будет использоваться этот код - и если он будет использоваться в тесном цикле, это может излишне взорвать использование вашей памяти.
Эйко
@Eiko Это просто преждевременная оптимизация , не может быть общим руководством.
Эонил
1
Я думаю, что это больше дизайн, особенно при работе с модельными классами. Я считаю рост памяти побочным эффектом, и это не то, что я хочу появляться часто. Хуже того, у другого разработчика, использующего мой код, нет никаких шансов, кроме как обернуть дорогостоящие вызовы в пулы автоматического выпуска (если это вообще возможно - мои объекты могут быть отправлены в какой-то другой код библиотеки). И эти проблемы трудно диагностировать позже, но, во-первых, дешево избежать. Если вы копируете / автоматически высвобождаете объекты, которые были переданы, вы можете потеряться, если они намного больше, чем вы ожидали. Я более расслаблен с кодом GUI, все же.
Эйко
@ Eiko Я согласен, autoreleaseчто в общем случае память будет дольше, а ручное управление retain/releaseможет уменьшить потребление памяти в этом случае. Однако это должно быть руководство для оптимизации в особом случае (даже если вы чувствуете себя всегда!), Не может быть причиной для обобщения преждевременной оптимизации как практики . И на самом деле ваше предложение не противоположно мне. Я упомянул это как случай действительно нуждающийся :)
Eonil