无法找到协议声明

64

我有两个视图控制器A和B,它们都将对方定义为自己的代理。

当我除了在头文件开头定义协议并导入其他头文件之外什么都没做时,我收到两个错误,类似于-

无法找到"BDelegate"协议声明,在A.h中显示(我写了)无法找到"ADelegate"协议声明,在B.h中显示(我写了)

网上搜索后,人们写道循环包含头文件可能会导致这些问题。他们建议使用#include@class声明,例如-

@class A

而不是

#import A.h

#import B.h 中:

我已经尝试了几乎所有这些导入、@classes#include 的组合,但仍然无法摆脱警告。此外,在线上的解决方案建议将 #import 移动到 .m 文件中,但也没有帮助。部分原因是在线上的解决方案有点模糊 - 如果你能把它拆分开来就太好了。

有什么建议可以修复这个问题吗?


-- BigViewController.h --

#import "BaseViewController.h"
#include "BaseViewController.h"

@class BigViewController;

@protocol BigViewControllerDelegate
-(void) BigViewController:(BigViewController *) bigView;
@end

@interface BigViewController : UIViewController <BaseViewControllerDelegate>
{    
     //delegate
     id <BigViewControllerDelegate> delegate;

ivars...    
}

@properties...
@end

--------------------------------------------------

-- BaseViewController.h --

#<UIKit/UIKit.h>

#import "BigViewController.h"
#include "BigViewController.h"

@class BigViewController;

@protocol BaseViewControllerDelegate
- (void) setParametersWithItemChosen:(Item *) item;
@end

@interface BaseViewController : UIViewController <...BigViewControllerDelegate...>
{

   ivars...

   //delegate
    id <BaseViewControllerDelegate> delegate;
}

@properties...
@end

1
也许你可以发布一些相关的代码(精简后显示你正在定义什么)。 - PengOne
我刚刚发布了一些内容。缩进非常奇怪 - 抱歉! - Ak1
将来请使用顶部的“{}”按钮来包含代码,否则请手动使用反引号进行格式化。 - PengOne
我也遇到了同样的问题,按照@TwistedUmbrella的指示解决了。 - meadlai
请检查您的包含文件。 这是两个相互包含的头文件的症状。 有时会导致此错误。 - skywinder
显示剩余2条评论
6个回答

106

让我进一步缩小示例,并对行进行标记:

VC1.h

#import "VC2.h"  // A

@class VC1;

@protocol VC1Delegate // B
@end

@interface VC1 : UIViewController <VC2Delegate> // C
@end

VC2.h

#import "VC1.h"  // D

@class VC2;

@protocol VC2Delegate // E
@end

@interface VC2 : UIViewController <VC1Delegate> // F
@end

考虑当某个东西 #imports VC1.h 时会发生什么:它到达A行,然后处理导入。D行不做任何操作,因为VC1.h已经被导入了。然后处理E行。然后是F行,由于我们还没有到达B行,所以协议没有声明,所以出现错误!

接着考虑当某个东西# imports VC2.h时会发生什么:它到达D行,然后处理导入。A行不做任何操作,因为VC2.h已经被导入了。然后处理B行。然后是C行,由于我们还没有到达E行,所以协议未被声明,所以出现错误!

第一步是重新考虑这两个类是否真的需要彼此作为代理。如果您可以打破这个循环,那可能是最好的方法。如果不能,您将需要重构标题。最简单的方法可能是将代表放入自己的标题中:

VC1Delegate.h

@class VC1;

@protocol VC1Delegate // B
@end

VC1.h

#import "VC1Delegate.h"
#import "VC2Delegate.h"

@interface VC1 : UIViewController <VC2Delegate> // C
@end

VC2Delegate.h

@class VC2;

@protocol VC2Delegate // E
@end

VC2.h

#import "VC1Delegate.h"
#import "VC2Delegate.h"

@interface VC2 : UIViewController <VC1Delegate> // F
@end

如果您现在跟踪导入,您会发现适当的协议现在将始终在@interface行尝试使用它们之前声明。


6
谢谢你的优秀解释!如果有人想要一些与故事板、分割视图等相关的清晰写作示例...Tim Roadley在www.timroadley.com上有一些优秀的教程(我认为那是正确的地址...)。我是从那些页面被引导到这里的。 - Bertie
1
我本来想点个赞的,但既然它已经有42个赞了,而42是宇宙的秘密,而这也是宇宙的秘密,那我就把它留在这里吧。 - CommaToast
太好了,我已经寻找这样的答案有一段时间了! - Simon Unsworth

103

在 #import 行上方编写协议声明代码

@protocol -----

@end

import ----

@interface classname ---


3
谢谢……这对我有效,但我不知道为什么。你能解释一下吗? - Surender Rathore
1
三个小时后,你救了我的命!!你能解释一下吗?谢谢。 - Mário Carvalho
3
如有时间,建议阅读Anomie的答案,以获取更进一步的解释。将协议放在import之前可以确保协议在导入时被定义。这个问题类似于循环类依赖。Anomie建议将代理分开保存到它们自己的文件中,这是非常值得推荐的。 - avelis
1
检查你的包含文件。 这是两个相互包含的头文件的症状。 有时会导致此错误。 - skywinder
非常感谢。这让我很烦恼,但你们说的强制声明协议是必要的。当你仔细看头文件时,它就更有意义了,因为你可以将其分成两部分:协议和它们的来源。非常感谢,这对我很有帮助,我从现在开始会把这个做法变成常规。 - FrostyL
显示剩余2条评论

37

我遇到了几乎相同的问题,通过以上答案的帮助,我解决了它,但是方法稍微有些不同。

我所做的仅仅是在头文件中,在协议声明之后添加#import语句。 希望我的做法可以帮到你。如果有人知道这样做有不良影响,请告诉我。


2
在阅读其他答案后,我尝试了这个想法。似乎当两个文件头互相导入时会创建一个循环导入。在具有协议的文件头中,将调用该协议的文件头的导入移动到协议声明下面是一个完美的解决方法。 - Abandoned Cart

12

因为我不太喜欢在类和协议声明之间仅仅添加一些#import语句,所以我找到了另一种解决方案。

基本上,你只需要将<YourProtocolName>从.h类文件移动到.m文件的类扩展中。

所以在你的.m文件中添加:

@interface YourClassName () <YourProtocolName>
@end

我不知道这是否真的是一个好的做法,但它似乎是一个更清洁的解决方案,可以避免导入循环依赖。


10

尝试将< BaseViewControllerDelegate >或< BigViewControllerDelegate >放在实现文件中而不是头文件中。这样做可以解决问题。


1
除了那个,StackOverflow上没有其他答案解决了我的问题。谢谢。 - Isaac

3
我按照将协议移动到导入之前的修复方法,并解决了问题...导入包含了委托,所以那是问题的原因。
但我想,为什么我还要导入委托呢?我没有引用它的属性,也没有直接调用任何它的方法(这就是协议声明的作用)。
我尝试注释掉导入委托的部分,看到了出错的地方,发现我导入委托时实际上是导入了一个声明委托正在导入的东西,也就是说我导入的是A(也是我的委托),A正在导入B,而我实际上使用的是B。所以我把导入A的部分注释掉并添加了一个导入B的部分。然后我就可以将导入-协议的顺序恢复到之前的状态了。

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