将 Objective-C 代码拆分成多个文件

12

我经常觉得需要将 Objective-C 代码拆分成多个文件以获得更好的可读性。我想避免创建类并调用它们,而是希望使用简单的导入(就像在 php 中一样)。

如果有人可以提供一个有效的示例,请告知。


1
看一个类似的例子:https://journeytoios.wordpress.com/2016/11/02/split-ios-code-in-different-files/ - NeverHopeless
@NeverHopeless:你的评论应该成为这篇文章的答案。谢谢。 - chipbk10
7个回答

14

我认为在这种情况下你正在查看类别:

你只需要创建一个新的 .h .m 文件对,在 .h 文件中:

#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 即可完成设置。这样您就可以扩展您的类。


我正在尝试解决这个问题。它显示“无法找到ViewController的接口声明”(ViewController是我的类名),并且ViewController+myCat.h出现了此错误。 - Naveed Abbas
1
尝试进行以下操作 - 文件->新建->文件->Objective-C类别,在弹出的窗口中,确保您选择“Category On”中的ViewController,并为您的类别命名(在上述示例中,您将仅编写Networking)。请告诉我。 - Kaan Dedeoglu
它在分类为On的ViewController上不起作用,而是建议使用UIViewController。我创建了一个单独的类,并基于该类创建了该类别,它可以工作。但仍然遇到意外结果。一些变量可能没有在主类中更新等。感谢帮助。 - Naveed Abbas

13
你说“我想避免创建类并调用它们。” 你需要克服你的害怕添加类。如果你觉得需要将一个类的实现拆分成多个文件,那么很可能是因为你试图在一个单一的类中做太多的事情。你需要让这个类把一些职责交给其他类(“委托”)。
话虽如此,有几种方法可以拆分一个类的实现。更好的方法是使用分类或类扩展,除了修复过度膨胀的类设计外,你可以在Objective-C编程语言中了解所有关于分类和扩展的内容。请注意,链接器将在创建可执行文件时将分类和扩展合并到类中,因此在自己的类上使用分类或扩展不会产生运行时开销。

更糟糕的方法是使用C预处理器的#include指令将多个文件粘贴在一起。您可以从实现文件中取出一些方法并将它们放入新的“片段”文件中,然后在实现文件中#include该片段文件。这样做会使您的源代码更难理解。我不建议这样做,但这里还是有一个例子:

MyObject.m

#import "MyObject.h"

@implementation MyObject

- (void)aMethod { ... }

#include "MyObject-moreMethods.m"

@end

MyObject-moreMethods.m

// Note: do not include this target in the “Compile Sources” build phase of your target.
// And **NO** @implementation statement here!

- (void)methodTwo { ... }

- (void)methodThree { ... }

只要我尝试在.m文件中定义任何方法,它就会出现错误(缺少方法声明的上下文)。我需要在指定方法名称时非常准确吗? - Naveed Abbas
@ToughGuy,这种情况的发生是因为你在“编译源代码”中包含了该目标。 - Colas
答案中提供的链接指向苹果文档的页面已经失效(“抱歉,无法找到该页面”)。 - Pang

6
[编辑/更新 - 我已经放弃了下面描述的方法,转而使用类别,正如其他答案中提到的那样。我的典型情况是,随着视图添加控制组件,View Controller文件变得笨重,因为需要添加代码来适应各种委托和数据源方法。现在我在视图控制器文件中添加代码存根,然后在类别中实现它们。例如,我可能有一个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指令被拉入。这可以通过在创建文件时(或将其添加到项目时)不将其添加到目标中,或通过在“Build Phases”->“Compile Sources”窗格中删除它们来实现。
然后在父.m文件的主体内#include子.m文件。编译器会“就地”加载它们,并按照所需的方式编译源代码,而不会抱怨。
这种方法的缺点(目前为止)是调试器无法识别子方法,并且不会在断点上中断。因此,我建议仅在代码经过彻底测试或对于相对简单且众所周知的代码块(例如表委托和数据源方法)时使用此方法。
以下是一个包含表和nib中文本字段的项目的.h和.m文件,其中支持委托方法在子.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

我喜欢它。Swift 版本是多少? - MobileMon
请注意将“@implementation ViewController”一行更正为“@implementation MyViewController”。 - undefined

3

这表明你的类可能过于庞大。

一种解决方法:使用分类。声明通常可以保留在头文件中。然后,您的实现可以被分成不同的分类。只需确保指定您正在实现的分类,以便编译器可以将其与其声明匹配,并在缺少定义时通知您。


2

一个将大类分解的好方法是将功能分组到类别中(就像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

这样,我可以将我的大类实现清晰地组织成小类,并将所有类功能信息分组存储在单个基础标头中以供阅读和导入。

如果您使用内部标头,则无需在“基本扩展”中重新声明属性。https://dev59.com/_HfZa4cB1Zd3GeqPRW3A#19096613 - malhal
同意。但这是个人偏好,我通常为了清晰起见会制作单独的头文件。 - Kraming

0

在iOS中最好使用分类来保持代码的整洁。


0

我知道这是一个老的线程,Objective C 的神灵们可能不会喜欢这个解决方案,但如果你需要拆分文件,请创建一个新的 .m 文件并将你的代码粘贴到那里。然后,在你想要包含它的 .m 文件中的 @implementation 后面加入 #include "yournewfile.m"。避免编译器错误的诀窍是进入 Build Phase 并从 Compile Sources 中删除 "yournewfile.m"。


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