Стиль интерфейса какао

Я работаю над проектом по созданию приложения для iPhone. К нам на несколько недель приехал консультант по Какао. Он показал мне интересную идиому Какао, касающуюся интерфейсов, но между нами существовал сложный языковой барьер, и он не мог объяснить Зачем, что это было сделано или где это было задокументировано, чтобы я мог узнать больше самостоятельно. Я вошел в режим обезьяны и просто использовал стиль, который он предпочитает. Но меня чертовски раздражало то, что я не знаю больше об истории этого стиля. Это определенно НЕ неформальный протокол. Конечно же, глядя на некоторый заголовков Cocoa API, я иногда вижу, что стиль, который он утверждал, был способом «Какао». Вот пример (обратите внимание на аксессоры, мутаторы и т. д., У каждого есть собственное объявление интерфейса без забавных фигурных скобок):

@interface AViewController : UIViewController <UITextViewDelegate> {

@public
    UITableView *tableView;
@private
    NSUInteger someIndex;
}

@property (nonatomic, retain) ...
@end

@interface AViewController (AViewControllerCreation)

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil withController:(id)controller;

@end

@interface AViewController (AViewControllerMutator)

- (void) doSomeSettingStuff;

@end

@interface AViewController (AViewControllerAccessor)

- (NSString *)doSomeAccessorStuff;

@end

@interface AViewController (AViewControllerAction)

- (IBAction)cancel:(id)sender;

@end

@interface AViewController (AViewControllerTableViewDelegate)  <UITableViewDelegate, UITableViewDataSource>

@end

Вы можете увидеть этот стиль настройки интерфейса в NSButton, NSControl и т. д. Интересно, что соответствующие классы, такие как UIButton, UIControl, НЕ используют эту идиому. Хм, вероятно, они появились после того, как я предполагаю, что UIKit был создан после AppKit. Так является ли эта идиома «старой шляпой»? Кроме того, есть ли причина для этого, кроме стиля? Это хороший стиль? Плохо? Есть документы, которые касаются этого? Спасибо всем.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
3 715
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Это то, что в Objective-C называется «категориями». Категории позволяют иметь несколько блоков @interface и @implementation для одного и того же класса. Это работает даже в той степени, в которой вы можете добавлять методы в классы в стандартных фреймворках Apple, например. добавление категории в NSString для добавления в нее новых методов. Категории могут добавлять методы, но не переменные экземпляра, поэтому в вашем примере первый @interface - это объявление основного класса, а все остальные - категории в классе AViewController.

Это ни в коем случае не «старая шляпа», но ваш пример доводит использование категорий до довольно причудливой крайности. Категории имеют смысл везде, где есть логический смысл разбить реализацию класса на несколько блоков, например, если у класса есть набор методов, которые логически распадаются на две или более группы. Они также иногда используются для объявления псевдоприватных методов, помещая категорию @interface с именем "private" в тот же файл, что и @implementation. Динамическая отправка ObjC означает, что не существует такой вещи, как частный метод, но этот подход позволяет избежать публикации имен методов, которые вы бы предпочли, чтобы люди не использовали.

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

Спасибо, Том. Кстати, я удалил много методов, так что это чисто педантично. Я ДЕЙСТВИТЕЛЬНО понимаю, почему ему нравится группировать вещи (например, аксессоры / мутаторы). Опять же, посмотрев на заголовки NSButton и UIButton, я вижу, что используются оба. Но ваши ответы указывают на то, что на самом деле это категории. Лучший ответ еще Том!

Rob 16.12.2008 20:37

Я не знаю; Для меня это очень похоже на неформальные протоколы, в основном для делегатов. См. Страницы 297 - 298 в Программирование какао в Mac OS X, 3-е издание. Протоколы реализованы с помощью категорий ... И, честно говоря, в вашем образце они явно чрезмерно используются.

Это один из способов реализации неформального протокола (Obj-C 2.0 также имеет новые дополнительные методы протокола), но такие вещи, как методы инициализации, средства доступа и мутаторы, не будут частью протокола.

Marc Charbonneau 16.12.2008 20:27

Этот пример очень странный и определенно поднимет красный флаг для любого опытного программиста Cocoa.

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

