与Objective-C中的头文件和实现文件混淆

Owe*_*wen 25 iphone properties objective-c

首先,请原谅这个问题的愚蠢,但我不是来自C/C++背景.我有点不清楚.h和.m文件在属性方面的角色差异.

我理解接口的概念,我看到.h文件部分是实现的接口,但我不清楚的是:

  • 为什么在{}括号之外定义属性/方法?
  • 当我写这样的东西时,我在括号中定义了什么:

    IBOutlet UITextField*numberField;

    这是接口中的字段定义吗?

  • 当我将.Property行添加到.h文件时,这些是auto属性的实际实现还是只是一个接口蓝图?如果是@syntesis那么实际的实现呢?

我想我最大的困惑似乎是,如果我想要一个属性,我在接口支撑中的三个不同位置(1)定义我需要的东西,(2)在大括号外面的@property和(3)在@synthesis中定义.m文件.这看起来很长,但如果我能弄清楚这三个部分的作用,那就好了.

干杯,克里斯.

Tyl*_*ler 79

下面我将回答你的问题,但也许是为了了解这种东西的最佳方式是阅读专为新的人的语言,比如一些人性化的笔记的学习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
Run Code Online (Sandbox Code Playgroud)

大括号内的三个变量是实例变量,类的每个实例都将为每个变量都有自己的值.大括号之外的所有内容都是@end方法的@property声明(包括声明).

(旁注:对于许多对象,拥有retain属性很有用,因为你想避免复制对象的开销,并确保在你使用它时不会释放它.这对于retain一个NSString例子来说是合法的,但通常认为使用copy而不是retain因为NSString*实际上可能指向一个NSMutableString对象,这可能会在您的代码期望它保持不变时更改.)

是什么@property呢

当你宣布一个时@property,你做了两件事:

  1. 在类的接口中声明一个setter和getter方法,和
  2. 指示setter和getter的行为方式.

对于第一个,它足以知道这一行:

@property (nonatomic, retain) NSString* questionStr;
Run Code Online (Sandbox Code Playgroud)

与此基本相同:

- (NSString*) questionStr;                           // getter
- (void) setQuestionStr: (NSString) newQuestionStr;  // setter
Run Code Online (Sandbox Code Playgroud)

在标题中.你真的在宣布这两种方法; 您可以直接调用它们,或使用点符号作为快捷方式为您调用它们.

"基本相同"中的"基本"部分是由nonatomic和等关键字给出的额外信息retain.

该nonatomic关键字表示他们并不一定是线程安全的.common 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
Run Code Online (Sandbox Code Playgroud)

如果@property声明questionStr是assign相反的,那么所有的myQuestion.questionStr =陈述都不会对保留计数做出任何改变.

您可以在这里阅读更多有关房产的信息.

做什么IBOutlet和IBAction做什么

这些基本上是无操作词,它们只是一种告诉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.
Run Code Online (Sandbox Code Playgroud)

这个语法在Objective-C中的工作方式相同:

Question* question = [[Question alloc] init];
question->questionStr = @"Is this a long answer?";  // YES
Run Code Online (Sandbox Code Playgroud)

但是当你这样做时,幕后没有方法调用,不像点符号.使用点表示法,您将调用setter(或者如果之后没有=则获取getter),并且这两行是相同的:

question.questionStr = @"Chocolate?";
[question setQuestionStr:@"Chocolate?"];
Run Code Online (Sandbox Code Playgroud)

避免使用箭头符号支持点表示法通常是个好主意,因为点符号可以强制执行有效状态 - 例如,您的类具有的指针始终保留.您甚至可以通过将实例变量声明为不允许其他人使用箭头符号@private; 如果你@property为它声明一个,他们仍然可以使用getter和setter来访问它.

@synthesize的作用

现在,当您开始实际实现您的类时,请@synthesize说"确保为此属性实现getter和setter".它没有说"为我实现这两个",因为编译器很有礼貌,可以先检查你自己的实现,只填写你错过的部分.@synthesize即使您使用@property了wazoo ,也不必使用它 - 如果您正在使用这种东西,您可以随时为您的setter和getter提供实现.

您可能在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
Run Code Online (Sandbox Code Playgroud)

这里发生的一件事是我们伪造一个名为的实例变量numWrongAnswers,如果我们将它存储在类中,这将是冗余信息.由于我们始终知道numWrongAnswers+ numCorrectAnswers= numTimesAsked,我们只需要存储这三个数据点中的任何两个,我们总是可以通过使用我们知道的两个值来考虑另一个数据点.这里的要点是要理解@property声明实际上只是声明一个setter和getter方法,它通常对应于一个实际的实例变量 - 但并非总是如此.在@synthesize默认情况下,关键字不符合实际的实例变量,这样很容易为编译器为您的实现填写.

有单独.h和.m文件的原因

顺便说一下,在一个文件(.h头文件)中声明方法并在另一个文件(.m或方法文件)中定义它们的实现的全部要点是帮助解耦代码.例如,如果只更新.m项目中的一个文件,则不必重新编译其他.m文件,因为它们的目标代码将保持不变 - 这样可以节省时间.另一个优点是你可以使用一个只包含头文件和预编译目标代码的库,甚至是你需要头文件的动态库,这样编译器就知道存在哪些方法,但这些方法甚至没有链接到与您的可执行文件.当您第一次开始编码时,很难理解这些优点,但只是在短时间内实现的逻辑分解和封装变得有用.

我希望这对你有所帮助!

  • 实际上,NSString属性应标记为副本. (3认同)
  • 对整体令人印象深刻的帖子的一些建设性批评:(1) NSString 属性确实应该标记为保留,(2) 我认为在这种情况下不需要关于为什么需要 .m 和 .h 文件的内容,因为最好坚持使用到底是怎么回事。我喜欢您谈论 IBOutlet 和 IBAction 的目的。 (2认同)