Які найкращі практики ви використовуєте, коли пишете Objective-C та какао? [зачинено]


346

Я знаю про HIG (що дуже зручно!), Але які програми програмування ви використовуєте, коли пишете Objective-C, а точніше, коли використовуєте Cocoa (або CocoaTouch).


дивіться цю публікацію в блозі, дуже приємно. ironwolf.dangerousgames.com/blog/archives/913
користувач392412

Відповіді:


398

Я почав робити кілька речей, які я не вважаю стандартними:

1) З появою властивостей я більше не використовую "_" для приставки "приватних" змінних класу. Зрештою, якщо до змінної можна отримати доступ до інших класів, чи не має бути для неї властивості? Мені завжди не подобався префікс "_", щоб зробити код більш потворним, і тепер я можу його залишити.

2) Якщо говорити про приватні речі, я вважаю за краще розміщувати визначення приватних методів у файлі .m у розширенні класу, як:

#import "MyClass.h"

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

@implementation MyClass

Чому захаращувати .h-файл із сторонніми речами не варто піклуючись? Порожній () працює для приватних категорій у файлі .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 val = someFloat * 2.2f;

Це в основному важливо, коли someFloatнасправді це може бути подвійне, вам не потрібна математика змішаного режиму, оскільки ви втрачаєте точність у «валі» на зберіганні. Хоча номери з плаваючою комою підтримуються апаратно на айфонах, все ж може знадобитися більше часу, щоб зробити арифметику подвійної точності на відміну від одиничної точності. Список літератури:

На старих телефонах нібито обчислення працюють з однаковою швидкістю, але у регістрах може бути більше одиничних точних компонентів, ніж удвічі, тому для багатьох розрахунків однакова точність в кінцевому підсумку буде швидшою.

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


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);

4
Мене цього вкусив раніше.
Адам Ернст

Це хороша порада для будь-якої мови програмування
Том Фобеар

107

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

Приклади того, що робити, а що не робити:

  • Не оголошуйте id m_something;в інтерфейсі об'єкта і називайте його змінною або полем члена ; використовувати somethingабо _somethingдля його імені та називати його змінною екземпляра .
  • Не називайте геттера -getSomething; відповідне ім'я какао просто -something.
  • Не називайте сетера -something:; вона повинна бути-setSomething:
  • Назва методу перемежовується з аргументами і включає колонки; це -[NSObject performSelector:withObject:], ні NSObject::performSelector.
  • Використовуйте inter-caps (CamelCase) у назвах методів, параметрах, змінних, назвах класів тощо, а не під підрядниками (підкресленнями).
  • Назви класів починаються з великої літери, змінних та методів з малих літер.

Що б ви не робили, не використовуйте угорські позначення у стилі Win16 / Win32. Навіть Microsoft відмовився від цього з переходом на платформу .NET.


5
Я б заперечував, не використовуйте setSomething: / щось взагалі - замість цього використовуйте властивості. На даний момент мало людей, яким дійсно потрібно націлитись на Тигра (єдина причина, щоб не використовувати властивості)
Кендалл Хельмштеттер Гельнер

18
Властивості все ще генерують для вас методи аксесуара, а атрибути getter = / setter = у властивості дозволяють вказати назви методів. Крім того, ви можете використовувати синтаксис [foo something] замість 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
не завантажив би нитку, збережеш її двічі? (один раз у nib, другий за віднесенням до власності). Чи повинен я звільнити їх у угоді?
Корнель

6
Щоб уникнути протікань, вам потрібно зняти розетки в viewDidUnload (iPhone OS 3.0+) або в користувацькому методі setView: Очевидно, ви повинні також випустити в угоді.
Франк Щебра

2
Майте на увазі, що не всі згодні з цим стилем: weblog.bignerdranch.com/?p=95
Майкл

Це так, як Apple робить і речі. "Починаючи розробку iPhone 3" згадується і про цю зміну в порівнянні з попередніми версіями.
ustun

