11

我经常觉得需要将 Objective-C 代码拆分为多个文件以提高可读性。我想避免上课并打电话给他们。我想要简单的导入(比如在 php 中)。

如果有人可以请参考一个工作示例。

4

7 回答 7

14

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

您所要做的就是在 .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 就可以了。这样你就可以扩展你的类。

于 2012-11-13T09:03:23.317 回答
13

你说“我想避免上课并打电话给他们。” 您需要克服对添加课程的恐惧。如果您觉得需要将一个类的实现拆分为多个文件,那么您很可能试图在一个类中做太多事情。您需要让该类将一些职责移交(“委派”)给其他类。

也就是说,有几种方法可以拆分类的实现。除了修复臃肿的类设计之外,更好的方法是使用类别或类扩展。您可以在The Objective-C Programming Language中阅读有关类别和扩展的所有信息。请注意,链接器在创建可执行文件时会将类别和扩展合并到类中,因此在您自己的类上使用类别或扩展不会造成运行时损失。

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

我的对象.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 { ... }
于 2012-11-13T09:05:04.430 回答
5

[编辑/更新——我已经放弃了下面描述的方法,转而使用类别,正如其他一些答案中提到的那样。我的典型情况是,当视图添加了控件组件时,视图控制器文件变得笨拙,因为添加了代码以适应控件的各种委托和数据源方法。现在我在视图控制器文件中添加代码存根,然后在类类别中真正实现它们。例如,我可能有一个具有搜索栏和集合视图的 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
于 2013-03-09T21:03:25.840 回答
3

这很好地表明您的班级太大了。

一种方法:使用类别。声明通常可以保留在标题中。那么你的实现可能会被划分。只需确保指定您正在实现的类别,以便编译器可以将其与其声明相匹配,并在您错过定义时通知您。

于 2012-11-13T09:03:41.483 回答
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

通过这种方式,我将我的大类实现清晰地组织成小类,所有类功能信息都分组在单个基本标头中以供读取和导入。

于 2016-12-08T10:58:12.967 回答
0

最好在 iOS 中使用类别保持代码干净。

于 2013-08-29T07:34:56.687 回答
0

我知道这是一个旧线程,Objective C 之神不会喜欢这个解决方案,但是如果您需要拆分文件,请创建一个新的 .m 文件并将您的代码粘贴到那里。然后,在要包含它的 .m 文件中的 @implementation 之后 #include "yournewfile.m" 。避免编译器错误的诀窍是进入构建阶段并从编译源中删除“yournewfile.m”。

于 2020-07-19T22:24:47.500 回答