views:

741

answers:

6

I came across a library written in Objective C (I only have the header file and the .a binary). In the header file, it is like this:

@interface MyClass : MySuperClass 
{ 
    //nothing here
}

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

How can I achieve the same thing? If I try to declare a property without its corresponding ivar inside the interface's {}, the compiler will give me an error. Ultimately, I want to hide the internal structure of my class inside the .a, and just expose the necessary methods to the header file. How do I declare instance variables inside the .m? Categories don't allow me to add ivar, just methods.

+1  A: 

No you can't. But you can do this if you're not using @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
KennyTM
+8  A: 

For 64 bit applications and iPhone applications (though not in the simulator), property synthesis is also capable of synthesizing the storage for an instance variable.

I.e. this works:

@interface MyClass : MySuperClass 
{ 
    //nothing here
}

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

@implementation MyClass
@synthesize anObject;
@end

If you compile for 32 bit Mac OS X or the iPhone Simulator, the compiler will give an error.

bbum
You are right! When I switch from Simulator to Device, the error disappears.
iamj4de
Note that, over time, the simulator has grown more and more like the iOS runtime... this works in the latest release, for example.
bbum
+1  A: 

Two possibilities:

  1. It could be taking advantage of the modern runtime's ability to synthesize instance variables, as bbum suggested.
  2. The property might not have an underlying instance variable in that class. Properties do not necessarily have a one-to-one mapping with instance variables.
Chuck
A: 

How about a macro trick?

Have tested code below

  • have tested with dylibs - worked fine
  • have tested subclassing - Warning! will break, I agree this makes the trick not that useful, but still I think it tells some about how ObjC works...

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
epatel
The unprocessed header file will be unusable without the implementation file.
Chuck
Made a quick test, worked just fine. Might be worth a blog post later...
epatel
Thanks, nice trick. If you do write a blog post, please give the link here.
iamj4de
Have checked by calling `NSLog(@"%s: %d", __FILE__, class_getInstanceSize([MyClass class]));` both within MyClass.m and outside...gives same value so I think it's pretty safe too
epatel
No, it cannot possibly work in a project that does not have the .m file. You're saying you tested with just the .h and a compiled .a library and it worked?
Chuck
bbum
@bbum I agree that subclassing can be a problem. But I'll have a go anyway, my gut feeling is that the member part of the @interface is only used by the @implementation part which can be hidden in a .m file, so the members might not be necessary (unless you need them in a subclass).
epatel
@Chuck I'll do that test just to please you. The MyClass.m file is compiled to a MyClass.o file, which can easily be placed in a library. I'm confident it will work as the important parts seem to be created by the @implementation part (which is compiled seeing the members)
epatel
@Chuck I have tested creating a library now and only supplying the library and a header. Guess what...it worked fine, even as a dylib.
epatel
@bbum Have tested with subclassing now and it will break. Interesting.
epatel
+2  A: 

You may use of the same idiom used in Cocoa classes. If you have a look to NSString class interface in NSString.h you'll see that there is no instance variable declared. Going deeper in GNUstep source code you'll find the trick.

Consider the following code.

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

As you can see, the trick consist in overloading allocWithZone: in your class. This code is invoked by default alloc provided by NSObject, so you don't have to worry about which allocating method should be used (both are valid). In such allocWithZone:, you may use the Foundation function NSAllocateObject() to allocate memory and initialize isa for a MyClassImpl object instead of MyClass. After that, the user is dealing with a MyClassImpl object transparently.

Of course, the real implementation of your class shall be provided by MyClassImpl. The methods for MyClass shall be implemented in a way that considers a message receiving as an error.

Alvaro Polo
A: 

DON'T do this, but I feel it should be noted that the runtime has the ability to add ivars whenever you want with class_addIvar

Jared P