在Objective-C中为类定义私有方法的最佳方法


355

我刚开始编写Objective-C程序,并且以Java为背景,想知道编写Objective-C程序的人如何处理私有方法。

我了解可能有几种约定和习惯,并考虑此问题作为人们在Objective-C中使用的处理私有方法的最佳技术的集合。

发布时,请为您的方法添加一个参数。为什么好呢?它具有(您知道的)哪些缺点以及如何处理它们?


至于到目前为止的发现。

可以使用MyClass.m文件中定义的类别 [例如MyClass(私有)]对私有方法进行分组。

此方法有两个问题:

  1. Xcode(和编译器?)不会检查是否在相应的@implementation块中的私有类别中定义了所有方法
  2. 您必须在MyClass.m文件的开头放置@interface声明您的私有类别,否则Xcode会抱怨“自身可能无法响应消息” privateFoo”之类的消息。

第一个问题可以通过使用空类别 [例如MyClass()]解决。
第二个让我很困扰。我希望看到在文件末尾附近实现(和定义)私有方法。我不知道是否可能。


1
:人可能会发现这个问题有意思stackoverflow.com/questions/2158660/...
bbum

Answers:


436

正如其他人已经说过的那样,在Objective-C中没有私有方法。但是,从Objective-C 2.0(意味着Mac OS X Leopard,iPhone OS 2.0和更高版本)开始,您可以使用空名称(即@interface MyClass ())创建一个名为Class Extension的类别。类扩展的独特之处在于方法实现必须@implementation MyClass与公共方法相同。所以我这样构造我的类:

在.h文件中:

@interface MyClass {
    // My Instance Variables
}

- (void)myPublicMethod;

@end

在.m文件中:

@interface MyClass()

- (void)myPrivateMethod;

@end

@implementation MyClass

- (void)myPublicMethod {
    // Implementation goes here
}

- (void)myPrivateMethod {
    // Implementation goes here
}

@end

我认为这种方法的最大优点是,它允许您按功能而不是(有时是任意的)公共/私有区别对方法实现进行分组。


8
并且它将生成“ MYClass可能不响应'-myPrivateMethod-”,而不是异常/错误。
Özgür的

2
实际上,这已开始在Apple的样板代码中显示出来。++
克里斯·特拉希

75
使用LLVM 4编译器及更高版本,您甚至不需要执行此操作。您只需在实现中定义它们,而无需将它们放在类扩展中。
阿比森

1
如果收到@Comptrol提及的警告,那是因为您在下面而不是在另一个调用它的方法(请参阅Andy的答案)上方定义了一个方法,而忽略了这些警告,后果自负。我犯了这个错误,编译器通过正常的混乱,直到我嵌套像这样的电话:if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])...然后fWidthCombined总是未来通过为0
Wienke

6
@Wienke不再需要担心订单。LLVM的最新版本将找到该方法,即使它出现在调用位置的下方。
Steve Waddicor

52

如果运行时可以确定要使用哪个实现,那么Objective-C中实际上没有“私有方法”。但这并不是说不存在不属于文档接口的方法。对于那些方法,我认为类别很好。与其@interface像您的要点2那样将.m文件放在顶部,不如将它放在自己的.h文件中。我遵循的约定(在其他地方也看到过,我认为这是Apple约定,因为Xcode现在为其提供了自动支持)是在文件的类和类别后用+分隔命名该文件,因此@interface GLObject (PrivateMethods)可以在中找到GLObject+PrivateMethods.h。提供头文件的原因是可以将其导入到单元测试类中:-)。

顺便说一句,就.m文件末尾附近的实现/定义方法而言,您可以通过实现.m文件底部的类别来使用类别来实现:

@implementation GLObject(PrivateMethods)
- (void)secretFeature;
@end

或带有类扩展名(您称其为“空类别”的东西),只需最后定义这些方法即可。可以在实现中以任何顺序定义和使用Objective-C方法,因此没有什么可以阻止您将“私有”方法放在文件末尾。

即使使用类扩展,我也经常会创建一个单独的标头(GLObject+Extension.h),以便在需要时可以使用这些方法,模仿“朋友”或“受保护”的可见性。

自从最初编写此答案以来,clang编译器已开始对Objective-C方法进行两次处理。这意味着您可以避免完全声明您的“私有”方法,而无论它们位于调用站点之上还是之下,编译器都可以找到它们。


