Ответ 1
Я отвечу на ваши вопросы ниже, но, возможно, лучший способ изучить этот материал - прочитать некоторые удобные для пользователя ноты, предназначенные для людей, знакомых с языком, таких как обучаемый Objective-C на cocoadevcentral.
Пример
Я хотел бы помочь ответить на ваши вопросы с помощью примера (я люблю учиться на примере). Скажем, вы учитель, пишущий программу, которая задает студентам конкретный вопрос "да/нет", и отслеживает, сколько из них правильное и сколько учеников оно задало.
Вот возможный интерфейс для этого класса:
@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
a NSString
, как в этом примере, но часто считается хорошей практикой использовать copy
вместо retain
, так как NSString*
может на самом деле укажите объект NSMutableString
, который позже может измениться, когда ваш код ожидает, что он останется прежним.)
Что @property
делает
Когда вы объявляете @property
, вы делаете две вещи:
- Объявление метода setter и getter в интерфейсе класса и
- Указание того, как ведут себя сеттер и геттер.
Для первого достаточно знать, что эта строка:
@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
сделать
Это в основном слова no-op, которые действуют только как способ сказать Interface Builder, на какие части файла заголовка обратить внимание. 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
Но когда вы это делаете, не происходит вызова метода за кулисами, в отличие от точечной нотации. С помощью точечной нотации вы вызываете сеттер (или getter, если нет = после), и эти две строки одинаковы:
question.questionStr = @"Chocolate?";
[question setQuestionStr:@"Chocolate?"];
Часто рекомендуется избегать обозначений стрелок в пользу точечной нотации, поскольку точечная нотация позволяет вам применять правильное состояние - например, что указатели, которые имеет ваш класс, всегда сохраняются. Вы даже можете запретить другим использовать нотацию стрелки, объявив переменные вашего экземпляра как @private
; они могут использовать getter и setter для доступа к нему, если вы объявите для него @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
- это просто объявление метода setter и getter, который обычно соответствует фактической переменной экземпляра, но не всегда. Ключевое слово @synthesize
по умолчанию соответствует фактической переменной экземпляра, так что компилятор легко заполнит вашу реализацию.
Причины наличия отдельных файлов .h
и .m
Кстати, вся точка объявления методов в одном файле (заголовочный файл .h
) и определение их реализации в другом (файл .m
или методов) помогает развязать код. Например, если вы обновляете только один файл .m
в своем проекте, вам не нужно перекомпилировать другие файлы .m
, так как их объектный код останется прежним - это экономит время. Еще одно преимущество заключается в том, что вы можете использовать библиотеку, которая включает только файлы заголовков и предварительно скомпилированный объектный код или даже динамические библиотеки, где вам нужен файл заголовка, чтобы компилятор знал о том, какие методы существуют, но эти методы даже не связаны в с вашим исполняемым файлом. Эти преимущества трудно оценить при первом запуске кодирования, но только логическое разбиение и инкапсуляция реализации становятся полезными через некоторое время.
Я надеюсь, что это полезно!