Blog by Railsware

Using Forward Declaration In Your Objective-C Projects

Forward Declaration In Your Objective-C Projects
Screen Shot 2013-08-08 at 16.56.23If you’re new to Objective C and iOS development, this topic will explain some important things about Forward Declaration and will help you understand the concept behind it.So, let’s skip the theory and jump right into a small example to see what it’s all about.We’ll create a small project, that is quite useless, but will at the same time serve as a good example and cmake utility.So, here is…Empty class Warrior
// Warrior.h
#import <Foundation/Foundation.h>

@interface Warrior : NSObject

@end
// Warrior.m
#import "Warrior.h"

@implementation Warrior

@end
Empty class Armor
// Armor.h
#import <Foundation/Foundation.h>

@interface Armor : NSObject

@end
// Armor.m
#import "Armor.h"

@implementation Armor

@end
main.m as a driver – it just creates Warrior object and prints it
// main.m
#import <Foundation/Foundation.h>
#import "Warrior.h"

int main() {
    Warrior *w = [Warrior new];
    NSLog(@"%@", w);
    return 0;
}
And cmake configuration file
// CMakeLists.txt
cmake_minimum_required(VERSION 2.8)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -framework Foundation")

set(SOURCES Warrior.m Armor.m main.m)
add_executable(fdecl ${SOURCES})
So, let’s build our simple program
$ cd directory_with_project
$ cmake . # prepare makefile
$ make # build
We see which modules are being built
Scanning dependencies of target fdecl
[ 33%] Building CXX object CMakeFiles/fdecl.dir/Warrior.m.o
[ 66%] Building CXX object CMakeFiles/fdecl.dir/Armor.m.o
[100%] Building CXX object CMakeFiles/fdecl.dir/main.m.o
Linking CXX executable fdecl
[100%] Built target fdecl
And now we’ll add an ability to wear armor
// Warrior.h
@interface Warrior : NSObject

- (void)wearArmor:(Armor *)armor;

@end
// Warrior.m
#import "Warrior.h"

@implementation Warrior
{
    Armor *_armor;
}

- (void)wearArmor:(Armor *)armor
{
    _armor = armor;
}

@end
Remember this state, we’ll get back to it later.We need to inform compiler about the Armor class in order to compile these changes.The first approach is to use #include/#import statements and it’s being used by a lot of developers.So let’s do it
// Warrior.h
#import "Armor.h"

@interface Warrior : NSObject

- (void)wearArmor:(Armor *)armor;

@end
and build
$ make
Scanning dependencies of target fdecl
[ 33%] Building CXX object CMakeFiles/fdecl.dir/Warrior.m.o
[ 66%] Building CXX object CMakeFiles/fdecl.dir/main.m.o
Linking CXX executable fdecl
[100%] Built target fdecl
Looks good, but let’s add some property to our instance of Armor class, e.g. ‘is it magical?’
// Armor.h
#import <Foundation/Foundation.h>

@interface Armor : NSObject

- (BOOL)isMagical;

@end
// Armor.m
#import "Armor.h"

@implementation Armor 

- (BOOL)isMagical 
{
    return YES;
}

@end
and let’s build our program again
$ make
Scanning dependencies of target fdecl
[ 33%] Building CXX object CMakeFiles/fdecl.dir/Warrior.m.o
[ 66%] Building CXX object CMakeFiles/fdecl.dir/Armor.m.o
[100%] Building CXX object CMakeFiles/fdecl.dir/main.m.o
Linking CXX executable fdecl
[100%] Built target fdecl
Good news is that the build of main.m was successful. There’s one thing though. Despite of the fact that the build has been successful, workflow has not been optimal – main doesn’t use Armor, therefore, it must know nothing about this class.Ok, let’s get back to the state where we’ve added wearArmor: method, but haven’t included Armor yet.Now we’ll go another way:Use #import at implementation (Warrior.m) and forward declaration (@class Armor;) at header (Warrior.h)
// Warrior.h
@class Armor;

@interface Warrior : NSObject

- (void)wearArmor:(Armor *)armor;

@end
// Warrior.m
#import "Armor.h"

@implementation Warrior
{
    Armor *_armor;
}

- (void)wearArmor:(Armor *)armor
{
    _armor = armor;
}
@end
Make it (make sure that you’ve removed isMagical from Armor, we need this to get the same experience as before)
$ make
Add isMagical method again
// Armor.h
#import <Foundation/Foundation.h>

@interface Armor : NSObject

- (BOOL)isMagical;

@end
// Armor.m
#import "Armor.h"

@implementation Armor 

- (BOOL)isMagical 
{
    return YES;
}

@end
Build and check the results
$ make
Scanning dependencies of target fdecl
[ 33%] Building CXX object CMakeFiles/fdecl.dir/Warrior.m.o
[ 66%] Building CXX object CMakeFiles/fdecl.dir/Armor.m.o
Linking CXX executable fdecl
[100%] Built target fdecl
Now it looks much better. Did you notice an important difference?In our example we have only three modules, so the benefit is barely noticeable.Things change dramatically when you’re working on a large project consisting of numerous classes and modules with lots of dependencies and complex relationships. Every time you add some small thing into the project you’ll have to wait more and more for compiler to re-build all the things. This is where you can get the full benefit from utilising FD approach.You can experience exactly the same problems when using @protocols as delegates.
// Awesome.h
@class Awesome;

@protocol AwesomeDelegate

- (void)awesome:(Awesome *)awesome didFailed:(NSError *)error;

@end

@interface Awesome : NSObject

@property (weak, nonatomic) id delegate;

@end
// and then include this header into each class that want to be an awesome delegate
So the right way is to use different files for a delegate and a class, which use this a delegate:
// AwesomeDelegate.h
@class Awesome;

@protocol AwesomeDelegate

- (void)awesome:(Awesome *)awesome didFailed:(NSError *)error;

@end
// Awesome.h
@protocol AwesomeDelegate;

@interface Awesome : NSObject

@property (weak, nonatomic) id delegate;

@end
To summarise, keep in mind the importance of forward declaration, as it might save your time dramatically with every build you run for the project.In short, your goal is to create as clean interface as possible, that would be readable and maintainable by others in the future. Therefore, it must contain only imports of a base class and protocols, which current class conforms to. For all the rest there’s… no, not MasterCard, forward declaration in this case.
Exit mobile version