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.mfile, but within this file I cannot definepeel. Attempting to definepeelwithinInternalBanana.mwill throw an error thatpeeledis not defined, sincepeeledis a private variable of the super class. If I definepeelwithinInternalBanana.mand it only calls[super peel], that will also fail sinceBananadoes not define apeelmethod. 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?