Я згадував про це в іншому коментарі, але повинен був розмістити його тут: Коли динамічний синтез ivar почне відбуватися для додатків для iOS (якщо / коли?), Ви будете раді, що ви поставите IBOutlet у власність проти ivar!
Джо Д'Андреа

97

Використовуйте статичний аналізатор LLVM / Clang

ПРИМІТКА: Під Xcode 4 це тепер вбудовано в IDE.

Ви використовуєте статичний аналізатор Clang, щоб - не дивно - проаналізувати свій код C та Objective-C (ще немає C ++) на Mac OS X 10.5. Встановлення та використання тривіально:

  1. Завантажте останню версію з цієї сторінки .
  2. З командного рядка, cdдо каталогу вашого проекту.
  3. Виконати scan-build -k -V xcodebuild.

(Є деякі додаткові обмеження тощо), зокрема, ви повинні проаналізувати проект у його конфігурації "Налагодження" - детальну інформацію див. На веб- сторінці http://clang.llvm.org/StaticAnalysisUsage.html - але це більш-менш. до чого воно зводиться.)

Потім аналізатор виробляє для вас набір веб-сторінок, який показує ймовірне управління пам’яттю та інші основні проблеми, які компілятор не в змозі виявити.


1
У мене виникли проблеми з тим, як це вдалося працювати, поки я не дотримувався цих інструкцій: oiledmachine.com/posts/2009/01/06/…
bbrown

15
У XCode 3.2.1 на Snow Leopard він вже вбудований. Ви можете його запустити вручну, запустивши -> Збірка та аналіз , або ви можете ввімкнути його для всіх збірок за допомогою налаштування "Запустити статичний аналізатор". Зауважте, що даний інструмент на даний момент підтримує лише 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;

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.
johne

Краще використовувати nil замість NULL, оскільки NULL не звільнить пам'ять.
Naveen Shan

@NaveenShan nil == NULL. Вони точно такі самі, за винятком того, що nilє idі NULLє void *. Ваше твердження не відповідає дійсності.

@WTP yep, nil == NULL, але використання nil - це, очевидно, кращий спосіб, якщо ви подивитеся на приклади фрагментів коду яблук, вони користуються nil скрізь, і, як ви вже сказали, nil - це ідентифікатор, що робить його кращим над пустотою * , у випадках, коли ви надсилаєте ідентифікатори, тобто.
Ахті

1
@Ahti точно, і 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 Reference-Objective-C 2.0.

"Розширення класів дозволяють оголосити додатковий необхідний API для класу в інших місцях, ніж у блоці @interface основного класу"

Отже вони є частиною фактичного класу - а НЕ є приватною категорією, крім класу. Тонка, але важлива відмінність.


Ви можете це зробити, але мені подобається чітко позначити його як "приватний" розділ (більше документації, ніж функціональний), хоча, звичайно, це вже багато очевидного з того, що він знаходиться у файлі .m ...
Kendall Helmstetter Gelner

2
За виключення там є різниця між приватними категоріями і розширеннями класу: «Розширення класу дозволяє оголосити додатковий необхідний API для одного класу в інших , ніж в блоці основного класу @interface місць, як показано в наступному прикладі:" Дивіться посилання на редагуванні.
schwa

Я погоджуюся, що є різниця, де компілятор попередить вас, коли ви не застосували методи CE - але я не вважаю цей аспект дуже важливим, коли всі методи знаходяться в одному файлі, і всі приватні. Я все ще віддаю перевагу аспекту ремонтопридатності маркування переднього опорного блоку приватним
Kendall Helmstetter Gelner

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;
}

Оскільки назва методу починається з "нового", споживачі вашого API знають, що вони несуть відповідальність за звільнення отриманого об'єкта (див., Наприклад, метод NSObjectControllernewObject ).

(1) Ви можете взяти під контроль, використовуючи власні локальні пули автоматичного випуску. Докладніше про це див. У пулах Autorelease .


