Objective-C: 关于头文件声明的简单问题

3
一个iPhone开发初学者有一个非常简单的问题:为什么要在头文件中声明方法,然后在实现文件中填写呢?这样做必须吗?
此外,我知道应该在头文件的@interface中声明变量,但是它们为什么只有有时候用@property标签重复呢?这是给其他类读取或写入变量的变量(因此它们自动创建getter和setter方法)吗?
祝好。
5个回答

10
在.h文件中声明一个方法被称为前向声明。如果你在.h文件中声明了一个方法头,那么编译器会在实际链接之前知道该方法的名称、参数和返回类型。你只能在.m文件中编写方法体。但是这个方法只能被在该文件中其后声明的方法使用。但是如果你在头文件中声明方法,那么就不是问题了。因为在编译器第一次通过时,方法签名将被所有人所知,并且在第二次通过时将被链接。
@property 和 @synthesize 标记用于创建自动 getter 和 setter(或 Objective-C 术语中的 Accessors 和 Mutators),但不仅仅如此。在 iOS 中,您必须手动进行内存管理(应该会在 iOS5 中更改,正如 Apple 所承诺的)。在 @property 标记中,您可以告诉内存在赋值期间的行为方式。
iOS 通过维护保留计数来跟踪对象的内存管理。当您分配一个对象时,它的保留计数变为1。然后,您可以通过retain方法手动增加保留计数(例如[myObj retain]),也可以通过release方法手动减少保留计数(例如[myObj release])。当保留计数降至0时,iOS 将从内存中删除该对象。使用@property标记,您可以定义在赋值期间如何管理保留计数。例如,@property标记中最常用的两个参数是:
@property (nonatomic, retain)
@property (nonatomic, assign)

在第一种情况下,当进行赋值操作时,对象的保留计数会自动增加1(例如:self.myObj = anotherClass.anotherObjOfSameClass;),而在后一种情况下,保留计数不会增加。


当然,一切都是正确的,但是可能初学者不需要过于关注引用计数,因为根据公开声明和LLVM的更改,编译器将在下一个版本中为您完成它。理解是有益的,就像了解所使用技术的底层技术一样,但我认为这将不再是基础知识。相关地,它在很大程度上用“strong”和“weak”取代了“retain”和“assign”,以指示您是否需要强链接(如保留)或弱链接(类似于分配,但自动清零)。因此,它是目的而非实现。 - Tommy
2
使用 Objective-C 3.0,不需要声明“private”方法,顺序也不再重要。 - Binarian

4
任何C语言中头文件的主要目的是将实现与可用方法分开。也就是说,您在头文件中定义类的模板,以便使用您的代码的人可以像“哦,我想使用这个和这个方法并且现在我知道如何实例化这个类的对象”一样简单。这一切都是关于抽象化的。 :-)

我不确定,但似乎@property标签是用于生成getter和setter方法的。我实际上从未进行过iOS开发。 - Vinay
@synthesize可选择生成getter / setters的实现。@property声明它们,不需要@synthesize。 - bbum
啊,好的,这样就更有意义了。 - Vinay

3
我猜这只是为了制作两个文件,一个用于公共API(.h),另一个用于你的逻辑和实现(.m),以便隐藏不让其他人看到。同时,属性标签有助于为变量创建getter和setter。
在头文件中使用@property,然后在实现中使用@synthesize,这将允许您通过getter和setter进行访问。
更多阅读,请参考:

http://developer.apple.com/library/mac/#referencelibrary/GettingStarted/Learning_Objective-C_A_Primer/_index.html#//apple_ref/doc/uid/TP40007594

提供信息,您也可以像这样做,但不建议这样做

#import <UIKit/UIKit.h>

@interface FileSystemDemoViewController : UIViewController {

    UITextView *actorListBox;
    NSArray *dataToShow;
}

@property (nonatomic, retain) IBOutlet UITextView *actorListBox;
@property (nonatomic, retain) NSArray *dataToShow;

-(IBAction) covertToAscending:(id)sender;
-(IBAction) covertToDescending:(id)sender;

@end

@implementation
...Your implementation here...
@end

以上所有代码都在.m文件中实现。
再次强调,不建议使用此方法
编程愉快。

