Guided by Bipin Ajay founder of Tech Jitendra ,
Thank you for reading. If you like the article stay tunned and follow me . Hope to see you next time .
For every Object Oriented language three concepts stand out: encapsulation, inheritance, and polymorphism. The concepts are general for programming languages, but strongly associated with Object Oriented Programming. I decided to explore the concepts as they are in Swift/Objective-C and implement them in C.
Some concepts have different forms in programming languages. For each concept I provide short description of what the concepts is.
C
If you’re a C programmer you can skip this part. But it you never touched C code or feel a bit rusty, here’s a quick refresher on language constructions used in example.
C has two files:
- .h — header file, contains function and data declarations, included by clients code;
- .c — source code, implementation.
We will work with pointers. Pointer is a typed reference to location in memory. Pointers are declared using asterisk after the type.
int *pointer;
We can create a pointer to anything in memory.
There’s no classes in C, but we have structures.
struct Foo {
int member;
};
Structures can contain only data not functions. But we have pointers to functions.
struct Foo {
int member;
void (*function)(void);
};
Looks familiar if you used blocks in Objective-C.
C requires declaration before use.
struct Foo;
void function(void);
We can create aliases to types for our convenience.
typedef struct Foo *FooRef; // FooRef is now a pointer to Foo
FooRef foo;
We can access members of a structure using dot notation. Accessing pointer value is called dereferencing. For members of a structure we can use -> operator.
*pointer = 123;foo->member = 123;
In C we can allocate and deallocate memory directly, without any memory management mechanism. This is performed using malloc and freefunctions.
FooRef foo = (FooRef)malloc(sizeof(Foo));
free(foo);
In general C is very focused on working with memory and pointers. Some features are implemented by using well defined memory layout.
Encapsulation
Encapsulation is the mechanism to restrict access to certain data, variables or fields. By restricting access to data we prevent unexpected changes. Encapsulation instead exposes functions to operate on that data.
Encapsulation restricts direct access to data and exposes functions to operate on that data.
Here’s an example to demonstrate the concept. Say I’m working on a drawing app and my drawing are represented by the Canvas class. It contains specific information needed to render (such as size, pixel density, color space, etc.), whether the destination is a display, a printer, or a bitmap.
class Canvas {
/// width of the canvas in pixels
private(set) var width: Int
/// height of the canvas in pixels
private(set) var height: Int init() {
width = 320
height = 240
}
}
Swift encapsulates data through properties. In Objective-C, property is a combination of instance variable and synthesized accessor method(s).
@interface Canvas : NSObject@property (nonatomic, readonly) int width;
@property (nonatomic, readonly) int height;@end
In C we use forward declaration for data structure and function in header file and implement them in .c file.
// Canvas.htypedef struct Canvas * CanvasRef;extern int CanvasGetWidth(CanvasRef canvas);
extern int CanvasGetHeight(CanvasRef canvas);extern CanvasRef CanvasCreate(void);
// Canvas.ctypedef struct Canvas {
int width;
int height;
} Canvas;int CanvasGetWidth(CanvasRef canvas) {
return canvas->width;
}int CanvasGetHeight(CanvasRef canvas) {
return canvas->height;
}CanvasRef CanvasCreate(void) {
Canvas *canvas = (Canvas *)malloc(sizeof(Canvas)); canvas->width = 320;
canvas->height = 240; return canvas;
};
All three languages provide encapsulation mechanisms. Naturally, encapsulation in Objective-C is very similar to C. With exception that the compiler will synthesize accessor methods for properties at compile time.
Encapsulation mechanism in Swift and Objective-C is weaker than C. This is general problem with any Object Oriented Language. What makes it weaker is necessary relationship between objects. Sometimes we need our inherited objects have access to parent data. This leads to a complex mix of access modifiers.
For Objective-C most concerning is dynamic nature of the language, partially inherited by Swift. Key-Value Coding, for instance, opens encapsulated data to unexpected access without any compiler protection.
Encapsulation in C is most strict. Everything is hidden in .c file, there is no way to access encapsulated data from the outside.
Inheritance
Inheritance allows creating new objects based on existing, inheriting properties and behaviour.
Swift and Objective-C provide inheritance mechanisms. Usually we use inheritance to extend existing type for a specific case, specialize more generic type. In example DisplayCanvas extends Canvas to provide color profile information.
/// The canvas when rendering destination is a display
class DisplayCanvas: Canvas {
/// The color profile of a display
enum ColorSpace {
/// Standard RGB color space
case sRGB
/// Wide gamut RGB (DCI-P3) color space
case wideGamutRGB
} private(set) var colorSpace: ColorSpace override init() {
colorSpace = .wideGamutRGB
super.init()
}
}
In C we can use composition to simulate inheritance.
// Canvas.htypedef enum ColorSpace {
sRGB = 0,
wideGamutRGB
} ColorSpace;extern CanvasRef DisplayCanvasCreate(void);
// Canvas.mtypedef struct DisplayCanvas {
Canvas super;
ColorSpace colorSpace;
} DisplayCanvas;CanvasRef DisplayCanvasCreate(void) {
DisplayCanvas *canvas
= (DisplayCanvas *)malloc(sizeof(DisplayCanvas)); canvas->super.width = 320;
canvas->super.height = 240;
canvas->colorSpace = wideGamutRGB; return (CanvasRef)canvas;
};
Inheritance in Swift and Objective-C not only allows using existing objects to create new, it is also subtyping mechanism. Subtyping allows using inherited type where the base type can be used, substitute the base type. In 1994 computer scientists Barbara Liskov and Jeannette Wing formulated Liskov Substitution Principle:
Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T.
Barbara Liskov and Jeannette Wing
Inheritance allows creating new objects based on existing, inheriting properties and behaviour. We must be able to use the new object where the base object can be used.
For our example we must be able to use DisplayCanvas wherever we can use Canvas.
/// Draws canvas to a destination
func draw(_ canvas: Canvas) {
print("width: \(canvas.width), height: \(canvas.height)")
}let canvas = DisplayCanvas()
draw(canvas)width: 320, height: 240
Same works in C.
// Canvas.hextern void CanvasDraw(CanvasRef canvas);
// Canvas.mvoid CanvasDraw(CanvasRef canvas) {
printf("width: %d, height: %d\n",
canvas->width, canvas->height);
}
// main.cCanvasRef canvas = DisplayCanvasCreate();
CanvasDraw(canvas);width: 320, height: 240
Because of the way C aligns structures in memory, we can cast pointers from one to the other if first elements are the same. If we would change order of members in our structure cast will not work.
typedef struct DisplayCanvas {
ColorSpace colorSpace;
Canvas super;
} DisplayCanvas; // We can not safely cast DisplayCanvas to Canvas
Another approach to inheritance would be using union and enum in base structure to identify the type.
typedef struct DisplayCanvas {
ColorSpace colorSpace;
} DisplayCanvas;typedef struct PrintCanvas {
int dpi;
} PrintCanvas;typedef enum CanvasType {
display = 0,
print
} CanvasType;typedef struct Canvas {
union {
DisplayCanvas display;
PrintCanvas printer;
} super;
CanvasType type;
} Canvas;
This way we can simulate type(of:) and isMemberOfClass: checks.
Thanking You
Tech Jitendra .