6
Я вважаю , що переваги НЕ використовуючи autorelease переважують витрати (тобто більше помилок витоку пам'яті). Код у головному потоці в будь-якому разі повинен бути досить коротким (або в іншому випадку ви заморозите інтерфейс користувача), а для більш тривалого, об'ємного пам'яті фонового коду, ви завжди зможете загортати місця, що займають пам'ять, у локальні пули автовипуску.
адиб

56
Я не погоджуюсь. Ви повинні використовувати об'єкти, що опубліковані автоматично, коли це можливо. Якщо вони занадто сильно збільшують слід пам'яті, вам слід скористатися іншим NSAutoreleasePool. Але лише після того, як ви підтвердили, що це справді проблема. Передчасна оптимізація і все таке ...
Свен,

3
Я витрачаю менше 40 сек. щодня набираючи [деякий випуск об'єкта] та читаючи "додатковий рядок" під час створення нового об'єкта, але я одного разу прогорів через 17 годин, щоб знайти помилку автоматичного випуску, яка відображатиметься лише в особливих випадках і не створювала когерентної помилки в консолі. Тож я погоджуюся з adib, коли він висловлюється так: "Я вважаю, що переваги від використання автоматичного випуску перевищують його витрати".
RickiG

7
Я погоджуюсь зі Свеном. Основна мета повинна бути чіткістю коду та зменшенням помилок кодування, з оптимізацією пам'яті лише там, де це потрібно. Введення [[Foo alloc] init] автовипуску] швидко, і ви негайно вирішите питання щодо випуску цього нового об’єкта. Читаючи код, вам не доведеться розшукувати відповідний випуск, щоб переконатися, що він не протікає.
Майк Веллер

3
Життєвий цикл об'єктів, що випускаються автоматично, досить чітко визначений та визначений на достатньому рівні.
Еоніл

69

Деякі з них уже були згадані, але ось що я можу придумати вгорі голови:

  • Дотримуйтесь правил іменування KVO. Навіть якщо ви зараз не використовуєте KVO, на мій досвід, часто це все ще вигідно в майбутньому. І якщо ви використовуєте KVO або прив'язки, вам потрібно знати, що справи працюватимуть так, як належить. Це стосується не лише методів аксесуарів та змінних екземплярів, а й багатьох зв'язків, перевірки, автоматичного сповіщення залежних ключів тощо.
  • Поставте приватні методи в категорію. Не лише інтерфейс, а й його реалізація. Добре провести деяку концептуальну відстань між приватними та неприватними методами. Я включаю все у свій .m файл.
  • Покладіть методи фонових ниток у категорію. Так само, як вище. Я виявив, що добре зберігати чіткий концептуальний бар'єр, коли ви замислюєтеся про те, що в основному, а що ні.
  • Використовуйте #pragma mark [section]. Зазвичай я групую за власними методами, переопределеннями кожного підкласу та будь-якою інформацією чи формальними протоколами. Це значно спрощує перехід до того, що я шукаю. З тієї ж теми групуйте подібні методи (наприклад, методи делегування табличного перегляду) разом, а не скрізь їх скрізь.
  • Приставка приватних методів & ivars з _. Мені подобається те, як це виглядає, і я рідше використовую івар, коли маю на увазі майно випадково.
  • Не використовуйте мутаторні методи / властивості в init & dealloc. У мене ніколи не було нічого поганого через це, але я бачу логіку, якщо ви зміните метод зробити щось, що залежить від стану вашого об'єкта.
  • Поставте IBOutlets у властивості. Я насправді просто прочитав це тут, але я почну це робити. Незалежно від будь-яких переваг пам’яті, стилістично це здається (принаймні, мені).
  • Уникайте написання коду, який вам абсолютно не потрібен. Це дійсно охоплює багато речей, як, наприклад, робити ivars, коли #defineбуде виконано тестування , або кешувати масив, а не сортувати його щоразу, коли потрібні дані. Про це я можу сказати дуже багато, але суть полягає в тому, щоб не писати код, поки вам це не потрібно, або якщо профайлер вам каже. Це робить речі набагато простішими в обслуговуванні в довгостроковій перспективі.
  • Закінчіть те, що ви починаєте. Маючи багато напівфабрикатів, баггі-код - це найшвидший спосіб вбити проект мертвим. Якщо вам потрібен метод заглушки, це добре, просто вкажіть його, помістивши NSLog( @"stub" )всередину, або, якщо ви хочете слідкувати за речами.

3
Я б запропонував вам поставити приватні методи у продовження класу. (тобто @interface MyClass () ... @end у вашому .m)
Джейсон Медейрос

3
Замість #PRAGMA можна використовувати коментар // Позначити: [Розділ], який є більш портативним і працює однаково.
aleemb

Якщо у мене відсутній спеціальний синтаксис, // Позначити: не додає мітку у спадне меню функцій Xcode, що насправді є половиною причини його використання.
Марк Шарбонно

6
Вам потрібно скористатися великими літерами, "// MARK: ...", щоб відобразити його у спадному меню.
Rhult

3
Що стосується Finish what you startвас, ви також // TODO:можете позначити код для завершення, який відображатиметься у спадному меню.
iwasrobbed

56

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

Ви також не маєте видимості від публічного та захищеного порівняно з приватним способом, що перешкоджає написанню тестів для ваших службовців.


Які рамки тесту ви рекомендуєте?
мельфар

13
Xcode включає OCUnit, рамку тестування блоку Objective-C та підтримку запуску пакетів тестових одиниць як частини вашого процесу збирання.
Кріс Гансон

55

Золоте правило: Якщо ти allocтоді release!

ОНОВЛЕННЯ: Якщо ви не використовуєте ARC


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

Переконайтеся, що ви вибрали закладку на сторінці Magic Debugging Magic . Це має бути вашою першою зупинкою, коли вдаряєтесь головою об стіну, намагаючись знайти джерело помилки з какао.

Наприклад, він розповість, як знайти метод, за яким ви спочатку виділили пам'ять, яка пізніше викликає збої (наприклад, під час завершення роботи програми).


1
Зараз на базі Magic на Debugging Magic використовується версія для iOS .
Jeethu

38

Постарайтеся уникати того, що я вирішив назвати Newbiecategoryaholism. Коли новачки в Objective-C виявляють категорії, вони часто роблять диких свиней, додаючи корисні невеликі категорії до кожного існуючого класу ( "Що? Я можу додати метод перетворення числа в римські цифри в рок NSNumber на!" ).

Не робіть цього.

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

Більшу частину часу, коли ви дійсно думаєте, що вам потрібен метод категорії, який допоможе впорядкувати якийсь код, ви виявите, що ви ніколи не будете повторно використовувати метод.

Існують і інші небезпеки, якщо ви не змінюєте назви методів вашої категорії (а хто, крім абсолютно божевільного ддрібіну?), Є ймовірність, що Apple, або плагін, або щось інше, що працює у вашому адресному просторі, також визначать ту саму категорію метод з однойменною назвою з дещо іншим побічним ефектом ....

ДОБРЕ. Тепер, коли вас попередили, ігноруйте "не робити цю частину". Але проявляйте граничну стриманість.


Мені подобається ваша відповідь, мої поради не використовуватимуть категорію для зберігання утилітного коду, якщо ви не збираєтесь копіювати якийсь код у більш ніж одному місці, і код чітко належить до класу, який ви збираєтесь класифікувати ...
Kendall Helmstetter Гелнер

Я просто хотів би повідомити і висловити свою підтримку методам категорій простору імен. Це просто здається правильним.
Майкл Баклі

+1, якщо тільки для римських цифр. Я б повністю зробив це!
Брайан Постув

14
Контр-точка: Протягом останніх півтора років я дотримувався прямо протилежної політики: "Якщо її можна реалізувати в категорії, зробіть це". Як результат, мій код набагато більш стислий, виразніший і легший для читання, ніж багатослівний зразок коду, який надає Apple. Я втратив загалом близько 10 хвилин на один конфлікт у просторі імен, і, мабуть, я отримав люди-місяці від ефективності, яку я створив для себе. Кожен свій, але я прийняв цю політику, знаючи ризики, і я надзвичайно радий, що так і зробив.
cduhn

7
Я не згоден. Якщо це буде функцією, і вона стосується об'єкта Foundation, і ви можете придумати гарне ім'я, вставте його в категорію. Ваш код буде більш читабельним. Я думаю, що справді важливим моментом є: робити все в міру.
mxcl

37

Протистояти субкласифікації світу. У Какао багато робиться шляхом делегування та використання базового часу виконання, що в інших структурах відбувається за допомогою підкласифікації.

Наприклад, у Java ви багато використовуєте екземпляри анонімних *Listenerпідкласів, а в .NET ви багато використовуєте свої EventArgsпідкласи. У какао ви нічого не робите - замість цього використовується цільова дія.


6
Інакше відомий як "Склад над спадщиною".
Ендрю Еблінг

37

Сортуйте рядки так, як бажає користувач

Коли ви сортуєте рядки для подання користувачеві, не слід використовувати простий compare:метод. Натомість завжди слід використовувати локалізовані методи порівняння, такі як localizedCompare:або localizedCaseInsensitiveCompare:.

Детальніше див. Пошук, порівняння та сортування рядків .


31

Заявлені властивості

Зазвичай ви повинні використовувати функцію оголошених властивостей Objective-C 2.0 для всіх своїх ресурсів. Якщо вони не є загальнодоступними, додайте їх у розширення класу. Використання оголошених властивостей робить семантику управління пам’яттю негайно зрозумілою та полегшує вам перевірку методу угоди - якщо ви згрупуєте декларації властивостей разом, ви можете швидко просканувати їх і порівняти з реалізацією методу угоди.

Вам слід добре подумати, перш ніж не позначати властивості як "неатомічні". Як зазначає Посібник з мов програмування Цілі C , властивості за замовчуванням є атомними та мають значні накладні витрати. Більше того, просто зробивши всі ваші властивості атомними, це не робить ваше додаток безпечним. Зверніть увагу, звичайно, що якщо ви не вказуєте "неатомічний" і не реалізуєте свої власні методи аксесуарів (а не синтезувати їх), ви повинні реалізувати їх атомним способом.


26

Подумайте про нульові значення

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


Я маю це: #define SXRelease(o); o = nilі те саме для CFReleaseі free. Це спрощує все.

26

Використовуйте NSAssert та друзів. Я постійно використовую nil як дійсний об'єкт ... особливо надсилання повідомлень до nil цілком справедлива в Obj-C. Однак якщо я дійсно хочу переконатися в стані змінної, я використовую NSAssert і 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;
}   

це легко забути. Важливо все-таки
Брок Вульф

3
Це викликає занепокоєння лише при утриманні від статичного набору тексту. Якщо компілятор знає тип, аргументи та типи повернення можуть відрізнятися без проблем. Персонналі, я вважаю це не часто проблемою. Apple також має багато методів, які мають однойменну назву, але відрізняються за типом повернення. Нарешті, є прапор компілятора, який попереджає вас у неоднозначних випадках.
Микола Рухе

Якщо ми будемо слідувати правилам Apple щодо встановлення імен, такої ситуації не станеться :)
Eonil

