從 C++ 到 Objective-C 的快速指南

oschina發表於2014-05-15

  簡介

  當我開始為iOS寫程式碼的時候,我意識到,作為一個C++開發者,我必須花費更多的時間來弄清楚Objective-C中怪異的東西。這就是一個幫助C++專家的快速指南,能夠使他們快速的掌握Apple的iOS語言。

  請注意這絕不是一個完整的指南,但是它讓你避免了閱讀100頁的手冊。除此之外,我知道你喜歡我的寫作風格。

  背景

  需要C++的技能,我會比較C++和Objective-C的東西。此外,COM程式設計也是有用的,因為Objective-C有類似於IUnkown的東西,因此基礎的COM程式設計是有幫助的(但不是必須的)

  Objective C++是C++和Objective C的組合。你也能使用任何C++的東西混合到Objective C中,但是記住重新命名你的檔案從.m到.mm

  鐺 - 鐺!

  我們馬上就開始我們的教程. 首先我會介紹 Objective-C 的東西,然後是C++中與它對等的東西.

  成員函式

// Objective-C
- (int) foo : (int) a : (char) b {}
+ (int) foo : (int) a : (char) b {}

// C++
int foo(int a,char b) {}  
static int foo(int a,char b) {}

// Objective-C
- (void) foo2  val1:(int) a; // named argument
// call
[obj foo2 val1:5];  // merely helper: You remember that 5 is assigned to param name val1.

  - 表示的是一個一般的成員函式(通過一個物件實體訪問), 而 + 則表示一個靜態成員函式, 不需要使用實體就能訪問. 當然,像C++, 靜態成員不能訪問實體變數.

  此外,Objective-C函式函式可以有賦予了名稱的引數,這樣讓什麼引數獲得什麼值會更一目瞭然. 理論上,被賦予了名稱的引數允許程式設計師按任何順序傳入引數,但是Objective-C卻規定要按宣告的順序傳參.

  通過一個指標或者一個靜態成員呼叫一個成員

// Objective-C
NSObject* ptr = ...; // some pointer
[ptr foo:5:3]; // call foo member with arguments 5 and 3
[NSObject staticfoo:5:3];  // call static function of NSOBject with arguments 4 and 3

// C++
CPPObject* ptr = ...; // some pointer
ptr->foo(5,3);
CPPObject::staticfoo(5,3);

  Objective-C 使用 [ ] 來呼叫成員函式並傳入用:分割開的引數, 其對於ObjectiveC中ptr為nil的情況非常友好,在這種情況下“呼叫”將會被忽略掉(而在C++中,這種情況會丟擲一個指標衝突異常 ). 這使得消除對nil物件的檢查成為可能.

  協議VS介面

// Objective-C
@protocol foo
- (void) somefunction;
@end

@interface c1 : NSObject<foo>

@end

@implementation c1
- (void) somefunction { ... }
@end

// C++
class foo
{
virtual void somefunction() = 0;
};

class c1 : public NSObject, public foo
{
void somefunction() { ... }
}

  協議= 抽象類. Objective-C 和 C++ 之間的區別在於,在 Objective-C 中, 函式並不是必須要被實現的. 你可以讓一個可選的方法被動的被宣告,而這僅僅只是向編譯器發出暗示而已,並不是編譯必須的. 

  檢查一個方法是否被實現了

// Objective-C
NSObject* ptr = ...; // some pointer
[ptr somefunction:5:3]; // NSObject 不必一定為其編譯而實現somefunction. 如果沒有被實現的話,會引發異常.

// C++
CPPObject* ptr = ...; // some pointer
ptr->somefunction(5,3); // CPPObject 必須實現 somefunction() 否則程式根本就不會被編譯.

  Objective-C 成員函式就是(Smalltalk中的) "訊息"  而在Objective-C中時,我們則說接收者 (即指標) 會向一個選擇器做出迴應, 這意味著其實現了我們嘗試去呼叫的虛擬函式. 當有一個介面是, C++ 物件必須實現其所有的成員函式. 而在 Objective-C 中這並不是必須的,因此我們可以向並不必須要實現它的某個地方傳送一個”訊息“  (如此就會引發一個異常).

