我经常觉得需要将 Objective-C 代码拆分为多个文件以提高可读性。我想避免上课并打电话给他们。我想要简单的导入(比如在 php 中)。
如果有人可以请参考一个工作示例。
我经常觉得需要将 Objective-C 代码拆分为多个文件以提高可读性。我想避免上课并打电话给他们。我想要简单的导入(比如在 php 中)。
如果有人可以请参考一个工作示例。
我认为您在这种情况下正在查看类别:
您所要做的就是在 .h 文件中创建一个新的 .h .m 对:
#import MyClass.h
@interface MyClass(Networking)
//method declarations here
@end
在 .m 文件中:
#import MyClass+Networking.h
@implementation MyClass(Networking)
//method definitions here
@end
在 MyClass.m 文件中 - 执行 #import MyClass+Networking.h 就可以了。这样你就可以扩展你的类。
你说“我想避免上课并打电话给他们。” 您需要克服对添加课程的恐惧。如果您觉得需要将一个类的实现拆分为多个文件,那么您很可能试图在一个类中做太多事情。您需要让该类将一些职责移交(“委派”)给其他类。
也就是说,有几种方法可以拆分类的实现。除了修复臃肿的类设计之外,更好的方法是使用类别或类扩展。您可以在The Objective-C Programming Language中阅读有关类别和扩展的所有信息。请注意,链接器在创建可执行文件时会将类别和扩展合并到类中,因此在您自己的类上使用类别或扩展不会造成运行时损失。
更糟糕的方法是使用 C 预处理器的#include
指令将多个文件粘贴在一起。您可以从实现文件中取出一些方法并将它们粘贴到新的“片段”文件中,然后#include
将片段文件放入实现文件中。 这样做会使您更难理解您的源代码。 我不建议这样做,但无论如何这里有一个例子:
#import "MyObject.h"
@implementation MyObject
- (void)aMethod { ... }
#include "MyObject-moreMethods.m"
@end
// Note: do not include this target in the “Compile Sources” build phase of your target.
// And **NO** @implementation statement here!
- (void)methodTwo { ... }
- (void)methodThree { ... }
[编辑/更新——我已经放弃了下面描述的方法,转而使用类别,正如其他一些答案中提到的那样。我的典型情况是,当视图添加了控件组件时,视图控制器文件变得笨拙,因为添加了代码以适应控件的各种委托和数据源方法。现在我在视图控制器文件中添加代码存根,然后在类类别中真正实现它们。例如,我可能有一个具有搜索栏和集合视图的 AlbumViewController 屏幕,因此我创建了 AlbumViewController+SearchBar 和 AlbumViewController+CollectionView 类别。这允许 View Controller 类保持在合理的大小,而不会产生我在下面列出的包含文件的任何缺点。唯一的缺点是任何实例变量,即属性,
我也想拆分我的文件,但认为类别在某些情况下不是正确的解决方案。例如,随着 nib 增长到包含多个对象(例如表格、按钮、导航栏、选项卡等),需要将所有支持方法放在 viewcontroller.m 文件中可能会导致文件非常大且难以处理。
在本次讨论中,我将原始/标准 .m 文件称为父文件,将子 .m 文件称为子文件。
目标是拥有一个父 .h 和 .m,以及多个子 .m 文件,每个文件都可以独立编辑,但编译时就像所有子 .m 文件都在父 .m 文件中一样。
例如,如果您希望能够将所有与表相关的方法放在一个文件中,对其进行编辑,然后让它像包含在 viewcontroller.m 实现文件中一样进行编译,这将很有用。这似乎是可能的,但需要一点努力,并且有一个(可能是严重的)缺点。
要记住的是,使用了两种不同的工具:IDE(提供智能源代码编辑)和编译器/制作系统,它将项目的源代码转换为可运行的应用程序。
要使 IDE 按预期工作,子文件需要看起来是类实现的一部分。这可以通过将@implementation 和@end 指令包装在条件编译宏中来实现。单独编辑文件时,IDE 将子文件视为类的主体,但编译器不会。
为了让编译器不抱怨,您不能将子文件视为目标的一部分——而是通过预处理器 #include 指令将它们拉入。这可以通过在创建文件(或添加到项目)时不将它们添加到目标来完成,或者通过在“构建阶段”->“编译源”窗格中删除它们来完成。
然后,您可以在父 .m 文件的正文中 #include 子 .m 文件。编译器“就地”加载它们并根据需要编译源代码而不会抱怨。
这种方法的缺点(到目前为止)是调试器无法识别子方法,并且不会在放置在它们上的断点处中断。出于这个原因,我建议仅在对代码进行彻底测试之后使用这种方法,或者用于相对琐碎且众所周知的代码块,例如表委托和数据源方法。
以下是项目的 .h 和 .m 文件,在 nib 中有一个表格和一个文本字段,支持的委托方法在子 .m 文件中定义。在 nib 中,接口对象正常连接,并将委托设置为文件所有者。
文件(父)“MyViewController.h”:
#import <UIKit/UIKit.h>
@interface MyViewController : UIViewController
@property (retain, nonatomic) IBOutlet UITableView *myTable;
@property (retain, nonatomic) IBOutlet UITextField *myTextField;
@end
文件(父)MyViewController.m:
#import "MyViewController.h"
#define VIEW_CONTROLLER_MAIN_BODY 1
@interface MyViewController ()
@end
@implementation MyViewController
#include "MyViewController_TableMethods.m"
#include "MyViewController_TextFieldMethods.m"
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
[_myTable release];
[_myTextField release];
[super dealloc];
}
文件(子)MyViewController_TableMethods.m:
#import <UIKit/UIKit.h>
#import "MyViewController.h"
#ifndef VIEW_CONTROLLER_MAIN_BODY
@implementation ViewController
#endif
#pragma mark -
#pragma mark Table View Common Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
{
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
static NSString *myIdentifier = @"myCellIdentifier";
static UITableViewCellStyle myStyle = UITableViewCellStyleSubtitle;
UITableViewCell *cell = [self.myTable dequeueReusableCellWithIdentifier:myIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:myStyle reuseIdentifier:myIdentifier] autorelease];
}
cell.textLabel.text = @"Title";
cell.detailTextLabel.text = @"Details";
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
#ifndef VIEW_CONTROLLER_MAIN_BODY
@end
#endif
文件(子)MyViewController_TextFieldMethods.m:
#import <UIKit/UIKit.h>
#import "MyViewController.h"
#ifndef VIEW_CONTROLLER_MAIN_BODY
@implementation MyViewController
#endif
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
self.myTextField.text = @"Woo hoo!";
return YES;
}
#ifndef VIEW_CONTROLLER_MAIN_BODY
@end
#endif
这很好地表明您的班级太大了。
一种方法:使用类别。声明通常可以保留在标题中。那么你的实现可能会被划分。只需确保指定您正在实现的类别,以便编译器可以将其与其声明相匹配,并在您错过定义时通知您。
分解大类的一个好方法是类别中的分组功能(就像 Apple 在 UIViewController 中所做的那样)。对于一个大类,我通常将与其相应功能相关的方法和属性分组,然后在基本头文件中,我拆分
@interface SomeClass : NSObject
@property (strong, nonatomic) BaseProperty *sp;
- (void)someMethodForBase;
@end
@interface SomeClass (FunctionalityOne)
@property (strong, nonatomic) FuncOne *fpOne;
- (void)someMethodForFunctionalityOne;
@end
@interface SomeClass (FunctionalityTwo)
@property (strong, nonatomic) FuncTwo *fpTwo;
- (void)someMethodForFunctionalityTwo;
@end
因为您不能在类别中添加属性(您不能在类别中添加新的 iVar 和属性不合成)所以您再次在基本扩展的实现中声明它。
@interface SomeClass()
@property (strong, nonatomic) FuncOne *fpOne;
@property (strong, nonatomic) FuncTwo *fpTwo;
@end
@implementation SomeClass
//base class implementation
- (void)someMethodForBase {
}
@end
在 SomeClass+FunctionalityOne.m 中实现各自的功能
@implementation SomeClass (FunctionalityOne)
@dynamic fpOne;
//Functionality one implementation
- (void)someMethodForFunctionalityOne {
}
@end
在 SomeClass+FunctionalityTwo.m 中实现各自的功能
@implementation SomeClass (FunctionalityTwo)
@dynamic fpTwo;
//Functionality two implementation
- (void)someMethodForFunctionalityTwo {
}
@end
通过这种方式,我将我的大类实现清晰地组织成小类,所有类功能信息都分组在单个基本标头中以供读取和导入。
最好在 iOS 中使用类别保持代码干净。
我知道这是一个旧线程,Objective C 之神不会喜欢这个解决方案,但是如果您需要拆分文件,请创建一个新的 .m 文件并将您的代码粘贴到那里。然后,在要包含它的 .m 文件中的 @implementation 之后 #include "yournewfile.m" 。避免编译器错误的诀窍是进入构建阶段并从编译源中删除“yournewfile.m”。