22

Якщо ви використовуєте Leopard (Mac OS X 10.5) або новішої версії, ви можете скористатися програмою Instruments для пошуку та відстеження витоків пам'яті. Після побудови програми в Xcode виберіть Запуск> Почати з інструмента продуктивності> Витоки.

Навіть якщо у вашій програмі не з’являються витоки, ви можете занадто довго тримати об’єкти. У Instruments ви можете використовувати для цього інструмент ObjectAlloc. Виберіть інструмент ObjectAlloc у вашому документі «Інструменти» та підберіть деталі інструменту (якщо він ще не відображається), вибравши «Перегляд»> «Деталі» (біля нього має бути галочка). У розділі "Тривалість розподілу" у деталях ObjectAlloc переконайтесь, що ви вибрали перемикач поруч із "Створено та все ще живе".

Тепер, коли ви припиняєте запис своєї програми, вибір інструмента ObjectAlloc покаже вам, скільки посилань на кожен нерухомий об’єкт у вашій програмі в стовпці "# Net". Переконайтесь, що ви не тільки дивитесь на власні класи, але й на класи об'єктів верхнього рівня ваших файлів NIB. Наприклад, якщо у вас немає вікон на екрані, і ви бачите посилання на ще живе NSWindow, можливо, ви не випустили його у своєму коді.


