Introduction
When I started coding for iOS, I realized that I would spend more time to figure out Objective-C's weird stuff, being a C++ developer. Here is a quick guide for C++ experts to find their way through Apple's iOS language quickly.
Note that this is by no means a complete guide, but it's here so you avoid reading an 100-page manual. Besides, I know you love my writing style anyway.
Background
C++ skills required, I will compare C++ stuff with Objective-C stuff. Also, COM programming is useful since Objective-C objects are similar to IUnknown, so basic COM knowledge will be helpful (but not required).
Objective C++ is C++ and Objective-C together. You can use any C++ stuff and mix it with any Objective-C stuff, just remember to rename your file from .m to .mm.
Taram-Taram!
We are starting immediately our tutorial. First I give the Objective-C stuff, then it's C++ equivalent.
Member functions
- (int) foo : (int) a : (char) b {}
+ (int) foo : (int) a : (char) b {}
int foo(int a,char b) {}
static int foo(int a,char b) {}
- (void) foo2 val1:(int) a;
[obj foo2 val1:5];
- indicates a normal member function (accessible through an object instance), where + indicates a static member function, accessible without instance. Of course, like C++, static members cannot access instance variables.
In addition, Objective-C functions can have named arguments which make it more clear which parameter gets what value. In theory, named arguments would also allow the programmer to pass parameters in any order, however Objective-C requires the same order as in the declaration.
Calling a member through a pointer, or a static member
NSObject* ptr = ...;
[ptr foo:5:3];
[NSObject staticfoo:5:3];
CPPObject* ptr = ...;
ptr->foo(5,3);
CPPObject::staticfoo(5,3);
Objective-C uses [ ] to call member functions and passes parameters separated with :. Unlike C++, it's very fine for ObjectiveC for ptr to be nil, in which case the "call" is ignored (where, in C++, it would throw a pointer violation exception). This makes it possible to eliminate checks against nil objects.
Protocols vs Interfaces
@protocol foo
- (void) somefunction;
@end
@interface c1 : NSObject<foo>
@end
@implementation c1
- (void) somefunction { ... }
@end
class foo
{
virtual void somefunction() = 0;
};
class c1 : public NSObject, public foo
{
void somefunction() { ... }
}
Protocol = Abstract class. The difference between Objective-C and C++ is that, in Objective-C, the functions are not required to be implemented. You can have forced an optional methods, but this is merely a hint to the compiler and not a requirement for compilation.
Checking if a method is implemented
NSObject* ptr = ...;
[ptr somefunction:5:3];
CPPObject* ptr = ...;
ptr->somefunction(5,3);
Objective-C member functions are "messages" (Smalltalk) and when, in Objective-C we say that the receiver (the pointer) responds to a selector, it means that it implements the virtual function we are trying to call. When there is an interface, C++ objects must implement all of its member functions. In Objective-C this is not required, so we can send a "message" to somewhere that does not necessarily implement it (so an exception is raised).
NSObject* ptr = ...;
if ([ptr respondsToSelector:@selector(somefunction::)]
[ptr somefunction:5:3];
Now we are sure that the receiver responds to the selector, so we can call it. In C++ this check is not needed, because implementations must always "respond to the selector", or the source does not compile. Note that we must know how many parameters the selector gets (hence the 2 ::s in the @selector :)
Downcasting
NSObject* ptr = ...;
if ([ptr isKindOfClass:[foo class]]
[ptr somefunction:5:3];
CPPObject* ptr = ...;
foo* f = dynamic_cast<foo*>(ptr);
if (f)
f->somefunction(5,3);
It's like downcasting in C++ now, only with the "isKindOfClass" helper of NSObject, the base of all Objective-C classes.
Conforms to protocol?
NSObject* ptr = ...;
if ([ptr conformsToProtocol:@protocol(foo)]
[ptr somefunction:5:3];
CPPObject* ptr = ...;
foo* f = ptr;
f->somefunction(5,3);
Now we check if the receiver conforms to a protocol (or, in C++, implements an interface), so we can send messages that this protocol contains. Hey, it's mostly like Java classes and interfaces, where, in C++, there is no technical difference between a fully implemented class and an "interface".
void* or id or SEL?
id ptr = ...;
if ([ptr conformsToProtocol:@protocol(foo)]
[ptr somefunction:5:3];
SEL s = @selector(foo:);
void* ptr = ...;
foo* f = dynamic_cast<foo*>(ptr);
if (f)
f->somefunction(5,3);
id is the generic void*-like for Objective-C classes. You have to use id instead of void* because id can be a managed pointer through ARC (more on this later) and therefore, the compiler needs to distinguish between raw pointer types and Objective-C pointers. SEL is a generic type for selectors (C++ function pointers) and you generally create a selector by using the keyword @selector along with the function name and a number of :::::s, depending on how many parameters can be passed. A selector is actually a string, which binds to a method identifier at runtime.
Class declaration, methods, data, inheritance.
@class f2;
@interface f1 : NSOBject
{
int test;
@public
int a;
int b;
f2* f;
}
- (void) foo;
@end
@implementation f1
- (void) foo
{
a = 5;
self->a = 5;
super.foo();
}
@end
class f1 : public CPPObject
{
int test;
public:
class f2* f;
int a;
int b;
void foo();
}
void f1 :: foo()
{
a = 5;
this->a = 5;
CPPOBject::foo();
}
Implementation scope in Objective-C is within the @implementation/@end tags (where in C++ we can implement it anywhere with the :: scope operator). It uses the @class keyword for forward declarations. Objective-C has private protection as default, but only for data members (methods must be public). Objective-C uses self instead of this and it calls it's parent class through the super keyword.
Constructors and destructors
NSObject* s = [NSObject alloc] init];
[s retain];
CPPObject* ptr = new CPPObject();
ptr->AddRef();
NSObject* s = [NSObject alloc] initwitharg:4];
[s release];
CPPOBject* ptr = new CPPOBject(4);
ptr->Release();
Memory allocation in Objective-C is done by calling the static member function alloc, which all objects have (descendants of NSObject). self is assignable in Objective-C and it is set to nil if construction failed (where, in C++, an exception is thrown). The actual constructor call after memory allocation is a common member function which, by default, is init in Objective-C.
Objective-C uses the same reference counting method as COM, and it uses retain and release (equal to AddRef() and Release() methods of IUnknown). When the reference count reaches zero, dealloc is automatically called (the destructor) and then the object is removed from memory.
Multithreading
@interface f1 : NSOBject
{
}
- (void) foo;
- (void) threadfunc :(NSInteger*) param;
- (void) mt;
@end
@implementation f1
- (void) threadfunc : (NSInteger*) param
{
[self performSelectorOnMainThread: @selector(mt)];
}
- (void) mt
{
}
- (void) foo
{
[self performSelectorInBackground: @selector(thradfunc:) withObject:1 waitUntilDone:false];
<div>}
@end
Objective-C has build-in functions to NSObject that can perform a selector (== call a member) in another thread, in the main thread, wait for a call etc. See more on NSObject.
Memory and ARC
@interface f1 : NSObject
{
}
@property (weak) NSAnotherObject* f2;
@end
- (void) foo
{
NSObject* s = [NSObject alloc] init];
}
This is where you will forget your good C++ habits. OK Objective-C used to have a garbage collection, which we, C++ programmers, hate, as it's slow and reminds us of Java. But ARC (automatic reference counting) is a compile time feature that tells the compiler "here are my objects: please figure out when they have to be destroyed". With ARC you do not need to send retain/release messages to your objects; the compiler does that automatically.
To help the compiler decide how long to retain an object, you also have weak references to variables. By default, all variables are strong references (== as long they exist, the object exists) . You can also have a weak reference, which loses it's value as long as every other strong reference is out. This is useful in class members that get their values from the XCode Builder Interface (like the RC editor) and, when the class is destroyed, those members lose their value as well.
You can think of all this in C++ terms of shared_ptr and weak_ptr.
Strings
NSString* s1 = @"hello";
NSString* s2 = [NSString stringWithUTF8String:"A C String"];
sprintf(buff,"%s hello to %@","there",s2);
const char* s3 = [s2 UTF8String];
NSString is a non mutable representation of an Objective-C string. You can create it by using one of it's static methods, or by a string literal with the @ prefix. You can also use %@ to represent a NSString to the printf family.
Arrays
NSArray* a1 = [NSArray alloc] initWithObjects: @"hello",@"there",nil];
NSString* first = [a1 objectAtIndex:0];
NSArray and NSMutableArray are the two classes that handle arrays in Objective-C (the difference is that NSArray elements must be put in construction time by using one of its constructors, whereas NSMutableArray can be altered later). Constructors have the typical form of printf signature, you have to pass nil to "end the items". There are sort/search/insert functions to NSArray and NSMutableArray as well, in the first case it returns a new NSArray whereas, in the NSMutableArray case, it modifies the existing object.
Categories
class MyString : public string
{
public:
void printmystring() { printf("%s",c_str()); }
};
@interface MyString (NSString)
- (void) printmystring;
@end
@implementation MyString (NSString)
- (void) printmystring
{
printf("%@",self);
}
@end
MyString s1 = "hi";
s1.printmystring();
string s2 = "hello";
s2.printmystring();
NSString* s2 = @"hello";
[s2 printmystring];
C++ relies on inheritance to extend a known class. This can be bothersome because all users of an extended class must use another type (in that case, MyString instead of string). Objective-C allows the extension of a known class within the same type by using Categories. This allows all source files that view the extension .h file (typically something like NSString+MyString.h) to instantly become able to call the new member functions without the need to change NSString type to MyString.
Blocks and Lambdas
-(void)addButtonWithTitle:(NSString*)title block:(void(^)(AlertView*, NSInteger))block;
[object addButtonWithTitle:@"hello" block:[^(AlertView* a, NSInteger i){}];
A block is the Objective-C way to simulate a lambda function. Check Apple's documentation and the example of AlertView (UIAlertView with blocks) for more.
Important tip for C++ developers using Objective-C and ARC
class A
{
public:
NSObject* s;
A();
};
A :: A()
{
s = 0;
}
You already know the misery of having all customers giving a 1/5 feedback because your bugware crashed in release mode but not while debugging. No user understands programmers, do they?
Let's see what happens here. The s = 0 line assigns 0 to the variable, and therefore, whatever was in that variable before must be released first, so the compiler executes a [s release] before assignment. If s was already 0, as in debug builds, nothing happens; It's perfectly valid to use [s release] if s is nil. However, in release builds, s might be a dangling pointer, so it may contain any value before its "initialization" to 0.
In C++, this is not a problem, because no ARC is there. In Objective-C however the compiler does not have a way to know if this is an "assignment" or "initialization" (if the latter case, it will not send the release message.
This is the correct approach:
class A
{
public:
NSObject* s;
A();
};
A :: A() :s(0)
{
}
Now the compiler won't try a [s release] because it knows it's the first initialization of the object. Be careful!
Casting from Objective-C objects to C++ types
NSObject* a = ...;
void* b = (__bridge void*)a;
void* c = (__bridge_retained void*)a;
NSObject* d = (__bridge_transfer NSObject*)c;
I could analyse all this, but my advice is simple. Do not mix ARC types with non-ARC types. If you must cast some Objective-C object, use id instead of void*. Otherwise, you will run into severe memory troubles.
What Objective-C has and C++ hasn't
- Categories
- NSObject-based operations
- YES and NO (equal to true and false)
- NIL and nil (equal to 0)
- Named function arguments
- self (equal to this) but it can be changed in a constructor
What C++ has and Objective-C hasn't
- Static objects. Objects in Objective-C cannot be instantiated statically or in the stack. Only pointers.
- Multiple inheritance
- Namespaces
- Templates
- Operator overloading
- STL and algorithms ;
- Methods can be protected or private (in Obj-C, only public)
- const/mutable items
- friend methods
- References
- Anonymous function signatures (without a variable name)
Further Reading
From C++ to Objective-C guide, here.
History
- 10/05/2014 : First release.