I found this StackOverflow question on friend classes in Objective-C which was similar to the issue I'm trying to solve, but not quite the same. So I'd like to extend their example.
Suppose I want the ability to hand a Monkey multiple Bananas and he'll keep an array of all of his own Bananas. When I tell him to eat, he eats from his array. I can then ask him to give me a Banana at random:
Banana.h
@interface Banana : NSObject
- (BOOL)isPeeled;
@end
Banana.m
#import "Banana.h"
@implementation Banana
{
@private
BOOL peeled;
}
- (id)init
{
self = [super init];
if (self)
{
peeled = NO;
}
return self;
}
- (void)peel // PRIVATE METHOD
{
peeled = YES;
}
- (BOOL)isPeeled
{
return peeled;
}
@end
Monkey.h
#import "Banana.h"
@interface Monkey : NSObject
- (void)give:(Banana *)aBanana;
- (void)eat;
- (Banana *)getBanana;
@end
Monkey.m
#import "Monkey.h"
@implementation Monkey
{
@private
NSMutableArray *bananas;
}
- (id)init
{
self = [super init];
if (self)
{
bananas = [[NSMutableArray alloc] init];
}
return self;
}
- (void)give:(Banana *)aBanana
{
[bananas addObject:aBanana];
}
- (void)eat
{
BOOL hungry = YES;
for (int i = 0; i < bananas.count; i++)
{
if (!banana.isPeeled)
{
hungry = NO;
[banana peel];
break;
}
}
if (hungry)
{
NSLog(@"Monkey ANGRY!");
}
}
- (Banana *)getBanana
{
int r = arc4random_uniform(bananas.count);
return [bananas objectAtIndex:r];
}
@end
In this case, Banana.h
and Monkey.h
would be public headers in a library I'm distributing. The intended use of my library would look something like so:
ClientApp.m
#import "MonkeyLib/MonkeyLib.h"
@implementation SomeClass
- (void)someMethod
{
Banana *oneBanana = [[Banana alloc] init];
Monkey *oneMonkey = [[Monkey alloc] init];
[oneMonkey give:oneBanana];
[oneMonkey eat];
Banana *twoBanana = [oneMonkey getBanana];
if (twoBanana.isPeeled)
{
NSLog(@"No sad monkeys!");
}
}
@end
The immediate issue with the code above is that my library won't compile because no interface for the class Banana
defines a peel
method (as utilized by Monkey.m
)
The StackOverflow question I linked at the beginning of this question suggests creating a third header file:
BananaPrivate.h
@interface Banana (PrivateMethods)
- (void)peel;
@end
Then in Monkey.m
I can import BananaPrivate.h
and I can suddenly call the peel
method. However this fails because when I attempt to import BananaPrivate.h
I create a collision -- I've already defined Banana
when I imported Banana.h
in Monkey.h
I can't remove Banana.h
from Monkey.h
, because one of the methods on Monkey
returns an instance of type Banana
!
I found a quirky work-around by sub-classing Banana
like so:
InternalBanana.h
@interface InternalBanana : Banana
- (void)peel;
@end
Then within Monkey.m
I can safely #import "InternalBanana.h"
. However this has two issues:
- It only works for Bananas instantiated by my Monkey (if a user of my library instantiates a Banana and passes it to my Monkey, it will be a Banana and not an InternalBanana)
- I have to create an
InternalBanana.m
file, but within this file I cannot definepeel
. Attempting to definepeel
withinInternalBanana.m
will throw an error thatpeeled
is not defined, sincepeeled
is a private variable of the super class. If I definepeel
withinInternalBanana.m
and it only calls[super peel]
, that will also fail sinceBanana
does not define apeel
method. While this works, it does cause Xcode to throw a warning saying that there is no method definition forpeel
(but it builds and runs despite the warning)
What is the proper solution to this monkey business?