21

Прибирати в угоді.

Це одна з найпростіших речей, яку можна забути - esp. при кодуванні 150mph. Завжди, завжди, завжди очищайте свої змінні атрибути / учасники у взаємодії.

Мені подобається використовувати атрибути Objc 2 - з новою позначкою крапок - тому це робить очищення безболісним. Часто так просто, як:

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

Це подбає про випуск для вас і встановить атрибут NULL (що я вважаю оборонним програмуванням - на випадок, якщо інший метод, який надалі внизу в операційній операції, знову отримує змінну члена - рідко, але може трапитися).

Якщо GC увімкнено в 10.5, це вже не так вже й потрібно - але вам може знадобитися очищення інших створених вами ресурсів, ви можете зробити це замість методу завершення.


12
Загалом , ви повинні НЕ використовувати аксессор методи в dealloc (або ініціалізації).
mmalc

1
Окрім причин продуктивності (аксесуари дещо повільніші, ніж прямий доступ), чому я не повинен використовувати аксесуари у dealloc чи init?
schwa

1
(a) Причини продуктивності є цілком адекватною причиною самі по собі (особливо якщо ваші аксесуари атомні). (b) Ви повинні уникати будь-яких побічних ефектів, які можуть мати у постачальників. Останнє є особливо проблемою, якщо ваш клас може бути підкласом.
mmalc