В этой ситуации можно использовать ключевое слово #pragma mark <label>. что позволяет группировать похожие методы в реализации. Я думаю, это то, к чему вы стремитесь, хотя вам не нужно переусердствовать в создании групп. Например, в классе оконного контроллера у меня будет #pragma mark API, #pragma mark NSWindowController Overrides, #pragma mark NSObject Overrides, #pragma mark NSWindow Delegate Methods и так далее. Это помогает мне очень быстро найти метод, который я ищу, в Xcode и перейти к нему, хотя это всего лишь вопрос стиля, так что вы действительно можете использовать его, как сочтете нужным.

Привет, Марк. Да, мне нравится #pragma, и я уже использую его в xcode, это действительно помогает! Но кажется, что это приносит пользу только с организационной точки зрения. Да, не я придумал эту идиому, придумал он! Сначала мне это не понравилось, но я согласился. Он ЕСТЬ в NSButton, NSControl и т. д., И гм, это код Apple;)

Rob 16.12.2008 20:33

Что касается ответа Джона Руди. Это правда, что неформальные протоколы реализованы как категории, но обычно они категории в NSObject. Поскольку неформальный протокол может использоваться практически любым объектом, это должна быть категория класса, от которого наследуется принимающий объект, и почти все будет унаследовано от NSObject. Вы могли бы привести доводы в пользу неформального протокола как категории для какого-то другого класса в определенных ситуациях, но это необычный подход и определенно не «путь какао».

Хорошо, я считаю, что ответ Тома пока самый полезный. Однако, поскольку все, кажется, думают, что это чрезмерное использование категорий, я снова взглянул на NSButton. Ниже представлена ​​убитая версия шапки:

@interface NSButton : NSControl <NSUserInterfaceValidations>

- (NSString *)title;
- (void)setTitle:(NSString *)aString;
...
...
@end

@interface NSButton(NSKeyboardUI)
- (void)setTitleWithMnemonic:(NSString *)stringWithAmpersand;
@end

@interface NSButton(NSButtonAttributedStringMethods)
- (NSAttributedString *)attributedTitle;
- (void)setAttributedTitle:(NSAttributedString *)aString;
- (NSAttributedString *)attributedAlternateTitle;
- (void)setAttributedAlternateTitle:(NSAttributedString *)obj;
@end

@interface NSButton(NSButtonBezelStyles)
- (void) setBezelStyle:(NSBezelStyle)bezelStyle;
- (NSBezelStyle)bezelStyle;
@end

@interface NSButton(NSButtonMixedState)
- (void)setAllowsMixedState:(BOOL)flag;
- (BOOL)allowsMixedState;
- (void)setNextState;
@end

@interface NSButton(NSButtonBorder)
- (void) setShowsBorderOnlyWhileMouseInside:(BOOL)show;
- (BOOL) showsBorderOnlyWhileMouseInside;
@end

@interface NSButton (NSButtonSoundExtensions)
- (void)setSound:(NSSound *)aSound;
- (NSSound *)sound;
@end

Итак, если они используют категории для организации NSButton в разделы: (NSButtonMixedState) (NSButtonBorder) выше, у которых действительно есть только пара операций, почему это используется для организации средств доступа / мутаторов в плохом стиле? Конечно, моим первым примером был глупый педантичный интерфейс, но цель разделения операций на группы остается прежней.

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

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

Одна действительно веская причина для наличия общедоступной категории, которую Apple часто использует, - это расширение класса функциональностью, которая существует в структуре категории, но не существует в структуре, которая определяет класс. Например, NSString определен в Foundation.framework, но категории в NSString, определяющие методы рисования NSString на экране, определены в AppKit.framework.

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

Как отмечает Том, действительно большая реализация с несколькими исходными файлами (но с одним заголовком) также является хорошим кандидатом :)

Я просто хотел бы добавить к исходному ответу Тома: как правило, при объявлении частных методов лучше использовать расширение класса, чем категорию класса. Таким образом, вы можете реализовать расширение в том же блоке @implementation, что и общедоступные методы, без предупреждения об «отсутствующей реализации категории». Пример:

// .h file

@interface Foo : NSObject
-(void)publicMethod;
@end

// .m file

@interface Foo () 
// Notice the empty paren; this is how you define
// a class extension, which is not the same as a category
-(void)somePrivateMethod;
@end

@implementation Foo
#pragma mark Public methods
-(void)publicMethod;
{ ... }

#pragma mark Private methods
-(void)privateMethod;
{ ... }
@end

Другие вопросы по теме