【问题标题】:Hide instance variable from header file in Objective C在Objective C的头文件中隐藏实例变量
【发布时间】:2011-01-07 09:54:44
【问题描述】:

我遇到了一个用 Objective C 编写的库(我只有头文件和 .a 二进制文件)。 在头文件中是这样的:

@interface MyClass : MySuperClass 
{ 
    //nothing here
}

@property (nonatomic, retain) MyObject anObject;
- (void)someMethod;

我怎样才能达到同样的目的?如果我尝试在接口的 {} 中声明一个没有相应 ivar 的属性,编译器会给我一个错误。最终,我想将我的类的内部结构隐藏在 .a 中,并将必要的方法暴露给头文件。如何在 .m 中声明实例变量?类别不允许我添加 ivar,只能添加方法。

【问题讨论】:

    标签: objective-c header declaration private instance-variables


    【解决方案1】:

    不,你不能。但是如果你不使用@property,你可以这样做:

    .h

    @interface X : Y {
      struct X_Impl* impl;
    }
    -(int)getValue;
    @end
    

    .m

    struct X_Impl {
      int value;
    };
    ...
    @implementation X
    -(void)getValue {
      return impl->value * impl->value;
    }
    @end
    

    【讨论】:

      【解决方案2】:

      对于 64 位应用程序和 iPhone 应用程序(虽然不在模拟器中),属性合成也能够合成实例变量的存储。

      即这行得通:

      @interface MyClass : MySuperClass 
      { 
          //nothing here
      }
      
      @property (nonatomic, retain) MyObject *anObject;
      @end
      
      @implementation MyClass
      @synthesize anObject;
      @end
      

      如果你为 32 位 Mac OS X 或 iPhone Simulator 编译,编译器会报错。

      【讨论】:

      • 你是对的!当我从模拟器切换到设备时,错误消失了。
      • 请注意,随着时间的推移,模​​拟器变得越来越像 iOS 运行时......例如,这适用于最新版本。
      【解决方案3】:

      两种可能:

      1. 正如 bbum 所建议的那样,它可以利用现代运行时合成实例变量的能力。
      2. 该属性在该类中可能没有基础实例变量。属性不一定与实例变量具有一对一的映射关系。

      【讨论】:

        【解决方案4】:

        宏技巧怎么样?

        已经测试过下面的代码

        • 已经用 dylibs 测试过 - 工作正常
        • 已经测试了子类化 - 警告! 会中断,我同意这使得这个技巧没有那么有用,但我仍然认为它告诉了一些关于 ObjC 是如何工作的......

        MyClass.h

        @interface MyClass : NSObject {
        #ifdef MYCLASS_CONTENT
            MYCLASS_CONTENT // Nothing revealed here
        #endif
        }
        @property (nonatomic, retain) NSString *name;
        @property (nonatomic, assign) int extra;
        - (id)initWithString:(NSString*)str;
        @end
        

        MyClass.m

        // Define the required Class content here before the #import "MyClass.h"
        #define MYCLASS_CONTENT \
          NSString *_name; \
          int _extra; \
          int _hiddenThing; 
        
        #import "MyClass.h"
        
        @implementation MyClass
        @synthesize name=_name;
        @synthesize extra=_extra;
        
        - (id)initWithString:(NSString*)str
        {
            self = [super init];
            if (self) {
                self.name = str;
                self.extra = 17;
                _hiddenThing = 19;
            }
            return self;
        }
        
        - (void)dealloc
        {
            [_name release];
            [super dealloc];
        }
        
        @end
        

        【讨论】:

        • 没有实现文件,未处理的头文件将无法使用。
        • 谢谢,好技巧。如果您确实写了一篇博文,请在此处提供链接。
        • 已通过在 MyClass.m 内部和外部调用 NSLog(@"%s: %d", __FILE__, class_getInstanceSize([MyClass class])); 进行检查...给出相同的值,所以我认为它也很安全
        • 不,它不可能在没有 .m 文件的项目中工作。你是说你只用 .h 和一个已编译的 .a 库进行了测试并且它有效?
        • 除非你有子类,否则它会起作用。子类将非常非常破碎(尽管它可能巧合地在 64 位和 iPhone 上工作)。无论如何,看起来非常脆弱。
        【解决方案5】:

        您可以使用 Cocoa 类中使用的相同习语。如果您查看 NSString.h 中的 NSString 类接口,您会发现没有声明实例变量。深入了解 GNUstep 源代码,您会发现诀窍。

        考虑下面的代码。

        MyClass.h

        @interface MyClass : NSObject
        
        // Your methods here
        - (void) doSomething;
        
        @end
        

        MyClass.m

        @interface MyClassImpl : MyClass {
           // Your private and hidden instance variables here
        }
        @end
        
        @implementation MyClass
        
        + (id) allocWithZone:(NSZone *)zone
        {
           return NSAllocateObject([MyClassImpl class], 0, zone);
        }
        
        // Your methods here
        - (void) doSomething {
          // This method is considered as pure virtual and cannot be invoked
          [self doesNotRecognizeSelector: _cmd];          
        }
        
        @end
        
        @implementation MyClassImpl
        
        // Your methods here
        - (void) doSomething {
          // A real implementation of doSomething
        }
        
        @end
        

        如您所见,诀窍在于在您的类中重载 allocWithZone:。这段代码默认调用NSObject提供的alloc,所以你不用担心应该使用哪种分配方法(两者都有效)。在这样的 allocWithZone: 中,您可以使用 Foundation 函数 NSAllocateObject()MyClassImpl分配内存并初始化 isa > 对象而不是 MyClass。之后,用户将透明地处理 MyClassImpl 对象。

        当然,你的类的真正实现应该由MyClassImpl提供。 MyClass 的方法应以将消息接收视为错误的方式实现。

        【讨论】:

        • 这正是我想要的,而且效果很好。谢谢!
        【解决方案6】:

        不要这样做,但我觉得应该注意的是,运行时可以随时使用 class_addIvar 添加 ivars

        【讨论】:

        • 类一旦注册就不能再添加实例变量了。由于编译器生成的所有类都是自动注册的,这仅适用于通过调用objc_allocateClassPair创建的类
        【解决方案7】:

        我能够在我的图书馆中执行以下操作:

        myLib.h:

        @interface MyClass : SomeSuperClass <SomeProtocol> {
          // Nothing in here
        }
        
        - (void)someMethods;
        @end
        

        myLib.m

        @interface MyClass () 
            SomeClass *someVars;
        
            @property (nonatomic, retain) SomeClass *someVars;
        @end
        
        
        @implementation MyClass
        
        @synthesize someVar;
        
        - (void)someMethods {
        }
        @end
        

        协议当然是可选的。我相信这也会使您的所有实例变量成为私有,尽管我不是 100% 确定。对我来说,它只是我的静态库的一个接口,所以这并不重要。

        无论如何,我希望这对你有所帮助。对于其他阅读本文的人,请让我知道这是否总体上很糟糕或有任何不可预见的后果。我自己对 Obj-C 很陌生,所以我总是可以使用有经验的人的建议。

        【讨论】:

          【解决方案8】:

          我认为在另一个答案中编写的以下代码没有按预期工作。 扩展类中定义的“SomeClass *someVars”不是MyClass的实例变量。我认为它是一个 C 全局变量。如果你合成 someVars,你会得到编译错误。而且 self.someVars 也不起作用。

          myLib.h

          @interface MyClass : SomeSuperClass <SomeProtocol> {
              // Nothing in here
          }
          
          - (void)someMethods;
          @end
          

          myLib.m

          @interface MyClass () 
              SomeClass *someVars;
          
              @property (nonatomic, retain) SomeClass *someVars;
          @end
          
          @implementation MyClass
          
          @synthesize someVar;
          
          - (void)someMethods {
          }
          @end
          

          【讨论】:

            【解决方案9】:

            您可以使用类扩展。类扩展类似于类别,但没有任何名称。在 Apple 文档中,他们只定义了私有方法,但实际上您也可以声明内部变量。

            MyClass.h

            @class PublicClass;
            
            // Public interface 
            @interface MyClass : NSObject
            
            @property (nonatomic, retain) PublicClass *publicVar;
            @property (nonatomic, retain) PublicClass *publicVarDiffInternal;
            
            - (void)publicMethod;
            
            @end
            

            MyClass.m

            #import "PublicClass.h"
            #import "InternalClass.h"
            
            // Private interface
            @interface MyClass ( /* class extension */ ) 
            {
            @private
                // Internal variable only used internally
                NSInteger defaultSize;
            
                // Internal variable only used internally as private property
                InternalClass *internalVar;  
            
            @private 
                // Internal variable exposed as public property 
                PublicClass *publicVar;
            
                // Internal variable exposed as public property with an other name
                PublicClass *myFooVar;
            }
            
            @property (nonatomic, retain) InternalClass *internalVar;
            
            - (void)privateMethod;
            
            @end
            
            // Full implementation of MyClass
            @implementation MyClass
            
            @synthesize internalVar;
            @synthesize publicVar;
            @synthesize publicVarDiffInternal = myFooVar
            
            - (void)privateMethod 
            {
            }
            
            - (void)publicMethod 
            {
            }
            
            - (id)init
            {
               if ((self = [super init]))
                 {
                   defaultSize = 512;
                   self.internalVar = nil;
                   self.publicVar = nil;
                   self.publicVarDiffInternal = nil; // initialize myFooVar
                 }
            
               return self;
            }
            @end
            

            您可以只使用您的公共 API 和公共属性将 MyClass.h 提供给任何人。在 MyClass.m 上,您在类扩展上声明您的成员变量 private 和 public,以及您的私有方法。

            这样很容易暴露公共接口并隐藏细节实现。我用在我的项目上没有任何麻烦。

            【讨论】:

              【解决方案10】:

              根据我一直在查看的文档,没有问题。隐藏实例变量所需要做的就是在 @implementation 部分的开头,在 { ... } 内声明它们。但是,我是 Objective C 的新手,我有可能误解了一些东西——我怀疑语言已经改变了。我实际上已经尝试过这个系统,使用 XCode 4.2,为 iPad 构建代码,它似乎工作正常。

              我的这个想法的一个来源是http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocDefiningClasses.html 的 Apple 开发人员文档,它给出了这种模式:

              @implementation 类名

              {

              // Instance variable declarations.
              

              }

              // 方法定义。

              @结束

              【讨论】:

                猜你喜欢
                • 2013-08-18
                • 2010-11-01
                • 2013-07-27
                • 2013-02-02
                • 2023-04-04
                • 1970-01-01
                • 2011-12-09
                • 2013-03-01
                相关资源
                最近更新 更多