3
Зауважу, що якщо ви працюєте в сучасному режимі із синтезованими ivars, ви повинні використовувати аксесуари у взаємодії. Дуже багато сучасного коду виконання - це GC, але не все це.
Луї Гербарг

1
Більш розгорнутий погляд на те, як використовуватись та не використовувати методи аксесуарів та властивості аксесуарів -initта -deallocметоди, можна знайти тут: mikeash.com/?page=pyblog/…
Йоган

17

Усі ці коментарі чудові, але я дуже здивований, що ніхто не згадував посібник зі стилів Google Objective-C, який був опублікований деякий час назад Я думаю, що вони виконали дуже ретельну роботу.


7
Хм, перший приклад уже повний фігня. Ніколи не документуйте мовні ідіоми. Якби я знайшов такі коментарі у файлі заголовка, я б не намагався читати далі.
Стефан Еггермонт

5
Ой мої очі !!!!! Я не можу повірити в те, що бачив.
Еоніл


13

Не забувайте, що NSWindowController та NSViewController випустять об'єкти верхнього рівня файлів NIB, якими вони керують.

Якщо ви вручну завантажуєте файл NIB, ви несете відповідальність за звільнення об'єктів верхнього рівня NIB, коли закінчите з ними.


12

Один з досить очевидних для початківців користувачів: використовувати функцію автоматичного відступу Xcode для вашого коду. Навіть якщо ви копіюєте / вставляєте з іншого джерела, після того, як ви вставили код, ви можете вибрати весь блок коду, клацнути правою кнопкою миші на ньому, а потім вибрати опцію для повторного відступу всього цього блоку.