3
为什么要将.h和.m文件分开?
历史上,在C语言中,如果你包含了一个.h文件,它几乎等效于把这个文件粘贴到包含它的文件中。实际上,所有声明都会在使用它们的每个文件中重复。在C语言中,你可以重复声明多少次都没关系,只要它们没有更改。然而,重复定义会导致函数多份拷贝和一般的问题。因此,你不能包含整个模块的代码,只能包含其声明。
我认为Objective-C更加聪明,如果想要的话,你甚至可以不用.m文件,把所有的代码都放在.h文件里。你也可以只在.m文件中定义一个类而没有.h文件,但是你不能真正让整个项目这样工作,因为没有东西能够访问其他内容。
然而,把声明和定义分开仍然是一个好习惯。这就像在餐厅有一个菜单,而不是必须回到厨房去看正在做什么。.h文件是模块内容如何被其他模块使用的简短摘要。
为什么有些实例变量有属性而有些没有?
对的,其中一部分原因是实例变量并不自动向其他类公开。属性默认是公开的,但不一定如此。你可以将属性设置为私有属性。
使用属性的另一个好处是自动内存管理。如果使用了retain属性,当你将一个值赋给一个属性时,这个值的保留计数会增加,先前值的保留计数(如果有的话)则会减少。这非常方便,避免了错误。即使其他类不访问实例变量,也很有用。如果不使用属性,你必须确保在实例方法内做适当的保留和释放操作,以避免泄漏内存。属性还有其他自动行为,比如锁定或拷贝等。
最后,你可以通过属性特性和@synthesize关键字获得内置行为。但你也可以决定不使用@synthesize关键字,为你的属性编写自定义getter和setter。它们可以做一些花哨的事情,比如在更改属性时更新相关的实例变量。

1

正如所述,.h和.m文件最初的原因是C编译器在独立处理每个源文件时,在连接时才会将它们连接在一起。因此需要一些机制来传播声明,以便可以对每个文件进行单独的编译器错误和警告检查,但定义保留在一个位置,以便链接器确保公共资源最终汇聚到公共位置。

随着现代运行时的出现,.h文件和.m文件之间的区别更应该被视为接口和实现之间的区别。接口是您向外界公布的内容。实现是仅供该类知道的内容。与外部世界的唯一契约是由接口定义的。

像大多数面向对象语言一样,Objective-C采用了为对象属性获取和设置器的概念。对象没有访问其他对象的实例变量,而是询问“这个值是什么?”或“请将那个的值设置为这个”。在Objective-C 2.0之前,您必须自己编写获取器和设置器,这引入了大量重复的样板代码。最初 @property 的目的是在接口中声明获取器或设置器。@synthesize用于为声明为@property的内容生成默认实现。

从新的运行时开始,不需要在接口中声明实例变量,可以完全在实现中保留它们。例如:

SomeClass.h:

@interface SomeClass: NSObject

- (void)doSomeTask;

@end

SomeClass.m:

// declare a category with some unexposed properties
@interface SomeClass ()
@property (nonatomic, assign) NSMutableArray *arrayForMe;
@end

@implementation SomeClass

@synthesize arrayForMe; // you can now use the property self.arrayForMe,
                        // even though you didn't declare a backing
                        // instance variable

- (void)doSomePrecursorTask
{
    // ...
}

- (void)doSomeTask
{
    // as a fact of implementation, I need to do something else
    // first. Because it's an implementation specific, I don't
    // want to put it in the declared interface

    [self doSomePrecursorTask];
    // ...
}

@end

新的运行时在iOS、Lion和64位Snow Leopard上可用。作为良好实践的一部分,现在最好将实例变量从头文件中移出。只需将公共接口放在那里,并将它们视为一种形式化和沟通的方式。我预计,在头文件中使用实例变量很快就会显得像NSEnumerator一样过时。

我正在寻找一个特定的原因,那就是“对象无法访问其他对象的实例变量”。我认为这是最完整的答案。 - Praveen S
是的。可能需要注意的是,在旧的运行时中,当处理子类时,编译器需要知道相关超类使用的所有实例变量的总大小 - 这就是为什么它们在头文件中的原因。在新的运行时中,它不需要知道这一点,因此没有理由将它们放在头文件中。你仍然可以这样做,但我建议不要这样做,因为这相当于发布一个特定于实现的实现细节。 - Tommy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接