【问题标题】:Best way to define private methods for a class in Objective-C在 Objective-C 中为类定义私有方法的最佳方法
【发布时间】:2010-09-15 09:57:38
【问题描述】:

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

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

请在发布时为您的方法提供一个论据。为什么好?它有哪些(您知道的)缺点以及您如何处理它们?


至于我目前的发现。

可以使用categories [例如MyClass (Private)] 在 MyClass.m 文件中定义,用于对私有方法进行分组。

这种方法有两个问题:

  1. Xcode(和编译器?)不会检查您是否在相应的 @implementation 块中定义了私有类别中的所有方法
  2. 您必须将 @interface 声明您的私有类别放在 MyClass.m 文件的开头,否则 Xcode 会抱怨类似“self may not respond to message "privateFoo" 这样的消息。

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

【问题讨论】:

标签: objective-c cocoa code-completion


【解决方案1】:

正如其他人已经说过的那样,Objective-C 中没有私有方法这样的东西。但是,从 Objective-C 2.0(意味着 Mac OS X Leopard、iPhone OS 2.0 及更高版本)开始,您可以创建一个名为 Class Extension 的具有空名称(即@interface MyClass ())的类别。类扩展的独特之处在于方法实现必须与公共方法在相同的@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

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

【讨论】:

  • 它会生成一个“MYClass 可能无法响应'-myPrivateMethod-”,而不是异常/错误。
  • 这实际上开始出现在 Apple 的样板代码中。 ++
  • 使用 LLVM 4 及更高版本的编译器,您甚至不需要这样做。你可以在你的实现中定义它们,而不需要将它们放在类扩展中。
  • 如果您收到@Comptrol 提到的警告,那是因为您在下面定义了一个方法,而不是在另一个调用它的方法之上(参见安迪的回答)——而忽略这些警告,后果自负。我犯了这个错误,编译器一直糊里糊涂,直到我嵌套了这样的调用:if (bSizeDifference && [self isSizeDifferenceSignificant:fWidthCombined])... 然后 fWidthCombined 总是以 0 的形式出现。
  • @Wienke 不再需要担心订单。最新版本的 LLVM 会找到该方法,即使它出现在调用它的位置下方。
【解决方案2】:

Objective-C 中并没有真正的“私有方法”,如果运行时可以确定使用哪个实现,它就会做到。但这并不是说没有不属于文档化接口的方法。对于那些方法,我认为一个类别很好。与其将@interface 放在 .m 文件的顶部(如您的第 2 点),不如将其放入自己的 .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 方法进行两次传递。这意味着您可以完全避免声明您的“私有”方法,无论它们是在调用站点之上还是之下,编译器都会找到它们。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 这个解决方案的优点是它避免了为了避免编译器警告而添加多余的程序结构。
    • 我也倾向于这样做,但也不是 Objective-C 专家。对于专家来说,有什么理由不这样做(除了方法排序问题)?
    • 方法排序似乎是一个小问题,但如果将其转换为代码可读性,它可能会成为一个非常重要的问题,尤其是在团队工作时。
    • 方法顺序不再重要。最新版本的 LLVM 并不关心这些方法的实现顺序。因此,您可以在订购时适合自己,而无需先声明。
    • 另见 @justin 的 this response
    【解决方案4】:

    @implementation 块中定义您的私有方法对于大多数用途来说是理想的。无论声明顺序如何,Clang 都会在 @implementation 中看到这些。无需在类延续(也称为类扩展)或命名类别中声明它们。

    在某些情况下,您需要在类延续中声明方法(例如,如果在类延续和@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 用于使用完整的类名作为方法名前缀!它比仅使用下划线甚至您自己的 TLA 更安全。 (如果私有方法位于您在另一个项目中使用的库中,而您忘记在一两年前的某个时候已经使用过该名称...?)
    【解决方案5】:

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

    //.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
    

    【讨论】:

    • Apple 保留名称,前导下划线供自己使用。
    • 如果您不使用 Apple 的框架怎么办?我经常在没有 Apple 框架的情况下开发 Objective-C 代码,实际上我是在 Linux、Windows 和 Mac OS X 上构建的。考虑到大多数使用 Objective-C 编写代码的人可能确实在 Mac OS X 上使用它,我还是删除了它。
    • 我认为这是 .m 文件中真正的私有方法。其他类类别方法实际上不是私有的,因为您不能将私有方法放在 @interface...@end 块中。
    • 你为什么要这样做?如果您只是在方法定义的开头添加“-”,您将访问“self”而不作为参数传递。
    • @Guy:因为这样可以通过反射检测到该方法,因此根本不是私有的。
    【解决方案6】:

    你可以使用积木吗?

    @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
    

    我知道这是一个老问题,但这是我在寻找这个问题的答案时发现的第一个问题。我还没有在其他任何地方看到过这个解决方案的讨论,所以如果这样做有什么愚蠢的地方,请告诉我。

    【讨论】:

    • 你在这里所做的是创建一个全局块类型变量,它并不比函数更好(甚至不是真正的私有,因为它没有声明static) .但我一直在尝试将块分配给私有 ivars(来自 init 方法)——有点 JavaScript 风格——这也允许访问私有 ivars,这是静态函数无法实现的。还不确定我更喜欢哪个。
    【解决方案7】:

    Objective C 中的每个对象都符合 NSObject 协议,该协议包含 performSelector: 方法。我之前也在寻找一种方法来创建一些我不需要在公共级别公开的“帮助程序或私有”方法。如果你想创建一个没有开销的私有方法并且不必在头文件中定义它,那么试试这个......

    使用与以下代码类似的签名定义您的方法...

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

    那么当您需要引用该方法时,只需将其作为选择器调用...

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

    这行代码将调用您创建的方法,并且没有关于没有在头文件中定义它的烦人警告。

    【讨论】:

    • 这样就没法传第三个参数了。
    【解决方案8】:

    如果您想避免顶部的 @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
    

    【讨论】:

      【解决方案9】:

      还有一件事我在这里没有提到——Xcode 支持名称中带有“_private”的 .h 文件。假设你有一个 MyClass 类——你有 MyClass.m 和 MyClass.h,现在你也可以有 MyClass_private.h。 Xcode 将识别这一点并将其包含在助手编辑器的“对应部分”列表中。

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

      【讨论】:

        【解决方案10】:

        没有办法绕过问题 #2。这就是 C 编译器(以及因此的 Objective-C 编译器)的工作方式。如果您使用 XCode 编辑器,函数弹出窗口应该可以轻松导航文件中的 @interface@implementation 块。

        【讨论】:

          【解决方案11】:

          没有私有方法有一个好处。您可以将您打算隐藏的逻辑移动到单独的类并将其用作委托。在这种情况下,您可以将委托对象标记为私有,并且从外部看不到它。将逻辑移动到单独的类(可能有几个)可以更好地设计您的项目。因为您的类变得更简单,并且您的方法被分组到具有正确名称的类中。

          【讨论】:

            【解决方案12】:

            正如其他人所说,在 @implementation 块中定义私有方法对于大多数用途来说是可以的。

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

            @implementation MyClass 
            // .. public methods
            
            # pragma mark private 
            // ...
            
            @end
            

            【讨论】:

              猜你喜欢
              • 2012-11-12
              • 2013-04-27
              • 1970-01-01
              • 2012-05-06
              • 1970-01-01
              • 1970-01-01
              • 2020-04-22
              • 1970-01-01
              • 2019-04-06
              相关资源
              最近更新 更多