Xcode насправді проаналізує цей розділ і відступить його на основі дужок, циклів тощо. Це набагато ефективніше, ніж натискання на пробіл або клавішу вкладки для кожного рядка.


Ви навіть можете встановити Tab на відступ, а потім зробити Cmd-A та Tab.
Плюменатор

10

Я знаю, що я не помітив цього при першому вступі в програму какао.

Переконайтеся, що ви розумієте обов'язки щодо управління пам'яттю стосовно файлів NIB. Ви несете відповідальність за звільнення об'єктів верхнього рівня у будь-якому завантажуваному вами файлі NIB. Прочитайте Документацію Apple з цього питання.


6
Це не правда. Незалежно від того, ви несете відповідальність за випуск об'єктів верхнього рівня, залежить від того, який клас ви успадковуєте та яку платформу використовуєте. Дивіться developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/… серед інших.
mmalc

10

Увімкніть усі попередження GCC, а потім вимкніть ті, які регулярно викликаються заголовками Apple, щоб зменшити шум.

Також часто запускайте статичний аналіз Кланг; ви можете ввімкнути його для всіх збірок за допомогою налаштування збірки "Запустити статичний аналізатор".

Напишіть одиничні тести та запустіть їх із кожною збіркою.


І, якщо можете, увімкніть "Поводитись із попередженнями як з помилками". Не дозволяти жодних попереджень.
Пітер Хосей

2
Зручний сценарій для налаштування вашого проекту з рекомендованими попередженнями доступний тут: rentzsch.tumblr.com/post/237349423/hoseyifyxcodewarnings-scpt
Йоган

10

Змінні та властивості

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

2 / Дайте своїм властивостям ім'я змінної, що не використовується за замовчуванням, наприклад:


@synthesize property = property_;

Причина 1: Ви виявите помилки, викликані забуттям "Я". при відведенні майна. Причина 2. З моїх експериментів у "Аналізатору витоку в інструментах" виникають проблеми з виявленням протікаючих властивостей із замовчуванням.

3 / Ніколи не використовуйте утримувати чи випускати безпосередньо на властивості (або лише у дуже виняткових ситуаціях). У вашій угоді просто призначте їм нуль. Збереження властивостей призначене для обробки збереження / звільнення самостійно. Ніколи не знаєш, чи не сеттер, наприклад, додавання та видалення спостерігачів. Ви повинні використовувати змінну безпосередньо тільки в її сеттері та геттері.

Перегляди

1 / Покладіть кожне визначення перегляду в xib (якщо ви можете, виняток - це зазвичай налаштування динамічного вмісту та шару). Це економить час (простіше, ніж писати код), його легко змінити, і він підтримує чистий код.

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

3 / IBOutlets не повинні завжди залишатися (або сильними). Зауважте, що більшість ваших IBOutlets є частиною вашої ієрархії перегляду і, таким чином, неявно зберігається.

4 / Відпустіть усі IBOutlets у viewDidUnload

5 / Зателефонуйте viewDidUnload з методу угоди. Це неявно не називається.

Пам'ять

1 / Автоматичні випуски об'єктів під час їх створення. Багато помилок викликані переміщенням виклику випуску в одну гілку if-else або після заяви про повернення. Випуск замість автоматичного випуску слід застосовувати лише у виняткових ситуаціях - наприклад, коли ви очікуєте запуску і не хочете, щоб ваш об'єкт був надрукований занадто рано.

2 / Навіть якщо ви використовуєте Автоматичний довідковий підрахунок, ви повинні чудово розуміти, як працюють методи збереження-випуску. Використовувати функцію утримування-випуску вручну не складніше, ніж ARC, в обох випадках вам доведеться враховувати витоки та цикли утримування. Подумайте про використання ретентивізованого випуску вручну для великих проектів або складних ієрархій об'єктів.

