C# to Objective-C - Part 7 : Namespaces

In C#, namespaces essentially serve 2 specific purposes: 

  1. Avoid naming collisions
  2. Inform a class of objects that are available. 

Lets take a look at the latter first: 

Using vs Import

C#

using System; 
using System.Text;
using MyCompany.CustomNamespace;

Objective-C

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "CustomClass.h"

As you can see, Objective-C does not  have  a concept of namespaces. Everything is essentially file based. You basically are informing each class about what ever interfaces that have been defined in other header files.

The import statement with quotes means the the header file has been defined in this project. The other import statements have been defined in another framework, where as the first part is the framework name, and the second part is the header file being referenced. A general rule that most frameworks follow, is to create a header file with the same name as the framework, that has import stmts to all publicly exposed classes in the framework.

While imports seem generally straight forward, they can easily lead to confusion and frustration if you don't fully understand how to use them. Probably the most common issue is when you  define 2 objects that have references to each other. This generally happens when you have a protocol and a property on your class that consumes it:

@protocol MyCustomDelegate
 
-(BOOL)customObject:(MyCustomObject*)obj supportsSomethingAtIndex:(int)index;
 
@end
 
@interface MyCustomObject : NSObject
 
@property(nonatomic, weak)id delegate;
 
@end

If you were to try and compile the code above, you'd immediately run into a compiler error. The problem is, that since each class has a dependency on the other, the compiler can't figure out what to do. 

That kind of sucks :) But relax, there is a very simple solution, you'll need to use the "@class" compiler directive.  

@class MyCustomObject;
 
@protocol MyCustomDelegate
 
-(BOOL)customObject:(MyCustomObject*)obj supportsSomethingAtIndex:(int)index;
 
@end
 
@interface MyCustomObject : NSObject
 
@property(nonatomic, weak)id delegate;
 
@end

Basically, all we're are doing is telling the compiler that the "MyCustomObject" will exist at some point, so just pretend its there for now. 

Ok, so thats probably the simplest way at looking at the problem, and was easy to solve b/c the protocol and interface were defined in the same file. But what happens when you define 2 objects in separate files.

// MyCustomObject.h
 
#import "MyOtherCustomObject.h"
 
@interface MyCustomObject : NSObject
@property(nonatomic, retain)MyOtherCustomObject* obj;
@end
// MyOtherCustomObject.h
 
#import "MyCustomObject.h"
 
@interface MyOtherCustomObject : NSObject
@property(nonatomic, retain)MyCustomObject* obj;
@end

Above we have to separate header files, with 2 different objects, that each have a reference to each other and if you try and compile you'll get an error.  So how do we sove this?

Well believe it or not, its pretty much the same solution:

// MyCustomObject.h
 
#import "MyOtherCustomObject.h"
 
@interface MyCustomObject : NSObject
@property(nonatomic, retain)MyOtherCustomObject* obj;
@end
// MyOtherCustomObject.h
 
@class MyCustomObject;
 
@interface MyOtherCustomObject : NSObject
@property(nonatomic, retain)MyCustomObject* obj;
@end

However, we're not completely done, if we try to access the obj property in the MyOtherCustomObject implementation, we'll get another compiler exception. 

// MyOtherCustomObject.m
 
#import "MyOtherCustomObject.h"

@implementation MyOtherCustomObject
-(id)init
{
   self = [super self];
   if(self)
   {
       self.obj = [[MyCustomObject alloc]init];
   }
   return self;
}
@end

So what did we miss? Well, b/c we use @class in the header, we didn't' actually inform the class about the MyCustomObject, we basically told it to just pretend that its there for now. So when we tried to actually access this pretend class the compiler got angry at us. 

But, again the solution turns out to be simple. In the Implementation file we can import the MyCustomObject header file, b/c the compiler has already done its walkthrough and validated all of our objects. 

// MyOtherCustomObject.m
 
#import "MyOtherCustomObject.h"
#import "MyCustomObject.h"

@implementation MyOtherCustomObject
-(id)init
{
   self = [super self];
   if(self)
   {
       self.obj = [[MyCustomObject alloc]init];
   }
   return self;
}
@end

And just like that, we've solved the problem. Obviously this is no where as intuitive as namespaces in C#, but once you begin to understand how the compiler works and to take advantage of importing header files, it does become second nature. 

Naming Collisions


In the beginning of this post i mentioned two important problems that namespaces solve.  And with Import statements you solve one of the issues. However, it does not solve the collision issue. 

In reality the best way to avoid collisions is to simply name your classes carefully. Its a really good idea to include prefixes on all of your class names so that they remain unique. 

However, if you're stuck using another framework that didn't follow those guidelines, you can try using the following compiler directive:

@compatibility_alias NewClassName OriginalClassName;

With this directive, you can rename a competing class, and thus fix the naming conflict you've run into. I've never actually run into this issue before, but its a good tool to have in your toolbox in case you ever do. 

Well that about wraps up my explanation of namespaces. As always I hope this was helpful. 

If you did find this interesting and hunger for more comparisons of C# and Objective-C, you can read more of this series on my blog

If there is a topic you've been waiting for me to talk about that I haven't covered yet, let me know on twitter:@codeBySteveZ

By Stephen Zaharuk (SteveZ)


 


Tags / ,