37

虽然我不是Objective-C专家,但我个人只是在实现类时定义方法。当然,它必须在调用它的任何方法之前(在上面)进行定义,但是它绝对需要最少的工作量。


4
该解决方案的优势在于,它避免了仅仅为了避免编译器警告而添加多余的程序结构。
Jan Hettich 2010年

1
我也倾向于这样做,但是我也不是Objective-C专家。对于专家来说,除了方法订购问题之外,还有什么理由不这样做吗?
埃里克·史密斯

2
方法排序似乎是一个小问题,但如果将其转换为代码可读性,则可能会成为一个非常重要的问题,尤其是在团队合作中。
borisdiakur's

17
方法排序不再重要。LLVM的最新版本并不关心方法的实现顺序。因此,您可以根据自己的需要进行订购,而无需先声明。
Steve Waddicor

另请参见@justin
lagweezle 2014年

19

@implementation大多数情况下,在块中定义私有方法是理想的。@implementation不管声明顺序如何,Clang都会在中看到这些内容。无需在类延续(也称为类扩展)或命名类别中声明它们。

在某些情况下,您需要在类延续中声明方法(例如,如果在类延续和之间使用选择器@implementation)。

static 函数对于特别敏感或对速度有严格要求的私有方法非常有用。

前缀命名约定可以帮助您避免意外覆盖私有方法(我发现类名是安全的前缀)。

命名类别(例如@interface MONObject (PrivateStuff))并不是一个特别好的主意,因为在加载时可能会发生命名冲突。实际上,它们仅对朋友或受保护的方法有用(很少是一个好的选择)。为确保警告您类别实现不完整,您实际上应该实施它:

@implementation MONObject (PrivateStuff)
...HERE...
@end

这是一个带注释的备忘单:

MONObject.h

@interface MONObject : NSObject

// public declaration required for clients' visibility/use.
@property (nonatomic, assign, readwrite) bool publicBool;

// public declaration required for clients' visibility/use.
- (void)publicMethod;

@end

MONObject.m

@interface MONObject ()
@property (nonatomic, assign, readwrite) bool privateBool;

// you can use a convention where the class name prefix is reserved
// for private methods this can reduce accidental overriding:
- (void)MONObject_privateMethod;

@end

// The potentially good thing about functions is that they are truly
// inaccessible; They may not be overridden, accidentally used,
// looked up via the objc runtime, and will often be eliminated from
// backtraces. Unlike methods, they can also be inlined. If unused
// (e.g. diagnostic omitted in release) or every use is inlined,
// they may be removed from the binary:
static void PrivateMethod(MONObject * pObject) {
    pObject.privateBool = true;
}

@implementation MONObject
{
    bool anIvar;
}

static void AnotherPrivateMethod(MONObject * pObject) {
    if (0 == pObject) {
        assert(0 && "invalid parameter");
        return;
    }

    // if declared in the @implementation scope, you *could* access the
    // private ivars directly (although you should rarely do this):
    pObject->anIvar = true;
}

- (void)publicMethod
{
    // declared below -- but clang can see its declaration in this
    // translation:
    [self privateMethod];
}

// no declaration required.
- (void)privateMethod
{
}

- (void)MONObject_privateMethod
{
}

@end

另一种可能并不明显的方法:C ++类型可以非常快并且可以提供更高程度的控制,同时可以最大程度地减少导出和加载的objc方法的数量。


1
+1,用于使用完整的类名作为方法名前缀!这比下划线甚至您自己的TLA安全得多。(如果私有方法位于您在另一个项目中使用的库中,而又忘记了在一两年前的某个时候已经使用过该名称,该怎么办...?)
big_m 2015年

14

您可以尝试在实现的上方或下方定义一个静态函数,该函数需要一个指向您实例的指针。它将能够访问您的任何实例变量。

//.h file
@interface MyClass : Object
{
    int test;
}
- (void) someMethod: anArg;

@end


//.m file    
@implementation MyClass

static void somePrivateMethod (MyClass *myClass, id anArg)
{
    fprintf (stderr, "MyClass (%d) was passed %p", myClass->test, anArg);
}


- (void) someMethod: (id) anArg
{
    somePrivateMethod (self, anArg);
}

@end

