Objective-C #import循环引用问题

13

我有以下代码:

#import <Foundation/Foundation.h>
#import "ServerRequest.h" // works even though this line is included
#import "ServerResponseRecord.h"

@protocol ServerRequestDelegate<NSObject>

-(void)request:(id)request gotResponseRecord:(ServerResponseRecord*)response;
-(void)request:(id)request gotError:(NSError*)error;

@end

它可以编译和正常运行。但是,如果我将方法声明替换为:

-(void)request:(ServerRequest*)request gotResponseRecord:(ServerResponseRecord*)response;
-(void)request:(ServerRequest*)request gotError:(NSError*)error;

我遇到了一个意外的语法错误:"error: expected ')' before 'ServerRequest'"。我能想到的唯一原因是ServerRequestDelegate.h和ServerRequest.h相互导入。然而,我不明白为什么在#import行中使用(id)request代码可以工作。我也不知道为什么会出现语法错误。

有人能提供一个好的解释吗?


1
https://dev59.com/lGkw5IYBdhLWcg3wQoWm 有一个明确的示例,展示了如何使用 @class 避免导入循环。 - bbum
2个回答

26

你已经暗示了解释:一个 #import 循环依赖。

我会做的第一件事是移除 #include,并在 @protocol 定义上面添加以下行:

@class ServerRequest;

这是一个前向类声明,可以帮助打破导入循环。请查看此SO问题以获取更多详细信息。Apple在此指南中也有简要说明。

基本上,#import一个文件会导致编译器将该文件的整个文本引入到相关文件中,虽然#import#include更智能,但这并不意味着您免于导入错误。 @class声明是一种告诉编译器类存在而不导入头文件的方法。当您只需要知道类名称而不关心其提供的方法时,可以使用它。通常,您应该在.h文件中使用@class,在.m文件中使用#import,其中您实际上与类进行交互。


1
是的,将代码移动到头文件中的@class声明更好,无论从哪个层面来看都是如此。但是,除非存在循环依赖而没有适当的前向声明,否则头文件中仍可能存在潜在的错误。 - Louis Gerbarg
1
这是真的,但事实上,在使用 id 作为类型时,包含头文件能够正常工作,但静态地将其表示为 ServerRequest* 时却不能工作,这表明头文件可能没问题,只有当编译器开始试图查找关于 ServerRequest 类的信息时才会出现问题。 - Quinn Taylor

-1

#import "loops" 不是问题。#import 与 #include 相同,只是它跟踪文件并确保预处理器仅在第一次读取它们。

通常,当您遇到这样的错误时,原因是包含文件中存在问题。因此,错误可能在 ServerResponseRecord.h 中,尽管实际上使用该对象可能会触发该错误。如果没有看到完整的头文件,则无法确定具体情况。


如果ServerResponseRecord出现了问题,为什么我把类型改成id后代码可以编译和运行得很好呢? - tba
1
由于预处理器是一个棘手的东西,有些扩展只会在某些情况下触发而不是其他情况。如果您仅查看文件本身,则假定ServerRequest和ServerResponseRecord具有有效定义,看起来很好。通过使用@class进行前向声明,您已经证明了这一点,这意味着您的其他头文件中存在潜在错误,只有在某些情况下才会触发。 - Louis Gerbarg

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