Коментарі

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 є лише легким шаром OOP над мовою 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 ++ та власних доповнень. Документуйте свій код, використовуйте стандартні стандартні відступи, пробіли та порожні рядки в потрібних місцях тощо.


9

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

Objective-C - це об'єктно-орієнтована мова, але функціональний стиль какао відомий, і він розроблений функціональним стилем у багатьох випадках.

  1. Відбувається поділ мутабельності. Використовуйте незмінні класи як основний, а об'єкт, що змінюється, як вторинний. Наприклад, в основному використовуйте NSArray і використовуйте NSMutableArray лише тоді, коли вам потрібно.

  2. Є чисті функції. Не так багато, купуйте багато API-програм фреймворку, розроблених як чисті функції. Подивіться на такі функції, як CGRectMake()або CGAffineTransformMake(). Очевидно, форма вказівника виглядає більш ефективно. Однак непрямий аргумент з покажчиками не може запропонувати побічних ефектів. Дизайн конструкцій максимально чисто. Відокремте навіть державні об’єкти. Використовуйте -copyзамість того, -retainщоб передавати значення іншому об'єкту. Оскільки спільний стан може мовчки впливати на мутацію до значення в іншому об'єкті. Тому не може бути побічним ефектом. Якщо у вас є значення зовнішнього від об'єкта, скопіюйте його. Тому також важливо розробити загальний стан якомога мінімальніше.

Однак не бійтеся також використовувати нечисті функції.

  1. Є лінива оцінка. Побачити щось на зразок -[UIViewController view]власності. Перегляд не буде створено, коли об’єкт створений. Він буде створений під viewчас першого читання властивості абонента . UIImageне завантажуватиметься, поки воно фактично не буде намальовано. Подібної конструкції існує багато. Цей вид конструкцій дуже корисний для управління ресурсами, але якщо ви не знаєте концепції лінивої оцінки, зрозуміти їх поведінку непросто.

  2. Відбувається закриття. Використовуйте С-блоки максимально. Це значно спростить ваше життя. Але прочитайте ще раз про управління блоковою пам’яттю, перш ніж використовувати її.

  3. Є напів-авто GC. NSAutoreleasePool. Використовуйте -autoreleaseосновне. Використовуйте вручну, -retain/-releaseколи вам це справді потрібно. (наприклад: оптимізація пам'яті, явне видалення ресурсів)


2
Щодо 3) Я пропоную зворотний підхід: Використовуйте вручну зберігати / відпускати, де це можливо! Хто знає, як цей код буде використовуватися - і якщо він буде використовуватися в тісному циклі, він може непотрібно підірвати ваше використання пам'яті.
Ейко

@Eiko Це просто передчасна оптимізація , не може бути загальним керівництвом.
Еоніл

1
Я думаю, що це більше дизайнерська річ, особливо коли ми працюємо над класами моделей. Я розглядаю зростання пам’яті як побічний ефект, і це не те, що я хочу з’являтися часто. Гірше, що інший розробник, який використовує мій код, не має жодного шансу, крім того, щоб загортати дорогі дзвінки в пули автовипуску (якщо це взагалі можливо - мої об’єкти можуть бути надіслані до іншого коду бібліотеки). І ці проблеми важко діагностувати пізніше, але в першу чергу їх уникнути дешево. Якщо ви копіюєте / автовипускаєте об'єкти, які були передані, ви можете загубитися, якщо вони значно більше, ніж ви очікували. Хоча я більш розслаблений з кодом GUI.
Ейко

@Eiko Я погоджуюсь autorelease, що взагалі довше зберігатиме пам’ять, і вручну retain/releaseможна зменшити споживання пам'яті у цьому випадку. Однак це повинно бути настановою для оптимізації спеціальних випадків (навіть ви відчуваєте себе завжди!), Не може бути причиною узагальнення передчасної оптимізації як практики . А насправді ваша пропозиція не протилежна мені. Я згадував це як випадок справді потрібного :)
Eonil,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.