Скрыть переменную экземпляра из заголовочного файла в Objective C - PullRequest
5 голосов
/ 20 января 2010

Я натолкнулся на библиотеку, написанную на Objective C (у меня есть только заголовочный файл и двоичный файл .a). В заголовочном файле это выглядит так:

@interface MyClass : MySuperClass 
{ 
    //nothing here
}

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

Как мне достичь того же? Если я попытаюсь объявить свойство без соответствующего ивара внутри интерфейса {}, компилятор выдаст мне ошибку. В конечном счете, я хочу скрыть внутреннюю структуру моего класса внутри .a и просто предоставить необходимые методы в заголовочный файл. Как мне объявить переменные экземпляра внутри .m? Категории не позволяют мне добавлять ivar, только методы.

Ответы [ 10 ]

14 голосов
/ 20 января 2010

Для 64-битных приложений и приложений iPhone (хотя и не в симуляторе) синтез свойств также способен синтезировать хранилище для переменной экземпляра.

т.е. это работает:

@interface MyClass : MySuperClass 
{ 
    //nothing here
}

@property (nonatomic, retain) MyObject *anObject;
@end

@implementation MyClass
@synthesize anObject;
@end

Если вы компилируете для 32-битной Mac OS X или iPhone Simulator, компилятор выдаст ошибку.

13 голосов
/ 05 апреля 2010

Вы можете использовать ту же идиому, которая используется в классах Какао.Если вы посмотрите на интерфейс класса NSString в NSString.h, вы увидите, что не объявлена ​​переменная экземпляра.Углубившись в исходный код 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: в вашем классе.Этот код вызывается по умолчанию alloc , предоставленным NSObject , поэтому вам не нужно беспокоиться о том, какой метод распределения следует использовать (оба действительны).В таком allocWithZone: вы можете использовать функцию Foundation NSAllocateObject () для выделения памяти и инициализации isa для объекта MyClassImpl вместо MyClass .После этого пользователь прозрачно обрабатывает объект MyClassImpl .

Конечно, реальная реализация вашего класса должна обеспечиваться MyClassImpl .Методы для MyClass должны быть реализованы таким образом, чтобы считать получение сообщения ошибкой.

6 голосов
/ 09 сентября 2011

Вы можете использовать расширение класса. Расширение класса похоже на категорию, но без имени. В документации 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

Вы можете передать MyClass.h любому, у кого есть только публичный API и общедоступные свойства. На MyClass.m вы объявляете переменную-член private и public и ваши private-методы в расширении вашего класса.

Таким образом, вы можете легко открывать открытые интерфейсы и скрывать детализацию реализации. Я использовал свой проект без каких-либо проблем.

2 голосов
/ 16 января 2012

Согласно документации, на которую я смотрел, проблем нет. Все, что вам нужно сделать, чтобы скрыть переменные экземпляра, это объявить их в начале раздела @implementation, внутри {...}. Тем не менее, я относительный новичок в Objective C, и есть шанс, что я что-то неправильно понял - я подозреваю, что язык изменился. Я на самом деле пробовал эту систему, используя XCode 4.2, сборку кода для iPad, и она, кажется, работает нормально.

Одним из моих источников этой идеи является документация для разработчиков Apple по адресу http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocDefiningClasses.html,, в которой приведен такой шаблон:

@ реализация ClassName

{

// Instance variable declarations.

}

// Определения методов.

@ конец

1 голос
/ 20 января 2010

Две возможности:

  1. Возможно, что современная среда выполнения может синтезировать переменные экземпляра, как предположил bbum.
  2. Свойство может не иметь базовой переменной экземпляра в этом классе. Свойства не обязательно имеют однозначное сопоставление с переменными экземпляра.
1 голос
/ 20 января 2010

Нет, ты не можешь. Но вы можете сделать это, если вы не используете @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
0 голосов
/ 01 апреля 2011

Не думаю, что следующий код, написанный в другом ответе, работает должным образом. «SomeClass * someVars», определенный в классе расширения, не является переменной экземпляра MyClass. Я думаю, что это глобальная переменная Си. Если вы синтезируете некоторые переменные, вы получите ошибку компиляции. И 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
0 голосов
/ 02 декабря 2010

Мне удалось сделать следующее в моей библиотеке:

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, поэтому всегда могу воспользоваться советами опытных.

0 голосов
/ 05 апреля 2010

НЕ делайте этого, но я чувствую, что следует отметить, что среда выполнения имеет возможность добавлять ivars всякий раз, когда вы хотите с помощью class_addIvar

0 голосов
/ 20 января 2010

Как насчет макро-трюка?

Проверенный код ниже

  • протестировали с 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
...