Найкращий спосіб визначення приватних методів для класу в Objective-C


355

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

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

Будь ласка, додайте аргумент свого підходу, коли публікуєте його. Чому це добре? Які недоліки у нього є (які ви знаєте) і як ви з ними справляєтеся?


Щодо моїх висновків поки що.

Можна використовувати категорії [наприклад, MyClass (Private)], визначені у файлі MyClass.m, для групування приватних методів.

Цей підхід має 2 питання:

  1. Xcode (і компілятор?) Не перевіряє, чи визначаєте ви всі методи в приватній категорії у відповідному блоці @implementation
  2. Ви повинні поставити @interface, декларуючи вашу приватну категорію на початку файлу MyClass.m, інакше Xcode скаржиться на повідомлення типу "самовідвід не може відповісти на повідомлення" privateFoo ".

Перший випуск можна обійти з порожньою категорією [наприклад, MyClass ()].
Другий мене сильно турбує. Я хотів би бачити приватні методи, реалізовані (і визначені) наприкінці файлу; Я не знаю, чи це можливо.


1
Люди могли б знайти це питання цікаво: stackoverflow.com/questions/2158660 / ...
bbum

Відповіді:


436

Не існує, як уже говорили інші, такого поняття, як приватний метод в Objective-C. Однак, починаючи з Objective-C 2.0 (мається на увазі Mac OS X Leopard, iPhone OS 2.0 та пізніших версій), ви можете створити категорію з порожнім іменем (тобто @interface MyClass ()), що називається Розширення класу . Що стосується розширення класу, це те, що реалізація методу повинна @implementation MyClassвідповідати загальнодоступним методам. Тому я структурую свої класи так:

У файлі .h:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

І у файлі .m:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

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


8
і це створить "MYClass може не відповідати на" -myPrivateMethod- ", не виняток / помилка.
Özgür

2
Це фактично починає з'являтися в кодовій коробці Apple. ++
Кріс Трахей

75
з компілятором LLVM 4 і далі, вам навіть цього не потрібно робити. ви можете просто визначити їх у своїй реалізації, не потребуючи їх розміщення в класі.
Абізерн

1
Якщо ви отримуєте застереження @Comptrol, це тому, що ви визначили метод нижче, а не вище іншого методу, який його викликає (див. Відповідь Енді) - і ви ігноруєте ці застереження з вашої небезпеки. Я зробив цю помилку, і компілятор заплутався добре, поки я if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...вклав дзвінок так: Тоді fWidthCombined завжди проходив через 0.
Wienke

6
@Wienke Більше не потрібно турбуватися про замовлення. Останні версії LLVM знайдуть метод, навіть якщо він з’явиться нижче, де він викликається.
Стів Ваддікор

52

Насправді не існує «приватного методу» в Objective-C, якщо час виконання може визначити, яка реалізація для його використання зробить це. Але це не означає, що не існує методів, які не входять до документально підтвердженого інтерфейсу. Для цих методів я вважаю, що категорія - це добре. Замість того, щоб розміщувати @interfaceу верхній частині файлу .m, як ваш пункт 2, я би розмістив його у власному .h-файлі. Конвенція, яку я дотримуюсь (і я бачила в іншому місці, я думаю, що це умова Apple, оскільки Xcode тепер надає автоматичну підтримку для неї) - це називати такий файл за його класом і категорією з +, що розділяє їх, тому @interface GLObject (PrivateMethods)його можна знайти в GLObject+PrivateMethods.h. Причина надання файлу заголовка полягає в тому, що ви можете імпортувати його у ваші тестові класи одиниці :-).

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

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

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

Навіть із розширеннями класу я часто створюю окремий заголовок ( GLObject+Extension.h), щоб я міг використовувати ці методи, якщо потрібно, імітуючи видимість "друг" або "захищений".

Оскільки ця відповідь була написана спочатку, компілятор clang почав робити два проходи для методів Objective-C. Це означає, що ви можете уникнути декларування своїх "приватних" методів повністю і незалежно від того, чи вони розташовані вище або нижче викличного сайту, їх знайде компілятор.


37

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


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

1
Я також схильний робити це, але також не є експертом Objective-C. Для експертів, чи є якась причина не зробити цього таким чином (окрім випуску замовлення методів)?
Ерік Сміт

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

17
Впорядкованість методів вже не є значною. Останні версії LLVM не хвилюються, в якому порядку реалізовані методи. Таким чином, ви можете влаштувати себе на замовлення, не потребуючи декларації спочатку.
Стів Ваддікор

