Путаница с заголовками и файлами реализации в Objective-C - PullRequest
25 голосов
/ 20 сентября 2009

Прежде всего, пожалуйста, прости глупость этого вопроса, но я не из C / C ++ фона. Мне немного неясно, какая разница в ролях между файлами .h и .m, когда дело касается свойств.

Я понимаю концепцию интерфейсов и вижу, что отчасти файл .h является интерфейсом для реализации, но мне неясно, что это:

  • Почему свойства / методы определены вне фигурных скобок {}?
  • Что я определяю в скобках, когда пишу что-то вроде этого:

    IBOutlet UITextField * numberField;

    Это определение поля в интерфейсе?

  • Когда я добавляю строки @Property в файлы .h, это фактические реализации свойства n auto или просто проект интерфейса? Если да, то является ли @syntesis фактической реализацией?

Я думаю, что моя самая большая путаница в том, что если я хочу свойство, я определяю, что мне нужно, в трех разных местах (1) в фигурных скобках интерфейса, (2) как @property вне фигурных скобок и (3) с @ синтез в .m файле. Кажется, это выглядит долго, но хорошо, если я смогу понять, что делают эти три части.

Ура, Крис.

Ответы [ 4 ]

79 голосов
/ 20 сентября 2009

Я отвечу на ваши вопросы ниже, но, возможно, лучший способ научиться этому материалу - это прочитать несколько удобных для пользователя заметок, предназначенных для новичков в языке, таких как учебное пособие Learn Objective-C более 100 * * кокоадевцентральный .

Пример

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

Вот возможный интерфейс для этого класса:

@interface Question : NSObject {
  NSString* questionStr;
  int numTimesAsked;
  int numCorrectAnswers;
}

@property (nonatomic, retain) NSString* questionStr;
@property (nonatomic, readonly) int numTimesAsked;
@property (nonatomic) int numCorrectAnswers;
@property (nonatomic) int numWrongAnswers;

- addAnswerWithTruthValue: (BOOL) isCorrect;
@end

Три фигурные скобки - это переменные экземпляра , и каждый экземпляр вашего класса будет иметь свои собственные значения для каждой из этих переменных. Все, что находится за скобками, но до @end является объявлением метода (включая объявления @property).

(Примечание: для многих объектов полезно иметь свойства retain, поскольку вы хотите избежать накладных расходов при копировании объекта и убедиться, что он не освобождается при его использовании. Допустимо retain и NSString, как в этом примере, но часто считается хорошей практикой использовать copy вместо retain, поскольку NSString* может фактически указывать на NSMutableString объект, который может позже измениться, если ваш код ожидает, что он останется прежним.)

Что @property делает

Когда вы объявляете @property, вы делаете две вещи:

  1. Объявление метода установки и метода получения в интерфейсе класса и
  2. Указывает, как ведут себя сеттер и геттер.

Для первого достаточно знать, что эта строка:

@property (nonatomic, retain) NSString* questionStr;

в основном то же самое, что и это:

- (NSString*) questionStr;                           // getter
- (void) setQuestionStr: (NSString) newQuestionStr;  // setter

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

Часть "в основном" в "в основном то же самое" - это дополнительная информация, предоставляемая такими ключевыми словами, как nonatomic и retain.

Ключевое слово nonatomic указывает на то, что они не обязательно поточнобезопасны. Общее ключевое слово retain указывает, что объект сохраняет любое установленное значение и освобождает предыдущие значения при отпускании.

Например:

// The correct answer to both questions is objectively YES.
Question* myQuestion = [[Question alloc] init];
NSString* question1 = [[NSString alloc] initWithString:@"Is pizza tasty?"];
// question1 has retain count of 1, from the call to alloc
myQuestion.questionStr = question1;
// question1 now has a retain count of 2
NSString* question2 = [[NSString alloc] initWithString:@"Free iPhone?"];
myQuestion.questionStr = question2;
// question1 has a retain count of 1, and question2 has retain count of 2

Если бы декларация @property для questionStr была взамен assign, тогда все операторы myQuestion.questionStr = вообще не внесли бы никаких изменений в число удержаний.

Вы можете прочитать немного больше о свойствах здесь .

Что IBOutlet и IBAction делают

Это в основном неиспользуемые слова, которые действуют только как способ сообщить Интерфейсному Разработчику, на какие части заголовочного файла обращать внимание. IBOutlet буквально становится пустой строкой, когда компилятор смотрит на нее, а IBAction становится возвращаемым значением void. Нам нужно, чтобы они работали с Interface Builder, поэтому они важны - просто не для компилятора.

Быстрая заметка о структурах C и стрелке против точечной записи

Кстати, часть данных объекта Objective-C очень похожа на структуру C. Если у вас есть указатель на структуру C, вы можете использовать обозначение стрелки -> для ссылки на определенную часть структуры, например:

struct MyStructType {
  int i;
  BOOL b;
};
struct MyStructType* myStruct;
myStruct->i = 3;
myStruct->b = TRUE;  // or YES in Objective-C.

Этот же синтаксис работает аналогично в Objective-C:

Question* question = [[Question alloc] init];
question->questionStr = @"Is this a long answer?";  // YES

