Правильный способ использования Objective C

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

интерфейсы и протоколы

Я понимаю понятия интерфейсов и реализации, поскольку они относятся к абстракции. Однако доминирующий шаблон, который я вижу в примере кода Objective-C, следующий...

@interface Foo : NSObject
{
  some ivars decls
}
some methods decls
@end

код >

И затем код клиента в форме...

Foo* f = [[Foo alloc] init];

код >

Мне кажется странным. Foo кажется реализацией в моем сознании (независимо от неудачного наименования ключевого слова @interface). Интерфейсы, на мой взгляд, не выставляют переменные экземпляра. Клиент этого класса не должен быть ознакомлен с деталями моей реализации.

Objective-C имеет ключевое слово @protocol, которое, на мой взгляд, больше похоже на интерфейс, чем ключевое слово @interface. Это позволяет вам определять методы и/или свойства и что они. Никакой реализации. Нет переменных экземпляра. Просто интерфейс.

@protocol Foo <NSObject>
  some methods
  maybe some properties
@end

@interface FooProvider : NSObject
  + (FooProvider*) sharedProvider;
  - (id<Foo>) fooWithBlahBlah;
@end

код >

Таким образом, код клиента будет иметь форму...

id<Foo> f = [[FooProvider sharedProvider] fooWithBlahBlah];
[f whatever];

код >

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

Некоторые рекомендации будут оценены.

Спасибо, Roman

Ответ 1

Я вижу, откуда вы пришли, в отношении наименования ключевого слова @interface, но Objective-C использует два ключевых слова @interface и @implementation, чтобы различать декларацию класса (интерфейс) и его определение (реализация). Часть @interface обычно помещается в заголовочный файл класса, а часть @implementation - в исходный файл. Я согласен с вами на 100%, что внутренние переменные экземпляра действительно не должны отображаться в файле заголовка. Я не уверен, что привело к такому решению, но я думаю, что это связано с наследованием объектов. Когда вы наследуете родительский класс:

#import "ParentClass.h"
@interface MyClass : ParentClass
...

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

Вы также правильно относитесь к использованию @protocol, поскольку он по существу определяет интерфейс, к которому должен придерживаться класс. Протоколы кажутся Objective-C ответами на множественное наследование.

В моем опыте с Objective-C протоколы не используются очень часто. Они имеют свое место, но большая часть кода использует базовые ключевые слова @interface и @implementation для определения класса, а протоколы используются только тогда, когда требуется дополнительная функциональность, которая не совсем принадлежит иерархии классов, но все еще нужно делиться между несколькими классами.

Если вы ищете руководство, я бы сказал:

  • Признать имена ключевых слов @interface и @implementation, даже если это не совсем то, что вы ожидаете. При программировании в Objective-C, пить Kool-Aid и видеть эти ключевые слова так, как планировали разработчики языка.
  • Используйте протоколы, когда они имеют смысл в вашем коде, но не злоупотребляйте ими, пытаясь сделать Objective-C более похожим на другой язык. Это может сработать, но другие люди будут съеживаться, когда они увидят ваш код, и это затруднит сотрудничество.
  • Продолжайте задавать такие хорошие вопросы. Вы подняли некоторые очень важные моменты!

Ответ 2

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

Java была начата в 1990 году внутри Sun. Objective-C уже был выпущен в 1986 году. Так что, если что-либо, терминология интерфейса Java противоречит Objective-C, а не наоборот. Однако интерфейс имеет гораздо более длительную историю как термин, чем любой из этих языков.

Идея @interface в Objective-C описана в Objective-C 2.0 Справочник по языку программирования:

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

методы экземпляра являются методами класса, являются общедоступными, если они объявлены в интерфейсе. если они объявлены в @implementation, они являются частными.

Реализация идет в @implementation. Возможно, вас смущает то, что Java не имеет понятия раздела объявления класса, например, в Objective-C. Объявление класса объявляет интерфейс класса общим способом без специфики реализации. В @implementation есть фактическая реализация, которая представляет собой детали того, как реализуется класс @interface.

Неверно, что Objective-C отличается от Java, это просто другое.

Но вы правы, протоколы являются ближайшим аналогом java-интерфейсов в Objective-C, которые вы собираетесь найти. Это не суперкласс для чего-либо и не имеет никакой связанной реализации. Вы предоставляете протоколы так же, как и интерфейсы в Java.

Однако обратите внимание, что протоколы не так распространены, как классы в Objective-C. Это должно помочь вашему мышлению.

Ответ 3

Никто еще не объяснил, почему переменные экземпляра появляются в @interface класса Objective-C. Объекты реализуются как структуры C, которые содержат переменные экземпляра. Когда вы создаете новый подкласс, новый тип структуры определяется со всеми надклассами ivars, за которыми следует новый класс ivars. Структура суперкласса должна содержать ивары своего суперкласса, а затем "чередуется полностью вниз" до структуры, которая представляет ивары базового класса.

В случае NSObject (и действительно Object) структура базового класса содержит только один ivar - указатель, называемый isa, в структуру Class, представляющую этот класс объекта. Поэтому, если бы я хотел подклассифицировать его следующим образом:

@interface GLObject : NSObject {
  int x;
}
@end

Мне нужно знать, как создать структуру, которая выглядит так:

{
  Class isa;
  int x;
}

поэтому расположение ivar родительского класса (и по индукции любого класса) должно быть частью общедоступного контракта класса, то есть части его @interface. Это может быть не очень красиво, но есть.

Ответ 4

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

@interface Foo : NSObject
// some methods but no ivars
+ (Foo*) fooWithBlahBlah
@end

И файл реализации...

@implementation Foo

+ (Foo*) fooWithBlahBlah {
    return [[SomeDerivedFoo alloc] init];
}

@end

Где SomeDerivedFoo объявляется внутренне невидимым для клиентов...

@interface SomeDerivedFoo : Foo
{
    // instance vars
}
// overrides of methods

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

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

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

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

Ответ 5

Альтернативой, если вы не можете поддерживать 64-битную рабочую среду и исключительно синтезировать ivars, заключается в создании id ivar в вашем классе. Если вам нужно вернуться в класс после отправки и добавить дополнительные переменные экземпляра, вы можете использовать этот id в качестве контейнера для них.

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

Набрав ivar как id, вы ничего не обещаете клиентскому коду (в любом случае это должно быть сделано @private).

Я бы предположил, что все ваши публичные классы действуют как прокси-серверы для частных реализаций, которые будут немного неуправляемы. Имейте в виду, что если вы это сделаете, вы можете использовать пересылку времени выполнения, чтобы уменьшить количество кода, который вы должны записать в открытом классе. См. -[NSObject forwardingTargetForSelector:]. Он задокументирован в 10.5 Примечания к выпуску фундамента