Objective-C關於id引起的一些思考

Objective-C關於id引起的一些思考

    Objective-C是面嚮對象語言,但其中又並不是所有是對象。在初學這門語言時,我經常從意識上將NS開頭的類型與C語言本來的那些類型分割開來,僞裝他們之間沒有聯繫,只關注「類」的世界。然而類終究只是一種應用上的抽象,就像「語法糖」同樣,拋開華麗的外表,內部依然是最樸素的結構體和指針。本篇博客的來由源自朋友的一個問題:在ARC環境,performSelector:withObject:方法如何傳遞非對象類型的數據呢?這個問題乍看起來簡單,但要較較真,卻也並不是那麼簡單。下面的內容都是有這個簡單的問題引出的,若是你感興趣,在讀以前能夠先試着解決下上面的疑問。編程

1、還要先說id

    id是Objective-C中定義的一種泛型實現,它能夠表示任何對象類型。儘管id看起來是如此簡單,但細細琢磨,其卻包含了3層意義:數組

1.做爲參數或返回值框架

    將id類型做爲函數的參數或返回值是最淺的一層意義,其增長了函數的靈活性,Foundation框架中也有其大量的應用,例如可變數組追加元素。編程語言

2.id類型的參數不會進行類型檢查函數

    這是id類型十分重要的一個特色,聲明爲id類型的對象就至關於告訴了編譯器不進行類型檢查(這也是和NSObject類型最大區別)。所以,你能夠將id類型的變量賦值給任何對象類型,也能夠將任何對象類型的變量賦值給id類型,更重要的是,使用id類型的對象能夠調用任意方法,都不會進行類型檢查。ui

3.id<protocol>是一種優雅的編程方式spa

    因爲id類型不會進行編譯檢查,要約束類型方法實現最好的方式就是經過協議,id<protocol>是一種十分優雅的編程方式,其再也不關心類型,只注重約定的實現,Foundation框架中的代理多采用這種方式設計。設計

    另外,在objc.h文件中,id被定位爲以下:代理

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
//結構體指針
typedef struct objc_object *id;

2、關於void與void*

    在開發中,void用的最多的地方要數標記Objective-C無返回值的函數,Objective-C函數和C函數不一樣,其必須有一個肯定的返回值類型,若是沒有返回值,則須要使用void來標記返回值類型,而C函數是能夠不指定返回值類型的,默認的C函數則是返回int類型的值,例以下面的兩個函數其實是徹底同樣的:指針

print(){
    printf("cccccc");
    return 0;
}

int print(){
    printf("cccccc");
    return 0;
}

void在C語言中還有一大用途在於約束無參函數,例如上面示例的函數雖然沒有參數,可是若是你在調用的時候強制傳入參數編譯器也不出進行錯誤提醒,若是將函數修改以下,則此函數就徹底不能傳入參數了:

int print(void){
    printf("cccccc");
    return 0;
}

    歸根結底,void大多時候用來表示「空」,而void * 則徹底不一樣,它所描述的其實是任意類型的指針。這裏和id很像對不對,雖然id描述的是Objective-C對象可是本質也是指針,那麼根據咱們的推測,id類型的數據和void*類型的數據是能夠進行類型轉換的。事實上,在MRC環境下確實如此,ARC環境下則要更復雜一些,因爲ARC機制要對Objective-C對象進行引用計數管理,對C指針並不會,所以在ARC環境下編譯器是不容許咱們直接將id於void*進行進行轉換的,例以下面的報錯:

3、ARC中用__bridge的應用

    前面說過,因爲ARC的緣由,致使沒法在Objective-C對象與C指針類型之間進行直接轉換,可是能夠經過__bridge來轉換,從字面理解,__bridge的做用就是用來橋接。在作Objective-C相關開發時,你必定遇到過CoreFoundation框架與Foundation框架混用的狀況,CF框架中的類都是由C語言直接實現的,例如CFString,CFURL等,其雖然能夠和NSString,NSURL互用,但在ARC環境在,卻必須進行橋接轉換,即便用__bridge。上面的代碼能夠作以下修改:

int a = 10;
    void * ap = &a;
    id c = (__bridge id)ap;

一樣,將id類型轉換爲void *以下:

NSNumber * num = [[NSNumber alloc]initWithInt:1];
    void * cNumber = (__bridge void *)num;

與__bridge相對應的還有__bridge_transfer與__bridge_retained,他們的區別是__bridge不會改變對象的全部權,__bridge_transfer會將對象全部權進行轉移,即release掉轉換前的Objective-C對象,而__bridge_retained則是將全部權進行retain強引用。

4、解決最初的問題

    再來看咱們最初的問題,下面方法的兩個參數都是id類型的:

- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

雖然咱們也能夠用其餘方式來達到相同的效果,好比修改原函數的參數類型,或者使用NSInvocation來發送消息,一種更簡便的方式以下:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    int a = 10;
    [self performSelector:@selector(log:age:) withObject:@"huishao" withObject:(__bridge id)(void*)a];
}

-(void)log:(NSString *)name age:(int)age{
    NSLog(@"%@,%d",name,age);
}

最後,總結一點,其實不論任何編程語言,類型檢查都是編譯時的特性,真正傳遞的數據依然是在運行時決定的。

相關文章
相關標籤/搜索