Но когда вы сделаете это, за сценой будет происходить вызов метода no , в отличие от точечной записи. С точечной нотацией вы вызываете установщик (или получатель, если нет = после), и эти две строки одинаковы:

question.questionStr = @"Chocolate?";
[question setQuestionStr:@"Chocolate?"];

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

Что делает @synthesize

Теперь, когда вы приступите к реализации своего класса, @synthesize говорит что-то вроде: «убедитесь, что метод getter и setter реализован для этого свойства». Он не говорит "реализуй оба эти для меня", потому что компилятор достаточно вежлив, чтобы сначала проверить вашу собственную реализацию и заполнить только те части, которые вы пропустили. Вам совсем не нужно использовать @synthesize, даже если вы используете @property вне wazoo - вы всегда можете просто предоставить свои реализации для ваших сеттеров и геттеров, если вы в такой вещи.

Вы, вероятно, заметили в интерфейсе Question выше, что есть свойство, которое не переменная экземпляра (numWrongAnswers), что хорошо, потому что вы просто объявляете методы. В приведенном здесь примере кода вы можете увидеть, как это на самом деле работает:

@implementation Question

@synthesize questionStr, numTimesAsked, numCorrectAnswers;

- (void) setNumCorrectAnswers: (int) newCorrectAnswers {
  // We assume the # increases, and represents new answers.
  int numNew = newCorrectAnswers - numCorrectAnswers;
  numTimesAsked += numNew;
  numCorrectAnswers = newCorrectAnswers;
}

- (int) numWrongAnswers {
  return numTimesAsked - numCorrectAnswers;
}

- (void) setNumWrongAnswers: (int) newWrongAnswers {
  int numNew = newWrongAnswers - self.numWrongAnswers;
  numTimesAsked += numNew;
}

- (void) addAnswerWithTruthValue: (BOOL) isCorrect {
  if (isCorrect) {
    self.numCorrectAnswers++;
  } else {
    self.numWrongAnswers++;
  }
}

@end

Здесь происходит то, что мы подделываем переменную экземпляра с именем numWrongAnswers, которая была бы избыточной информацией, если бы мы сохранили ее в классе. Поскольку мы знаем numWrongAnswers + numCorrectAnswers = numTimesAsked всегда, нам нужно хранить только любые две из этих трех точек данных, и мы всегда можем думать в терминах другой, используя два значения, которые мы знаем , Суть в том, чтобы понять, что объявление @property на самом деле просто объявляет метод установки и получения, который обычно соответствует фактической переменной экземпляра - но не всегда. Ключевое слово @synthesize по умолчанию действительно соответствует фактической переменной экземпляра, так что компилятору легко заполнить реализацию за вас.

Причины иметь отдельные файлы .h и .m

Кстати, весь смысл объявления методов в одном файле (заголовочный файл .h) и определения их реализации в другом (.m или файл методов) состоит в том, чтобы помочь отделить код. Например, если вы обновляете только один файл .m в своем проекте, вам не нужно перекомпилировать другие файлы .m, поскольку их объектный код останется прежним - это экономит время. Другое преимущество состоит в том, что вы можете использовать библиотеку, которая включает только файлы заголовков и предварительно скомпилированный объектный код, или даже динамические библиотеки, где вам нужен файл заголовка, чтобы компилятор знал, какие методы существуют, но эти методы даже не связаны в с вашим исполняемым файлом. Эти преимущества трудно оценить, когда вы впервые начинаете кодировать, но через некоторое время становятся полезными только логическая разбивка и инкапсуляция реализации.

Надеюсь, это полезно!

1 голос
/ 20 сентября 2009
  1. методы определены вне фигурных скобок, поскольку скобки предназначены для инкапсуляции состояния объекта, которое можно утверждать, не включает методы экземпляра или класса.

  2. То, что вы определяете в фигурных скобках, - это переменные экземпляра, на которые можно ссылаться как self.ivar

  3. Директивы @property и @synthesize просто устанавливают методы доступа для переменных экземпляра, поэтому вы можете установить их, выполнив self.ivar = someVar. Другими словами, он устанавливает «точечный синтаксис» для использования.

и для ответа на последний вопрос: чтобы определить свойство или переменную экземпляра, просто объявите его в своем файле .h как переменную внутри фигурных скобок. Чтобы настроить методы доступа для того же свойства, вам нужно сделать ОБА @property и @synthesize.

0 голосов
/ 20 сентября 2009

Переменные внутри скобок определяют физическую структуру вашего класса. Это фактические переменные экземпляра, которые хранят информацию.

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

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

0 голосов
/ 20 сентября 2009
  1. Это просто синтаксис Objective C, методы и @property вне {} и переменные внутри {}.

  2. @ свойство - это способ сообщить, что вы собираетесь писать геттер и сеттеры (что-то вроде принудительного применения), но вы можете написать геттер / сеттер без установки их @property. @property находится в файле .h, потому что его объявление. И почему он находится за пределами {}, ну, как я уже говорил, это просто синтаксис, что мы можем сделать?

  3. @ Синтез в действительности реализует геттер и сеттеры, если вы не синтезируете, но вы установили их @property, вы должны реализовать эти геттеры и сеттеры своими руками. И @synthesis находится в файле .m, потому что его реализация.

Что-нибудь еще для прочтения этой темы можно найти здесь.

http://theocacao.com/document.page/510

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...