1
苹果保留名称,并在其下划线,以供自己使用。
格奥尔格·舍利(GeorgSchölly)

1
如果不使用Apple的框架怎么办?我经常开发的Objective-C代码,而苹果公司的框架,在Linux,Windows和Mac OS X.我其实我构建去掉也无妨考虑谁在Objective-C代码可能就用它在Mac OS X上大多数人
dreamlax

3
我认为这是.m文件中的真正私有方法。实际上,其他类类别的方法不是私有的,因为您不能在@interface ... @ end块中将方法私有。
David.Chu.ca 2010年

为什么要这么做?如果仅在方法定义的开头添加“-”,则无需输入参数即可访问“ self”。
2014年

1
@Guy:因为该方法可以通过反射检测,因此根本不是私有的。
dreamlax

3

你可以用积木吗?

@implementation MyClass

id (^createTheObject)() = ^(){ return [[NSObject alloc] init];};

NSInteger (^addEm)(NSInteger, NSInteger) =
^(NSInteger a, NSInteger b)
{
    return a + b;
};

//public methods, etc.

- (NSObject) thePublicOne
{
    return createTheObject();
}

@end

我知道这是一个古老的问题,但这是我在寻找该问题的答案时发现的第一个问题。我在其他任何地方都没有看到过这种解决方案的讨论,所以让我知道这样做是否有些愚蠢。


1
您在这里所做的是创建一个全局块类型变量,它实际上并不比函数好(并且甚至不是真正私有的,因为它没有声明static)。但是我一直在尝试将块分配给私有ivars(通过init方法)(一种JavaScript风格),它也允许访问私有ivars,这是静态函数无法实现的。不确定我更喜欢哪个。
big_m 2015年

3

Objective C中的每个对象都遵循NSObject协议,该协议保留了performSelector:方法。我以前也曾在寻找一种创建不需要在公共级别公开的“帮助器或私有”方法的方法。如果您想创建一个没有开销且不必在头文件中定义的私有方法,那么请尝试一下...

用与下面的代码相似的签名定义您的方法...

-(void)myHelperMethod: (id) sender{
     // code here...
}

然后当您需要引用该方法时,只需将其作为选择器调用即可...

[self performSelector:@selector(myHelperMethod:)];

这行代码将调用您创建的方法,并且不会在标头文件中没有定义该方法的提示。


6
这样,您将无法传递第三个参数。
李富民

2

如果要避免@interface在顶部放置该块,则可以始终将私有声明放在另一个文件中,该文件MyClassPrivate.h不是很理想,但不会使实现混乱。

MyClass.h

interface MyClass : NSObject {
 @private
  BOOL publicIvar_;
  BOOL privateIvar_;
}

@property (nonatomic, assign) BOOL publicIvar;
//any other public methods. etc
@end

MyClassPrivate.h

@interface MyClass ()

@property (nonatomic, assign) BOOL privateIvar;
//any other private methods etc.
@end

MyClass.m

#import "MyClass.h"
#import "MyClassPrivate.h"
@implementation MyClass

@synthesize privateIvar = privateIvar_;
@synthesize publicIvar = publicIvar_;

@end

2

在这里我还没有提到的另一件事-Xcode支持名称中带有“ _private”的.h文件。假设您有一个MyClass类-您有MyClass.m和MyClass.h,现在还可以有MyClass_private.h。Xcode将识别出该错误,并将其包含在助手编辑器的“配对”列表中。

//MyClass.m
#import "MyClass.h"
#import "MyClass_private.h"

1

无法解决问题2。这就是C编译器(以及Objective-C编译器)的工作方式。如果使用XCode编辑器,则函数弹出窗口将使导航文件中的@interface@implementation块变得容易。


1

缺少私有方法有一个好处。您可以将要隐藏的逻辑移到单独的类,并将其用作委托。在这种情况下,您可以将委托对象标记为私有,并且从外部看不到它。将逻辑移到单独的类(也许是几个)上可以更好地设计项目。因为您的类变得更简单,并且您的方法以适当的名称分组在类中。


0

正如其他人所说,在@implementation大多数情况下,在块中定义私有方法是可以的。

关于代码组织的主题-我喜欢将它们放在一起,pragma mark private以便在Xcode中轻松导航

@implementation MyClass 
// .. public methods

# pragma mark private 
// ...

@end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.