// Objective-C
NSObject* ptr = ...; // some pointer
if ([ptr respondsToSelector:@selector(somefunction::)] 
    [ptr somefunction:5:3];

  現在我們就可以確定接收者向選擇器做出迴應, 我們因此就可以呼叫它了. 在 C++ 中不需要這樣的檢查, 因為實現必須常常”向選擇器做出迴應“, 否則原始碼根本就不會被編譯. 請注意我們必須知道選擇器獲取了多少個引數(因此在該@selector中是2個 ::s 

  向下轉型

// Objective-C
NSObject* ptr = ...; // some pointer
if ([ptr isKindOfClass:[foo class]] 
    [ptr somefunction:5:3];

// C++
CPPObject* ptr = ...; // some pointer
foo* f = dynamic_cast<foo*>(ptr);
if (f)
  f->somefunction(5,3);

  現在只有使用NSObject的"isKindOfClass"助手——所有Object-C類的基礎,才能像在C++中那樣向下轉型.

  符合協議?

// Objective-C
NSObject* ptr = ...; // some pointer
if ([ptr conformsToProtocol:@protocol(foo)] 
    [ptr somefunction:5:3];

// C++
CPPObject* ptr = ...; // 某個也繼承自foo的指標
foo* f = ptr; // 或者編譯器會警告我們說ptr不能同foo相容.
f->somefunction(5,3);

  現在我們要檢查接收器是否 符合一個協議 (或者說,在C++就是實現一個介面), 以便我們可以傳送這個協議包含的訊息. 嘿嘿,它像極了Java的類和介面,而在C++中,完全被實現的類和一個“介面”之間沒有技術上的差別. 

  void* 、 id 或者 SEL?

// Objective-C
id ptr = ...; // some pointer
if ([ptr conformsToProtocol:@protocol(foo)] 
    [ptr somefunction:5:3];
SEL s = @selector(foo:); // a pointer to a function foo that takes 1 parameter

// C++
void* ptr = ...; // some pointer
foo* f = dynamic_cast<foo*>(ptr); 
if (f)
  f->somefunction(5,3);

  id 是通用的用於Objective-C類的類似於 void* 的東西. 你只能使用id而不是 void* 因為id可以通過ARC(稍後會詳細介紹到它)程式設計一個可被管理的指標,而因此編譯器需要在元指標型別和Objective-C指標之間做出區分. SEL 是一個用於選擇器(C++函式指標)的通用型別,而你一般可以通過使用關鍵字@selector帶上函式名字和:::::s的一個數字來建立一個選擇器, 這取決於可以傳入多少引數. 選擇器實際上就是一個字串,它會在執行時繫結到一個方法識別器.

  類定義,方法,資料,繼承

// Objective C
@class f2; // forward declaration
@interface f1 : NSOBject // Objective-C supports only public and single inheritance
{
 int test; // default = protected
@public
 int a;
 int b;
 f2* f;
}
- (void) foo;
@end

@implementation f1
- (void) foo
{
 a = 5; // ok
 self->a = 5; // ok
super.foo(); // parent call
}
@end

// C++
class f1 : public CPPObject
{
 int test; // default = private
public:
 class f2* f; // forward declaration
 int a;
 int b;
 void foo();
}

void f1 :: foo()
{
 a = 5; // ok
 this->a = 5; // ok
 CPPOBject::foo(); // parent call
}

  Objective-C中的實現範圍在@implementation/@end 標記 (在 C++ 中我們可以將實現放在任何帶有::範圍操作符的地方)之中. 它使用@class關鍵字用於事先宣告. Objective-C 預設帶有 私有(private)保護, 但僅用於資料成員(方法必須是公共的). Objective-C 使用 self 而不是 this ,並且它還可以通過super關鍵字呼叫它的父類.

  構造器和析構器

// Objective-C
NSObject* s = [NSObject alloc] init]; // can return nil if construction failed
[s retain]; // Increment the ref count

// C++
CPPObject* ptr = new CPPObject();   // can throw
ptr->AddRef();

// Objective-C
NSObject* s = [NSObject alloc] initwitharg:4];
[s release];

// C++
CPPOBject* ptr = new CPPOBject(4);
ptr->Release();

  Objective-C中的記憶體分配是通過靜態成員方法alloc來實現的,所有 (做為NSObject後裔)的物件都有這個方法. self 在Objective-C中是可以被賦值的,而如果構建失敗的話它就會設定成nil(而在C++中則會被丟擲一個異常). 記憶體分配之後實際被構造器呼叫的是一個公共的成員函式,在Objective-C中預設的是init方法. 

  Objective-C 使用同COM益陽的引用計數方法, 而它也使用 retain 和 release (等同於IUnknown的 AddRef() 和 Release() 方法). 當引用計數到了0,則物件會從記憶體中被移除掉.

  多執行緒

// Objective C
@interface f1 : NSOBject // Objective-C supports only public and single inheritance
{
}
- (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></div>} 
@end

  Objective-C 有一些針對NSObject的內建功能,可以在另外一個執行緒中操作一個選擇器 (== 呼叫一個成員), 在主執行緒中,等待一次呼叫等等 . 在NSObject 參見更多資訊. 

  記憶體和ARC

// Objective-C
@interface f1 : NSObject
{
}
@property (weak) NSAnotherObject* f2; // 當沒有其它強引用存在的時候,它將會被自動設定成.
@end

-(void)foo
{
NSObject*s = [NSObject alloc]&nbsp;init]; // 如果構造失敗的話會返回nil
// use s
// end. Hooraah! Compiler will automatically call[s release] for us!
}

  這裡需要你忘記自己良好的 C++ 習慣. OK Objective-C 使用了一個垃圾收集機制,這種東西我們C++很討厭,因為它很慢並會讓我們想到Java. 但是 ARC (自動進行引用計算) 是一種 編譯時間 特性,它會告訴編譯器 "這裡是我的物件:請幫我算算啥時候它們才要被銷燬吧". 使用ARC你就不需要傳送 retain/release 訊息給你的物件了; 編譯器會自動幫你代勞的.

  為了能幫助編譯器決定保留一個物件多長時間,你還要一個弱引用指向一個變數. 預設的,所有的變數都是強引用(==只要強引用還存在,被引用的物件就會存在下去) . 你也可以獲取一個弱引用,它會隨著每個強引用消失而失去它的值. 這在類成員從XCode的Builder Interface(像RC 編輯器)處獲取它們的值時,會很有用,當類被銷燬時,那些成員也會失去它們的值.

  Strings

// Objective-C
NSString* s1 = @"hello";
NSString* s2 = [NSString stringWithUTF8String:"A C String"];
sprintf(buff,"%s hello to %@","there",s2);
const char* s3 = [s2 UTF8String];

  NSString 是一個Objective-C字串的不可變表示. 你可以使用其一個靜態方法,或者是一個帶有@字首的字串文字來建立NSString. 你也可以使用 %@  來向printf家族函式來表示一個NSString,

  陣列

// Objective-C
NSArray* a1 = [NSArray alloc] initWithObjects: @"hello",@"there",nil];
NSString* first = [a1 objectAtIndex:0];

  NSArray和NSMutableArray是在Objective-C中處理陣列的 兩個類(兩者的差異是,NSArray元素構造時必須通過建構函式,而NSMutableArray可以在之後更改)。典型建構函式的生效,你必須通過nil去作為“結尾元素”。排序/搜尋/插入函式對於NSArray和NSMutableArray來說是一樣的,在第一行中的例子它返回一個新的NSArray,而在NSMutableArray的例子裡,它修改的是一個存在的物件。

  分類

// C++
class MyString : public string
{
public:
  void printmystring() { printf("%s",c_str()); }
};


// Objective-C
@interface MyString (NSString)
- (void) printmystring;
@end

@implementation MyString (NSString)
- (void) printmystring
{
 printf("%@",self);
}
@end

// C++
MyString s1 = "hi";
s1.printmystring(); // ok
string s2 = "hello";
s2.printmystring(); // error, we must change s2 from string to MyString

// Objective-C
NSString* s2 = @"hello";
[s2 printmystring]; // valid. We extended NSString without changing types.

  C++依賴 繼承機制來實現一個已知的類。這是很麻煩的,因為所有使用者的實現類必須使用另外的型別名稱(在例子中,MyString用來代替string)。Object-C通過使用 分類(Categories)允許擴充套件一個已知的類內 同型(same type )。上面連結中所有原始碼在extension.h檔案 (具有代表性的是像NSString+MyString.h這樣的)中可以檢視,上面例子中,我們立即就有可以呼叫新的成員函式,而不需要改變NSString型別為MyString。

  塊 和 Lambda

// Objective-C
// member function
-(void)addButtonWithTitle:(NSString*)title block:(void(^)(AlertView*, NSInteger))block;

// call
[object addButtonWithTitle:@"hello" block:[^(AlertView* a, NSInteger i){/*DO SOMETHING*/}];

  塊 是Objective-C 用來模擬lambda功能的一種方式. 檢視Apple的文件,從AlertView的示例 (使用塊的UIAlertView)可以獲得更多有關塊的技術.

  C++ 開發者使用 Objective-C 和 ARC 的重要提示

// C++
class A
{
public:

   NSObject* s;
   A();
};     

A :: A()
 {
 s = 0; // 可能會奔潰,這是常發生在釋出模式下!
 }

  你已經知道給所有的顧客都打兩折對你而言有多痛苦了,因為你bug重重的軟體會在釋出模式下奔潰,而在除錯模式下總是妥妥的. 沒有使用者會理解程式設計師,是不是?

  讓我們來看看這裡發生了什麼.  s = 0 這一行將 0 賦值給了一個變數,而因此不管這個變數之前取值是什麼,首先都會被釋放掉,所以編譯器在賦值之前執行了一次 [s release] . 如果 s 之前已經是 0 了,假設是在除錯構建的話,不會發生任何不好的事情; 如果 s 是一個nil的話,使用[s release] 是再好也不過的. 然而,在釋出模式下, s可能是一個野指標,所以它可能在被“初始化”為0之前包含任何值(這很危險是不是?).

  在C++中,這並不是一個問題,因為它裡面是不存在ARC的. 而在Objective-C中編譯器並沒有辦法瞭解這是一次"賦值" 還是一次 "初始化" (如果是後者,它就不會傳送釋出訊息).

  下面是正確的方式:

// C++
class A
{
public:

   NSObject* s;
   A();
};     

A :: A() :s(0) // 現在編譯器就知道確定 it&apos;s 是一次初始化了, 一次就不存在 [s release]
 {
 }

  現在編譯器就不會去嘗試呼叫一個 [s release] 因為它知道它是這個物件的第一次初始化. 請小心!

  從Objective-C 物件到 C++ 型別的轉換

// Objective-C
NSObject* a = ...;
void* b = (__bridge void*)a; // 你必須在Objective-C和C型別支架使用 __bridge 
void* c = (__bridge_retained void*)a; // 現在是一個+1的保留計數,而你必須在稍後釋放這個物件
NSObject* d = (__bridge_transfer NSObject*)c; // 現在ARC取得了物件c的”擁有權“, 將其裝換成一個ARC管理的NSObject.

  我可以分析這一切,而我的建議是簡單的. 不要 將ARC型別和非ARC型別混在一起. 如果你必須轉換一些Objective-C物件的話,使用id而不是void*. 否則,你將會遇到嚴重的記憶體故障.

  Objective-C 有而 C++ 沒有的

  • 分類Categories

  • 基於NSObject的操作

  • YES 和 NO (等價於true和false)

  • NIL 和 nil (等價於0)

  • 可命名的函式引數

  • self (等價於 this) 而其可以在一個構造器中被改變

 C++ 有而 Objective-C 沒有的

  • 靜態物件. Objective-C 中的物件不能被初始化成靜態的,或者是存在於棧中. 只能是指標.

  • 多重繼承

  • 名稱空間

  • 模板

  • 操作符過載

  • STL 和演算法 ;

  • 方法可以是受保護的( protected )或者私有的( private ) (在Obj-C中,只能是公共的)

  • const/mutable 項

  • friend 方法

  • 引用

  • 匿名函式簽名 (沒有變數名稱)

 閱讀更多

  從C++ 到 Objective-C 指南, 這裡.

 歷史記錄

  • 10/05/2014 : 第一次釋出.

  原文地址:http://www.codeproject.com/Articles/770577/From-Cplusplus-to-Objective-C-A-quick-guide-for-pr

相關文章