Дивіться також цю відповідь від @justin
lagweezle

19

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

У деяких випадках вам потрібно буде оголосити метод у продовженні класу (наприклад, якщо використовується перемикач між продовженням класу та the @implementation).

static функції дуже хороші для особливо чутливих або критичних швидкості приватних методів.

Конвенція про іменування префіксів може допомогти вам уникнути випадкового перекриття приватних методів (я вважаю ім'я класу безпечним для префікса).

Іменовані категорії (наприклад @interface MONObject (PrivateStuff)) не є особливо хорошою ідеєю через потенційні зіткнення імен при завантаженні. Вони дійсно корисні лише для друзів або захищених методів (які дуже рідко є хорошим вибором). Щоб упевнитись, що про неповні реалізації категорій вам потрібно реалізувати:

@implementation MONObject (PrivateStuff)
...HERE...
@end

Ось невеликий анотований шпаргалка:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

Ще один підхід, який може бути не очевидним: тип C ++ може бути дуже швидким і забезпечувати набагато більш високий ступінь контролю, мінімізуючи кількість експортованих та завантажених методів objc.


1
+1 за використання повного імені класу як префіксу назви методу! Це набагато безпечніше, ніж просто підкреслення або навіть власний TLA. (Що робити, якщо приватний метод знаходиться в бібліотеці, яку ви використовуєте в інших своїх проектах, і ви забудете, що ви вже використовували це ім'я, десь рік або два тому ...?)
big_m

14

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

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end

1
Apple зарезервувала імена з провідним підкресленням для власного використання.
Георг Шоллі

1
А що робити, якщо ви не використовуєте рамки Apple? Я часто розробляю код Objective-C без рамок Apple, адже я будую на Linux, Windows та Mac OS X. Я його все одно видалив, враховуючи, що більшість людей, які кодують в Objective-C, ймовірно, використовують його на Mac OS X.
dreamlax

3
Я думаю, що це дійсно приватний метод у файлі .m. Інші методи категорії класів насправді не є приватними, оскільки ви просто не можете розмістити приватні методи в блоці @interface ... @ end.
David.Chu.ca

Навіщо ти це робив? якщо ви просто додасте "-" на початку визначення методу, ви отримаєте доступ до "self", не проходячи як параметр.
Хлопець

1
@Guy: адже тоді метод виявляється за допомогою рефлексії, а отже, зовсім не приватний.
dreamlax

3

Ви могли б використовувати блоки?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

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


1
Що ви тут зробили, це створити глобальну змінну блокового типу, яка насправді не краща за функцію (і навіть не справді приватну, оскільки вона не оголошена static). Але я експериментував із призначенням блоків приватним ivars (від методу init) - свого роду JavaScript-стилю - який також дозволяє отримати доступ до приватних ivars, чогось неможливо зі статичних функцій. Ще не впевнений, якому я віддаю перевагу.
big_m

3

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

визначте свій метод з аналогічним підписом, як код нижче ...

-(void)myHelperMethod: (id) sender{
     // code here...
}

тоді, коли вам потрібно посилатися на метод, просто викликайте його як селектор ...

[self performSelector:@selector(myHelperMethod:)];

цей рядок коду буде викликати створений вами метод і не матиме дратівливого попередження про невстановлення його у файлі заголовка.


6
Таким чином у вас немає можливості передати третій параметр.
Лі Фумін

2

Якщо ви хочете уникнути @interfaceблоку вгорі, ви завжди можете помістити приватні декларації в інший файл, MyClassPrivate.hне ідеальний, але це не захаращує реалізацію.

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end

2

Ще одне, що я тут не бачив - Xcode підтримує .h файли з ім'ям "_private". Скажімо, у вас клас MyClass - у вас є MyClass.m та MyClass.h, і тепер ви також можете мати MyClass_private.h. Xcode розпізнає це та включить його до списку "Контрагентів" у помічнику редактора.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"

1

Не можна обійти питання №2. Ось так працює компілятор C (а значить і компілятор Objective-C). Якщо ви використовуєте редактор XCode, спливаюча функція повинна полегшувати навігацію @interfaceта @implementationблоки у файлі.


1

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


0

Як говорили інші, визначення приватних методів у @implementationблоці для більшості цілей добре.

Щодо теми організації коду - мені подобається тримати їх разом, pragma mark privateщоб полегшити навігацію в Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.