over 4 years ago

I discussed Java for several weeks, but in my job, the primary language I used is Objective-C in the recent one year. When I was still at school, I studied Objective-C for fun, but never used it in real projects. Thus, I didn't realize the interesting part of Objective-C. After I used Objective-C to develop projects in my job, I found that Objective-C is a charming language. It is a static-typed and complied language, but any message sending (calling a method) is determined at runtime, therefore, it also provides many features usually visible in the dynamic languages. It is an object-oriented language, and has @protocol (as the Interface in UML) and @interface (as the Class in UML), but does not provide obvious mechanism to control the access of an encapsulation, for example, the today's topic: protected methods.

To discuss the topic is because I am pair programming with two programmers new to Objective-C, and both of them asked me the same question: how to declare and use protected methods in Objective-C? For Java, C++, or C# programmers, using protected methods is just as nature as drinking water. I was not used to the absence of protected methods in Objective-C (although Objective-C has @public, @protected, and @private modifiers, they can only be used on member data), same as JavaScript. For the question, I reviewed the projects I developed in the recently years, and I found that protected methods become less. It may due to that I used lots of interface declaration and implementations, and reduced the depth of class hierarchy.

Figure 1 is a common-seen class diagram. In the figure, the orange dash line represents a boundary -- inside the boundary is a system or a package, and outside the boundary is the outer system. In the package, AbstractClass implements the interfaceMethod() method of the SomeInterface interface, and then add an abstract method protectedMethodB(). ConcreteClass extends AbstractClass to implement protectedMethodB(). Then, in the outer system, Context uses the ConcreteClass and a customized CustomizedClass. In such a design, Context can only see two methods: interfaceMethod() and publicMethod(). Only AbstractClass can see all methods. For ConcreteClass and CustomizedClass, the methods other than privateMethod() are visible.

Figure 1 - A class hierarchy

The programming languages trends to be dynamic languages. Therefore, the visibility is usually checked at compile time. Using Java as an example, calling private methods directly in code will get a compiler error, but with the reflection API, calling private methods indirectly is allowed. Same as Objective-C, the methods declared in the Header file are public methods, and the methods only placed in the Implementation file are private methods, but both can be invoked through the method objc_msgSend provided in Objective-C Runtime. Therefore, the simulation discussed here is to simulate the visibility of protected methods only at compile time.

The simulation uses the extension feature -- separate the header file into two parts. One part is AbstractClass.h as shown in Code List 2 that imports the SomeInterface in Code List 1 and declares the publicMethod() method. Another part is AbstractClass_Protected.h that declares the protectedMethodA() and protectedMethodB() methods as shown in Code List 3. Then, in Code List 4, AbstractClass.m imports AbstractClass_Protected.h and implements the interfaceMethod(), publicMethod(), protectedMethodA(), and privateMethod() methods. Objective-C does not have the concept of abstract methods, so AbstractClass.m throws exception to simulate the abstract methods.

Code List 1 - The SomeInterface.h
#import <Foundation/Foundation.h>

@protocol SomeInterface <NSObject>

- (void)interfaceMethod;

@end
Code List 2 - The AbstractClass.h
#import <Foundation/Foundation.h>

#import "SomeInterface.h"

@interface AbstractClass : NSObject<SomeInterface>

- (void)publicMethod;

@end
Code List 3 - The AbstractClass_Protected.h
#import "AbstractClass.h"

@interface AbstractClass ()

- (void)protectedMethodA;

- (void)protectedMethodB;

@end
Code List 4 - The AbstractClass.m
#import "AbstractClass.h"

#import "AbstractClass_Protected.h"

@implementation AbstractClass

- (void)interfaceMethod {
    NSLog(@"interface method invoked");
}

- (void)publicMethod {
    NSLog(@"public method invoked");
    [self protectedMethodA];
    [self protectedMethodB];
    [self privateMethod];
}

- (void)protectedMethodA {
    NSLog(@"protected method A invoked");
}

- (void)protectedMethodB {
    [NSException raise:@"AbstractMethodInvokedException" format:@"Abstract method shouldn't be invoked"];
}

- (void)privateMethod {
    NSLog(@"private method invoked");
}

@end

So far so good. And then discuss how to make the protectedMethodA() and protectedMethoB() methods visible in the inherted classes. ConcreteClass imports AbstractClass_Protected.h, overrides the protectedMethodB() method, as shown in Code List 5 and Code List 6, and in the overridden method publicMethod(), both the methods protectedMethodA() and protectedMethodB() can be used directly. The problem seems to be solved. Well, only a part of problem is solved. Another problem is derived from the extension: should I open AbstractClass_Protected.h to the outer system?

Code List 5 - The ConcreteClass.h
#import "AbstractClass.h"

@interface ConcreteClass : AbstractClass

@end
Code List 6 - The ConcreteClass.m
#import "ConcreteClass.h"

#import "AbstractClass_Protected.h"

@implementation ConcreteClass

- (void)publicMethod {
    NSLog(@"overridden protected method A invoked");
    [self protectedMethodA];
    [self protectedMethodB];
}

- (void)protectedMethodB {
    NSLog(@"ConcreteClass protected method B invoked");
}

@end

Like C++ language, a class is separated into a header file and a implementation file. Although the header file exposes the private methods (variables) to the user, the compiler will check the legality of the method access. In C# or Java language that does not have header files, the visibility is compiled into the bytecode. So while using a packed DLL file or JAR file, the visibility of a method can be checked by the compiler. However, the visibility of a method is Objective-C is determined by whether the header file that have the metod delcaration can be accessed or not. That is if AbstractClass_Protected.h is opened to the outer system, not only CustomizedClass can see the protectedMethodA and protectedMethodB methods, but also Context. Otherwise, neither CustomizedClas nor Context can see the protected methods.

Table 1 lists the comparsion of the protected methods visibility simulated by opened and closed extension. In the table, 'o' means visible, and 'x' means invisible. The first column lists the different class, the second column lists the expected visibility (protected methods are invisible only in Context), the third column lists the actual visibility if the extension is opened (protected methods are visible in all classes), and the forth column lists the actual visibility if the extension is closed to outer system (protected methods are invisible in the Context and CustomizedClass). It is unfortunate that the extension can simulate the protected methods, but not perfectly. The trade-off to open or not open the extension depends on the actual situation. In fact, opening the extension header file is the same as declaring the protected methods in the original header file, so I prefer not to open the extension header file.

Table 1 - Protected methods visibility comparison

Class Expected Opened Extension Closed Extension
Context x o x
AbstractClass o o o
ConcreteClass o o o
CustomizedClass o o x
← Protected Methods in Objective C 閒談軟體架構:關於Android App Architecture →