Лучший способ определить частные методы для класса в Objective-C

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

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

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


Что касается моих выводов до сих пор.

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

Этот подход имеет 2 проблемы:

  1. Xcode (и компилятор?) Не проверяет, определяете ли вы все методы в частной категории в соответствующем блоке @implementation
  2. Вы должны поместить @interface, объявляющий вашу личную категорию, в начало файла MyClass.m, в противном случае XCode жалуется с сообщением типа «self может не отвечать на сообщение« privateFoo ».

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

5.10.2008 20:29:53
Люди могут найти этот вопрос интересным: stackoverflow.com/questions/2158660/…
bbum 3.03.2010 18:35:50
ma11hew28 20.07.2011 19:06:43
12 ОТВЕТОВ

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

37
5.10.2008 22:03:16
Преимущество этого решения заключается в том, что оно позволяет избежать добавления лишней структуры программы, чтобы избежать предупреждения компилятора.
Jan Hettich 23.06.2010 03:06:19
Я тоже склонен это делать, но также не являюсь экспертом Objective-C. Для экспертов, есть ли причина не делать это таким образом (кроме вопроса заказа метода)?
Eric Smith 10.11.2010 17:16:18
Упорядочение методов кажется незначительной проблемой, но если вы переведите его в удобочитаемость кода, это может стать довольно важной проблемой, особенно при работе в команде.
borisdiakur 5.09.2012 20:33:01
Метод заказа больше не имеет значения. Последние версии LLVM не заботятся о порядке реализации методов. Таким образом, вы можете подойти к заказу, не объявляя сначала.
Steve Waddicor 26.05.2013 11:05:23
Смотрите также этот ответ от @justin
lagweezle 10.11.2014 23:26:37

В 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. Это означает, что вы можете не объявлять ваши «приватные» методы полностью, и независимо от того, находятся они выше или ниже вызывающего сайта, они будут найдены компилятором.

51
8.05.2013 12:04:10

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

1
16.03.2009 18:33:08

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

//.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
14
16.03.2009 20:33:51
Apple зарезервировала имена с подчеркиванием для собственных нужд.
Georg Schölly 16.03.2009 19:42:47
А что если вы не используете фреймворки Apple? Я часто разрабатываю код Objective-C без фреймворков Apple, фактически я строю на Linux, Windows и Mac OS X. Я все равно удалил его, учитывая, что большинство людей, которые пишут в Objective-C, вероятно, используют его на Mac OS X.
dreamlax 16.03.2009 20:41:04
Я думаю, что это действительно частный метод в файле .m. Другие методы категории классов на самом деле не являются закрытыми, потому что вы просто не можете поместить private для методов в @interface ... @ end block.
David.Chu.ca 26.05.2010 21:14:53
Почему ты бы так поступил? если вы просто добавите «-» в начале определения метода, вы получите доступ к «себе» без передачи в качестве параметра.
Guy 21.10.2014 20:22:21
@Guy: потому что тогда метод обнаружим отражением, и, следовательно, не является частным.
dreamlax 21.10.2014 21:03:45

Как уже говорили другие, в Objective-C нет такого понятия, как закрытый метод. Однако, начиная с Objective-C 2.0 (имеется в виду Mac OS X Leopard, iPhone OS 2.0 и более поздние версии), вы можете создать категорию с пустым именем (то есть @interface MyClass ()) под названием Class Extension . Уникальным в расширении класса является то, что реализации методов должны идти так же, @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

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

436
9.02.2012 09:43:42
и он сгенерирует «MYClass может не отвечать на« -myPrivateMethod- », а не исключение / ошибку.
Özgür 26.08.2010 10:34:21
На самом деле это начинает проявляться в стандартном коде Apple. ++
Chris Trahey 17.06.2012 04:37:24
с компилятором LLVM 4 и выше вам даже не нужно этого делать. Вы можете просто определить их в своей реализации, не помещая их в расширение класса.
Abizern 23.07.2012 14:39:45
Если вы получаете предупреждения, о которых упоминает @Comptrol, то это потому, что вы определили метод ниже, а не над другим методом, который его вызывает (см. Ответ Энди) - и вы игнорируете эти предупреждения на свой страх и риск. Я допустил эту ошибку, и компилятор все перепутал, пока я не if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...вложил вызов, подобный этому: Тогда fWidthCombined всегда проходил как 0.
Wienke 28.07.2012 14:31:31
@ Wienke Больше не нужно беспокоиться о заказе. Последние версии LLVM найдут метод, даже если он появится ниже, где он вызывается.
Steve Waddicor 26.05.2013 11:03:20

Если вы хотите избежать @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
3.08.2010 23:56:50

все объекты в Objective C соответствуют протоколу NSObject , который поддерживает метод executeSelector : . Я также ранее искал способ создания некоторых «вспомогательных или частных» методов, которые мне не нужны были бы доступны на публичном уровне. Если вы хотите создать приватный метод без издержек и не определять его в заголовочном файле, тогда попробуйте ...

определите ваш метод с такой же сигнатурой, как в коде ниже ...

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

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

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

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

3
20.06.2013 15:03:43
Таким образом, вы не можете передать третий параметр.
Li Fumin 24.12.2011 15:14:22

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

1
8.08.2011 07:31:36

Вы могли бы использовать блоки?

@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

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

3
17.06.2012 04:35:28
Здесь вы создали глобальную переменную блочного типа, которая на самом деле не лучше, чем функция (и даже не является действительно закрытой, поскольку она не объявлена static). Но я экспериментировал с назначением блоков частным иварам (из метода init) - своего рода JavaScript-стиль - который также позволяет получить доступ к частным иварам, что невозможно из статических функций. Еще не уверен, что я предпочитаю.
big_m 2.11.2015 15:03:25

Еще одна вещь, о которой я не упоминал, упоминается здесь - Xcode поддерживает файлы .h с именем "_private". Допустим, у вас есть класс MyClass - у вас есть MyClass.m и MyClass.h, и теперь у вас также может быть MyClass_private.h. XCode распознает это и включит его в список «контрагентов» в помощнике редактора.

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"
2
14.02.2013 03:08:12

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

В некоторых случаях вам нужно будет объявить метод в продолжении класса (например, если используется селектор между продолжением класса и @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.

19
26.05.2013 09:45:48
+1 за использование полного имени класса в качестве префикса имени метода! Это гораздо безопаснее, чем просто подчеркивание или даже ваш собственный TLA. (Что, если закрытый метод находится в библиотеке, которую вы используете в другом из ваших проектов, и вы забыли, что вы уже использовали это имя где-то год или два назад ...?)
big_m 2.11.2015 14:55:42

Как говорили другие люди, определение приватных методов в @implementationблоке подходит для большинства целей.

На тему организации кода - мне нравится держать их вместе pragma mark privateдля облегчения навигации в Xcode

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
0
24.10.2018 23:32:19