Objective-C 2.0編程語言(一) 類與對象

Objective-C

Objective-C是對C的擴充,它最初的設計目的是給C語言一個通過簡單且直觀的方式而實現(xiàn)面向?qū)ο缶幊痰哪芰κを取1鞠盗杏糜谧晕也橐伞?/p>

面向?qū)ο蟮某绦蚨际菄@對象而構建,一個對象把數(shù)據(jù) 和一些對這些數(shù)據(jù)的操作(即Methods)捆綁在一起葱色,打包成一個獨立的編程單元豁护。

例如你正在寫一個畫圖程序,這個程序允許用戶創(chuàng)建諸如線亿扁、弧線、矩形鸟廓、文本、位圖等襟己,那么你就可以創(chuàng)建關于這些基本圖形的很多類引谜。例如一個矩形對象,應該有標識它的位置的實例變量和寬與高的信息擎浴,可能還會有別的實例變量來定義它的顏色员咽、矩形是否被填充、直線樣式等贮预。這個Rectangle類會有一些方法用來設置一個實例的位置贝室、大小、顏色仿吞、填充狀態(tài)滑频、直線樣式,還應該提供一個方法用來繪制自己唤冈。
在Objective-C中峡迷,Object的實例變量屬于Object的內(nèi)部數(shù)據(jù),通常要訪問這些數(shù)據(jù)只能通過對象的方法,你還可以通過作用域指示符(scope directives)為一個類的子類或別的對象指定訪問實例變量的權限绘搞。要獲取對象別的信息彤避,必須提供一個相應的方法,例如夯辖,Rectangle類應該有專用的方法來獲取它的大小和位置琉预。而且,對象只能看到為它自己而設計的方法蒿褂,它不會把別的對象的方法運用到自己身上模孩。就像一個函數(shù)隱藏局部變量一樣,對象不但隱藏實例變量贮缅,還會隱藏方法的實現(xiàn)榨咐。

一、OC中的類

編譯器為每個類定義一個類對象(Class_object)谴供。Class_objectClass的編譯版本块茁,而它所構建的對象被稱為類的實例。在你的程序中真正做工作的是類在運行時對象創(chuàng)建的那些實例桂肌。一個類的所有實例有同一套方法数焊,而且有相同一套實例變量。每個對象都有自己的實例變量崎场,但他們卻共享這套方法佩耳。根據(jù)約定,類名以大寫字母開頭谭跨,如Rectangle干厚,而實例變量通常以小寫字母開頭,如myRect螃宙。

  • 繼承(Inheritance)
    類的定義通常是添加式的蛮瞄,一個新的類往往都基于另外一個類,而這個新類繼承了原來類的方法和實例變量谆扎。新類通常簡單地添加實例變量或者修改它所繼承的方法挂捅,它不需要復制繼承的代碼。繼承將這些類連接成一個只有一個根繼承關系樹堂湖。在OC中闲先,寫基于功能框架的代碼時,這個根類通常是NSObject无蜂。每個類(除了根類)都有一個父類伺糠,而每個類,包括根類都可以成為任何數(shù)量子類的父類酱讶。
  • 抽象類
    有些類被設計為不能實例化退盯,他們只能作為super類被其他類所繼承。這些類往往自身是不完整的,而只是包含了一些有用的代碼渊迁,這些代碼能夠被子類所繼承慰照,從而減少子類的代碼量。NSObject類就是一個重要的抽象類琉朽。程序中經(jīng)常會定義NSObject的子類并使用這些子類的實例毒租,但從來沒有直接使用這個類的實例的器紧。抽象類通常包含一些幫助定義應用程序架構的代碼滔蝉,當你定義這些類的子類時蛹找,這些子類的實例能很好地適應這種應用程序架構绩郎,并能夠自動地和別的對象協(xié)作。由于抽象類只有定義了子類才能成為一個有用的類酌畜,因此它們常常被稱為抽象超類羊异。
  • 類類型
    類實際上是一類對象的詳細說明怎顾,定義了一個數(shù)據(jù)類型螟够。這個類型不但基于它所定義的數(shù)據(jù)結構灾梦,還取決于它所定義的一些行為,這些行為就是方法妓笙。因此若河,類名可以出現(xiàn)在任何C語言允許的類型說明符出現(xiàn)的地方,例如作為sizeof操作符的參數(shù):Int i = sizeof(Rectangle);
  • 靜態(tài)類型匹配
    Rectangle *myRect;
    這種聲明對象的方式為編譯器提供了對象種類的信息寞宫,所以被稱為靜態(tài)類型匹配萧福,靜態(tài)類型匹配使編譯器具備了一些類型檢查功能,例如如果一個對象接收一個沒有定義的消息時辈赋,可以發(fā)出警告鲫忍,而如果把對象定義為id,就會放松這種限制炭庙。盡管這樣饲窿,它還是無法替代動態(tài)綁定的位置。一個對象可以被靜態(tài)地匹配為它自己的類或者它所繼承的類焕蹄。例如因為繼承使得Rectangle成為一種Graphic,所以Rectangle實例可以靜態(tài)匹配為Graphic類:
    Graphic* myRect;
    這樣做之所以合法阀溶,是因為Rectangle本身就是一種Graphic腻脏,但它由于繼承了Shape和Rectangle的特性,所以又不止是一個Graphic银锻。為了類型檢查永品,編譯器會把myRect當作Graphic,但在運行時環(huán)境中击纬,它會被當作Rectangle對待鼎姐。
  • 動態(tài)類型匹配
    id myRect;
    對象通常都是被定義成指針,上面的靜態(tài)匹配使得指針的含義更加明確炕桨,而id卻隱藏了這些信息饭尝。
    id類型是一種靈活的數(shù)據(jù)類型, 只表示它是一個對象,不能為編譯器提供例如實例變量献宫,可執(zhí)行操作等信息钥平。所以每個對象在運行時必須提供這些信息。 而之所以能做到這點姊途, 是因為每個對象都有一個isa 實例變量來標示這個對象所屬的類每個Rectangle對象都能告訴運行時系統(tǒng)它是一個矩形類涉瘾,因此,動態(tài)類型匹配實際上發(fā)生在程序被執(zhí)行時捷兰。
    不論何時立叛,只要需要,運行時系統(tǒng)就能夠查明一個對象到底屬于哪個類贡茅,而這只需要查詢對象的isa實例變量就可以了秘蛇。這個isa指針還為對象提供了一種稱為“自省”(introspection)的功能。編譯器會在數(shù)據(jù)機構中記錄關于類定義的信息為運行時環(huán)境所用友扰。通過這種機制彤叉,你可以判斷一個對象是否提供了一個特定的方法,也可以獲取這個對象的超類(supperclass)的名字村怪。當然也可以通過靜態(tài)類型匹配為編譯器提供有關對象所屬類的信息秽浇,這也是為什么類名可以作為類型名而使用了。
  • 類型自省
    實例在運行時可以獲取自己的類甚负。例如柬焕,NSObject類中定義的isMemberOfClass(或isKindOfClass)方法可以檢查接收者是不是特定類的實例(或繼承特定類的實例)。

  • 消息語法

ObjC中使用[Receiver message]方式來進行方法調(diào)用,本質(zhì)其實就是向Receiver發(fā)送message消息.而message告訴這個Receiver要做什么梭域。

  • 給nil發(fā)送消息
    在ObjC中斑举,給nil發(fā)送消息是合法的,但在運行時什么都不做病涨,而發(fā)送給nil的消息帶有返回值也是合法的富玷。
    ■如果一個方法返回一個對象、任何類型的指針既穆、任何size小于或等于sizeof(void*)的類型赎懦,如float、 double幻工、 long double励两、或者long long,那么給nil發(fā)送消息將返回0囊颅。
    ■如果一個方法返回一個數(shù)據(jù)結構当悔,那么將這個消息傳遞給nil將為這個數(shù)據(jù)結構的每個成員都返回0.0傅瞻。
    ■如果一個方法返回上述類型外的其它類型,那么將這個消息傳遞給nil盲憎,返回值為定義嗅骄。
  • 多態(tài)動態(tài)綁定
    函數(shù)調(diào)用和消息的一個關鍵區(qū)別是,函數(shù)和它的參數(shù)是在編譯時綁定在一起的焙畔,而消息和接收者直到程序運行時掸读,消息被發(fā)送才實現(xiàn)這種綁定。因此宏多,響應一個消息的具體方法是在運行時才決定的儿惫,而不是在代碼被編譯的時候。Message啟動哪個Method取決于Message的Receiver伸但,不同的Receiver可能有同名的不同Method實體肾请,這就是多態(tài)。
    而編譯器要為一個Message找到正確的Method實體更胖,它必須知道這個Receiver屬于什么對象铛铁,而這是一個對象只有在運行時接收到Message后才能夠真正獲取的信息. 因此Method實體的選擇是發(fā)生在運行時的。

當Message發(fā)出之后却妨,運行時環(huán)境會查看Receiver以及它與消息同名的Method饵逐,它通過名字匹配找到這個Receiver的Method實體,并調(diào)用彪标,同時傳遞一個指向Receiver實例變量的指針倍权。Message中的方法名是用來選擇 Receiver的Method實體,因此,Message中的方法名也被稱為(selector)選擇器.
Method和Message的動態(tài)綁定,以及多態(tài)之間的協(xié)調(diào)合作捞烟,給了面向?qū)ο缶幊特S富的靈活性和強大的功能薄声,因為每個對象都可以有一個方法的自己的版本,但僅僅是接收相同消息的對象不同题画,而這些都可以在程序運行時完成,即不同的對象以各自的方式響應相同的消息.
可以簡化編程接口,容許在類與類之間重用一些習慣性的命名.

這樣你可以編寫應用于任何不同種類對象的代碼默辨,而不用去考慮到底是應用到什么樣的對象上.
這些對象甚至可以是尚未開發(fā)的,或者是由別的項目的程序員開發(fā)的.(ps.如果你寫了一個向id類型變量發(fā)送display消息的代碼苍息,任何擁有display方法的對象都可能成為這個消息的接收者缩幸。)

  • 方法和選取器:
    選取器確定的是方法名,不是方法的實現(xiàn), 這是多態(tài)和動態(tài)綁定的基礎.它使得向不同對象發(fā)送相同的消息成為現(xiàn)實.
  • 方法返回值和參數(shù)類型:
    消息機制是通過選取器找到方法的返回值類型和參數(shù)類型. 因此: 動態(tài)綁定需要同名方法的實現(xiàn) 擁有相同返回值類型和相同的參數(shù)類型;否則,運行時可能出現(xiàn)找不到對應方法的錯誤.(有一個例外,雖然同名靜態(tài)方法和實例方法擁有相同的選取器竞思,但是它們可以有不同的參數(shù)類型和返回值類型桌粉。)
  // SEL和@selector區(qū)別:選擇器的類型是SEL.而 @selector指示符是用來引用選擇器的, 它返回類型是SEL.
    SEL response;      
    response = @selector(load:)

    // 1. 通過字符串來得到選取器:
    responseSEL = NSSelectorFromString(@"loadDataForTableView:");
    //  2 . 通過選擇器轉(zhuǎn)換來得到方法名: 
    NSString  *methodName = NSStringFromSelector(responseSEL);

1.1 類對象

一個類的定義會包含豐富的信息(詳見附錄),但大部分都是關于這個類的實例的衙四,如:
■類名及超類
■關于實例變量的完整描述
■關于方法名及其參數(shù)的說明
■方法的實現(xiàn)
所有這些信息都被編譯被保存在一個運行時系統(tǒng)能夠訪問的數(shù)據(jù)結構中。編譯器創(chuàng)建一個而且只創(chuàng)建一個對象來表達這些信息患亿,那就是類對象,因此传蹈,在OC中押逼,所有的類自身也是一個對象。
雖然類對象保存了類的屬性惦界,但它本身并不是一個類的實例挑格。它沒有自己的實例變量,而且它也不能執(zhí)行為類實例設計的方法沾歪。不過漂彤,類定義了可以包含只為類對象使用的方法,這就是類方法(靜態(tài)方法)灾搏,這些方法不同于實例方法(動態(tài)方法)挫望。另外,類對象會繼承所有超對象的類方法狂窑,就像類實例可以繼承所有超類實例的方法一樣媳板。
在實際代碼中,類名就代表類對象泉哈,下面的例子中蛉幸,Rectangle類使用繼承自NSObject的方法返回類的版本號:
Int versionNumber = [Rectangle version];
但只有在作為接收者接收一個消息時,類名才能代表類對象丛晦,在別的地方奕纫,你必須通過給類實例或給類發(fā)送class消息來獲得一個類id,如:
Id aClass = [anObject class];
Id rectClass = [Rectangle class];
如上例所述烫沙,和所有其它的對象一樣匹层,類對象可以被轉(zhuǎn)化為id類型,而且類對象還可以更精確地轉(zhuǎn)化為類類型斧吐,如:
Class aClass = [anObject class];
Class rectClass = [Rectangle class];
所有的類都屬于類對象又固,使用Class類型和使用類名進行靜態(tài)類型匹配是等效的。因此煤率,類對象也像類實例那樣仰冠,可以進行動態(tài)類型匹配、接收消息以及從別的類繼承方法蝶糯。不同之處在于它們是由編譯器產(chǎn)生的洋只,沒有自己的數(shù)據(jù)結構,它們是用于運行時系統(tǒng)產(chǎn)生類實例的代理昼捍。

  • 創(chuàng)建實例
    類對象的主要用途是用于創(chuàng)建新的實例识虚,下面這個例子告訴Rectangle類創(chuàng)建一個Rectangle實例,并將其賦值給myRect變量:
    id myRect;
    myRect = [Rectangle alloc];
    alloc方法會為這個實例的每個實例變量動態(tài)分配內(nèi)存妒茬,并將除連接該實例和它的類的 isa變量外的所有實例變量全部清零,一個對象要能真正派上用場担锤,它還必須完整地初始化,這就是init方法的作用乍钻,通常init方法都緊挨著alloc方法肛循,如:
    myRect = [[Rectangle alloc] init];
    在一個實例能夠接收消息前铭腕,對實例進行這樣的初始化是必須的。Alloc方法返回一個新的實例多糠,然后init方法對這個事例進行初始化累舷。每個類對象至少擁有一個方法(如alloc)才能讓它具備創(chuàng)建新對象的能力。而每個實例至少一個方法(如init)讓它能夠有用夹孔。
    而初始化方法通常帶有一些參數(shù)用來傳遞特定的值被盈,而且用標簽來標示這些參數(shù),例如initWithPositon: size:搭伤,但不管怎樣只怎,它都是以init開頭。
  • 變量和類對象
    定義一個類闷畸,你可以為它定義實例變量尝盼,這個類的每個實例都保留一份這些實例變量的拷貝——每個對象管理自己的數(shù)據(jù)。
    但是你發(fā)現(xiàn)沒有佑菩,OC中并沒有類似實例變量那樣的所謂的“類變量”盾沫。而且類對象無法訪問任何實例的實例變量,它不能初始化殿漠、讀寫這些變量赴精。為了讓類的所有實例共享數(shù)據(jù),你必須定于外部變量(不是在類中定義的變量)绞幌。最簡單的辦法是像下面的代碼片斷那樣在類的實現(xiàn)文件中聲明一個變量:
int MCLSGlobalVariable;
@implementation MyClass
...
@end

更巧妙的做法是蕾哟,你可以把變量聲明為靜態(tài)的,并提供類方法來管理它莲蜘。靜態(tài)變量會把它的作用域限定在類范圍谭确,而且僅限于當前文件的類的實現(xiàn)部分。并不像實例變量那樣票渠,靜態(tài)變量不能被繼承逐哈,不能被子類直接操作。這種特性常常用于定于類的共享實例(如單一模式问顷,參考“創(chuàng)建單例”部分)昂秃。靜態(tài)變量能夠幫助類對象提供一種比“工廠模式”創(chuàng)建實例更多的功能,而且更接近一個完整和通用的對象杜窄。

static MyClass *MCLSSharedInstance;
@implementation MyClass
+ (MyClass *)sharedInstance
{
// check for existence of shared instance
// create if necessary
return MCLSSharedInstance;
}
// implementation continues
  • 初始化類對象
    如果類對象不分配實例肠骆,它就可以向類的實例那樣初始化,盡管程序并不為類對象分配內(nèi)存塞耕,但Objective-C確實也提供了一個初始化類對象的途徑蚀腿,如果類使用靜態(tài)或全局變量,那么initialize是一個初始化這些變量的好地方扫外。
    在類接受任何其它消息前唯咬,運行時環(huán)境會給類對象發(fā)送一個initialize消息纱注,當然這發(fā)生在它的超類收到initialize消息之后。這就給這個類一個在被使用前建立運行時環(huán)境的機會胆胰。
    如果沒有初始化工作要做,你就沒有必要實現(xiàn)一個initialize方法來響應這個消息刻获。因為繼承關系的存在蜀涨,給一個沒有實現(xiàn)initialize的類發(fā)送initialize消息,這個消息將會被傳遞到它的超類蝎毡,即使超類已經(jīng)受到這個消息也是如此厚柳。
    例如,A類實現(xiàn)了initialize方法沐兵,而類B繼承自類A别垮,但類B沒有實現(xiàn)initialize方法,在類B接收它的第一個消息之前扎谎,運行時環(huán)境會為它發(fā)送一個initialize消息碳想。但由于類B沒有實現(xiàn)initialize方法,類A的initialize方法會被執(zhí)行毁靶,因此胧奔,類A需要確保它的初始化邏輯只被執(zhí)行一次。為了確保初始化邏輯只執(zhí)行一次预吆,請使用如下的模板實現(xiàn)initialize方法:
+ (void)initialize
{
  static BOOL initialized = NO;
  if (!initialized) {
  // Perform initialization here.
  ...
  initialized = YES;
  }
}
  • 根類方法
    所有的類和實例龙填, 都需要有一個和runtime環(huán)境交互的接口。 類對象和實例都必須對自己有能力完成自省拐叉,并能夠匯報自己在繼承關系中所處的位置岩遗,所以要實現(xiàn)兩次:一次為實例提供可以和運行時環(huán)境交互的接口,另一次在類對象中復制這些接口凤瘦。 而NSObject會提供這些接口宿礁,所以NSObject不需要實現(xiàn)兩次。類對象扮演另外一個角色廷粒,就是執(zhí)行根類中定義的實例方法窘拯。當類對象接收一個不能響應消息時,運行時環(huán)境會判斷是否有根實例方法可以響應這個消息坝茎。類對象能夠執(zhí)行的唯一的實例方法就是那些在根類中定義的方法涤姊,而且還必須是沒有類方法能完成這項工作時才會由根類方法來完成。有關類方法執(zhí)行實例方法的特殊能力嗤放,請參考有關NSObject類的說明思喊。
  • 代碼中的類名
    在代碼中,類名職能在兩種不同的上下文環(huán)境中出現(xiàn)次酌,這兩種情況也反映了類名作為數(shù)據(jù)類型和對象的雙重角色:
    Rectangle * anObject;
    這里anObject被明確地定義為指向Rectangle的一個指針恨课。編譯器會認為它具有Rectangle的數(shù)據(jù)結構和Rectangle的實例方法以及Rectangle所繼承的方法舆乔。靜態(tài)類型匹配可以讓編譯器做到更好的類型檢查并使得代碼更加清晰。但只有實例才能靜態(tài)地進行類型匹配剂公,類對象不能希俩,因為它們不屬于某個類的一種。
    而在發(fā)送消息的表達式中纲辽,作為消息的接收者颜武,類名代表的是類對象,這種用法在前面的例子中已經(jīng)多次展示過拖吼。只有作為消息接收者鳞上,類名才能代表一個類對象。在任何別的上下文環(huán)境中吊档,必須要先通過發(fā)送一個class消息篙议,獲取類對象的id,例如:
    if ( [anObject isKindOfClass:[Rectangle class]] )
    如果你不知道在編譯時類名稱怠硼,僅僅是用“ Rectangle”這個名稱作為參數(shù)鬼贱,NSClassFromString將返回類對象:
    NSString *className;
    if ( [anObject isKindOfClass:NSClassFromString(className)] )
    如果className字符串不是一個有效的類名,返回nil。類名和全局變量存在同一個nameSpace拒名,具有唯一性吩愧。

附錄1:

類的基礎數(shù)據(jù)結構:

typedef struct objc_class *Class;
struct objc_class { // 類的數(shù)據(jù)結構
    Class isa  OBJC_ISA_AVAILABILITY; // 
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父類,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy),則super_class為NULL增显。
    const char *name                        OBJC2_UNAVAILABLE;  // 類名
    long version                            OBJC2_UNAVAILABLE;  // 類的版本信息雁佳,默認為0
    long info                               OBJC2_UNAVAILABLE;  // 類信息,供運行期使用的一些位標識
    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;

類實例數(shù)據(jù)結構:

struct objc_object {

    Class isa  OBJC_ISA_AVAILABILITY;

};
typedef struct objc_object *id;

可以看到同云,這個結構體只有一個字體糖权,即指向其類的isa指針。因此id其實就是指向Class類型的指針炸站。這樣星澳,當我們向一個Objective-C對象發(fā)送消息時,運行時庫會根據(jù)實例對象的isa指針找到這個實例對象所屬的類旱易。Runtime庫會在類的方法列表及父類的方法列表中去尋找與消息對應的selector指向的方法禁偎。找到后即運行這個方法。
當創(chuàng)建一個特定類的實例對象時阀坏,分配的內(nèi)存包含一個objc_object數(shù)據(jù)結構如暖,然后是類的實例變量的數(shù)據(jù)。NSObject類的alloc和allocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結構忌堂。
另外還有我們常見的id盒至,它是一個objc_object結構類型的指針。它的存在可以讓我們實現(xiàn)類似于C++中泛型的一些操作。該類型的對象可以轉(zhuǎn)換為任何一種對象枷遂,有點類似于C語言中void *指針類型的作用樱衷。

  1. isa:
    isa指針指向其類.
    NSArray *array = [NSArray array];
    顯而易見, array的數(shù)據(jù)結構中isa指針指向NSArray酒唉。
    而其實在這個例子中矩桂,+array消息發(fā)送給了NSArray類,這個NSArray也是一個對象黔州,類對象耍鬓。它也包含一個指向其類的一個isa指針。那么這些就有一個問題了流妻,這個isa指針指向什么呢?即metaClass元類笆制。 meta-class是一個類對象的類绅这。當我們向一個對象發(fā)送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法在辆;而向一個類發(fā)送消息時证薇,會在這個類的meta-class的方法列表中查找。
    meta-class之所以重要匆篓,是因為它存儲著一個類的所有類方法浑度。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同鸦概。
    如果你有想法箩张,再深入一下,meta-class也是一個類窗市,也可以向它發(fā)送一個消息先慷,那么它的isa又是指向什么呢?為了不讓這種結構無限延伸下去咨察,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class论熙,以此作為它們的所屬類。
  2. super_class:
    指向該類的父類摄狱,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy)脓诡,則super_class為NULL。
  3. cache:
    用于緩存最近使用的方法媒役。一個接收者對象接收到一個消息時祝谚,它會根據(jù)isa指針去查找能夠響應這個消息的對象。在實際使用中刊愚,這個對象只有一部分方法是常用的踊跟,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時商玫,我們都是methodLists中遍歷一遍箕憾,性能勢必很差。這時拳昌,cache就派上用場了袭异。在我們每次調(diào)用過一個方法后,這個方法就會被緩存到cache列表中炬藤,下次調(diào)用的時候runtime就會優(yōu)先去cache中查找御铃,如果cache沒有,才去 methodLists中查找方法沈矿。這樣上真,對于那些經(jīng)常用到的方法的調(diào)用,但提高了調(diào)用的效率羹膳。結構如下:
struct objc_cache {
/* mask :一個整數(shù)睡互,指定分配的緩存bucket的總數(shù)。在方法查找過程中陵像,
Objective-C runtime使用這個字段來確定開始線性查找數(shù)組的索引位
置就珠。指向方法selector的指針與該字段做一個AND位操作(index = 
(mask & selector))。這可以作為一個簡單的hash散列算法醒颖。*/
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
  /*occupied:一個整數(shù)妻怎,指定實際占用的緩存bucket的總數(shù)。*/
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
  /*buckets:指向Method數(shù)據(jù)結構指針的數(shù)組泞歉。這個數(shù)組可能包含不
超過mask+1個元素逼侦。需要注意的是,指針可能是NULL疏日,表示這個緩
存bucket沒有被占用偿洁,另外被占用的bucket可能是不連續(xù)的。這個數(shù)
組可能會隨著時間而增長沟优。*/
    Method buckets[1]                                        OBJC2_UNAVAILABLE;

};
  1. version:
    我們可以使用這個字段來提供類的版本信息涕滋。

附錄2

類與對象操作函數(shù)

runtime提供了大量的函數(shù)來直接操作類與對象數(shù)據(jù)結構,對照附錄1挠阁。類的操作方法大部分是以class為前綴的宾肺,而對象的操作方法大部分是以objc或object_為前綴。下面我們將根據(jù)這些方法的用途來分類討論這些方法的使用侵俗。

1. 類相關操作函數(shù)

我們可以回過頭去看看objc_class的定義锨用,runtime提供的操作類的方法主要就是針對這個結構體中的各個字段的。

類名(name)

類名操作的函數(shù)主要有:
// 獲取類的類名
const char * class_getName ( Class cls );

  • 對于class_getName函數(shù)隘谣,如果傳入的cls為Nil增拥,則返回一個char字符串啄巧。

父類(super_class)和元類(meta-class)

父類和元類操作的函數(shù)主要有:
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 判斷給定的Class是否是一個元類
BOOL class_isMetaClass ( Class cls );

  • class_getSuperclass函數(shù),當cls為Nil或者cls為根類時掌栅,返回Nil秩仆。不過通常我們可以使用NSObject類的superclass方法來達到同樣的目的。
  • class_isMetaClass函數(shù)猾封,如果是cls是元類澄耍,則返回YES;如果否或者傳入的cls為Nil晌缘,則返回NO齐莲。

實例變量大小(instance_size)

實例變量大小操作的函數(shù)有:
// 獲取實例大小
size_t class_getInstanceSize ( Class cls );

成員變量(ivars)及屬性

在objc_class中,所有的成員變量磷箕、屬性的信息是放在鏈表ivars中的选酗。ivars是一個數(shù)組,數(shù)組中每個元素是指向Ivar(變量信息)的指針岳枷。runtime提供了豐富的函數(shù)來操作這一字段星掰。大體上可以分為以下幾類:

  1. 成員變量操作函數(shù),主要包含以下函數(shù):
    // 獲取類中指定名稱實例成員變量的信息
    Ivar class_getInstanceVariable ( Class cls, const char *name );
    // 獲取類成員變量的信息
    Ivar class_getClassVariable ( Class cls, const char *name );
    // 添加成員變量
    BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
    // 獲取整個成員變量列表
    Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
  • class_getInstanceVariable函數(shù)嫩舟,它返回一個指向包含name指定的成員變量信息的objc_ivar結構體的指針(Ivar)。
  • class_getClassVariable函數(shù)怀偷,目前沒有找到關于Objective-C中類變量的信息家厌,一般認為Objective-C不支持類變量。注意椎工,返回的列表不包含父類的成員變量和屬性饭于。
  • Objective-C不支持往已存在的類中添加實例變量,因此不管是系統(tǒng)庫提供的提供的類维蒙,還是我們自定義的類掰吕,都無法動態(tài)添加成員變量。但如果我們通過運行時來創(chuàng)建一個類的話颅痊,又應該如何給它添加成員變量呢殖熟?這時我們就可以使用class_addIvar函數(shù)了。不過需要注意的是斑响,這個方法只能在objc_allocateClassPair函數(shù)與objc_registerClassPair之間調(diào)用菱属。另外,這個類也不能是元類舰罚。成員變量的按字節(jié)最小對齊量是1<<alignment纽门。這取決于ivar的類型和機器的架構。如果變量的類型是指針類型营罢,則傳遞log2(sizeof(pointer_type))赏陵。
  • class_copyIvarList函數(shù),它返回一個指向成員變量信息的數(shù)組,數(shù)組中每個元素是指向該成員變量信息的objc_ivar結構體的指針蝙搔。這個數(shù)組不包含在父類中聲明的變量缕溉。outCount指針返回數(shù)組的大小。需要注意的是杂瘸,我們必須使用free()來釋放這個數(shù)組倒淫。
  1. 屬性操作函數(shù),主要包含以下函數(shù):
    // 獲取指定的屬性
    objc_property_t class_getProperty ( Class cls, const char *name );
    // 獲取屬性列表
    objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
    // 為類添加屬性
    BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
    // 替換類的屬性
    void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
    這一種方法也是針對ivars來操作败玉,不過只操作那些是屬性的值敌土。我們在后面介紹屬性時會再遇到這些函數(shù)。

  2. 在MAC OS X系統(tǒng)中运翼,我們可以使用垃圾回收器返干。runtime提供了幾個函數(shù)來確定一個對象的內(nèi)存區(qū)域是否可以被垃圾回收器掃描,以處理strong/weak引用血淌。這幾個函數(shù)定義如下:
    const uint8_t * class_getIvarLayout ( Class cls );
    void class_setIvarLayout ( Class cls, const uint8_t *layout );
    const uint8_t * class_getWeakIvarLayout ( Class cls );
    void class_setWeakIvarLayout ( Class cls, const uint8_t *layout );
    但通常情況下贷笛,我們不需要去主動調(diào)用這些方法;在調(diào)用objc_registerClassPair時隐孽,會生成合理的布局竟稳。在此不詳細介紹這些函數(shù)。

方法(methodLists)

方法操作主要有以下函數(shù):
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

  • class_addMethod的實現(xiàn)會覆蓋父類的方法實現(xiàn)沦补,但不會取代本類中已存在的實現(xiàn)乳蓄,如果本類中包含一個同名的實現(xiàn),則函數(shù)會返回NO夕膀。如果要修改已存在實現(xiàn)虚倒,可以使用method_setImplementation。一個Objective-C方法是一個簡單的C函數(shù)产舞,它至少包含兩個參數(shù)—self和_cmd魂奥。所以,我們的實現(xiàn)函數(shù)(IMP參數(shù)指向的函數(shù))至少需要兩個參數(shù)易猫,如下所示:
    void myMethodIMP(id self, SEL _cmd)
    {
    // implementation ....
    }
    與成員變量不同的是耻煤,我們可以為類動態(tài)添加方法,不管這個類是否已存在擦囊。

另外违霞,參數(shù)types是一個描述傳遞給方法的參數(shù)類型的字符數(shù)組,這就涉及到類型編碼瞬场,我們將在后面介紹买鸽。

  • class_getInstanceMethod、class_getClassMethod函數(shù)贯被,與class_copyMethodList不同的是眼五,這兩個函數(shù)都會去搜索父類的實現(xiàn)妆艘。
  • class_copyMethodList函數(shù),返回包含所有實例方法的數(shù)組看幼,如果需要獲取類方法批旺,則可以使用class_copyMethodList(object_getClass(cls), &count)(一個類的實例方法是定義在元類里面)。該列表不包含父類實現(xiàn)的方法诵姜。outCount參數(shù)返回方法的個數(shù)汽煮。在獲取到列表后,我們需要使用free()方法來釋放它棚唆。
  • class_replaceMethod函數(shù)暇赤,該函數(shù)的行為可以分為兩種:如果類中不存在name指定的方法,則類似于class_addMethod函數(shù)一樣會添加方法宵凌;如果類中已存在name指定的方法鞋囊,則類似于method_setImplementation一樣替代原方法的實現(xiàn)。
  • class_getMethodImplementation函數(shù)瞎惫,該函數(shù)在向類實例發(fā)送消息時會被調(diào)用溜腐,并返回一個指向方法實現(xiàn)函數(shù)的指針。這個函數(shù)會比method_getImplementation(class_getInstanceMethod(cls, name))更快瓜喇。返回的函數(shù)指針可能是一個指向runtime內(nèi)部的函數(shù)挺益,而不一定是方法的實際實現(xiàn)。例如乘寒,如果類實例無法響應selector矩肩,則返回的函數(shù)指針將是運行時消息轉(zhuǎn)發(fā)機制的一部分。
  • class_respondsToSelector函數(shù)肃续,我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達到相同目的。

協(xié)議(objc_protocol_list)

協(xié)議相關的操作包含以下函數(shù):
// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

  • class_conformsToProtocol函數(shù)可以使用NSObject類的conformsToProtocol:方法來替代叉袍。
  • class_copyProtocolList函數(shù)返回的是一個數(shù)組始锚,在使用后我們需要使用free()手動釋放。

版本(version)

版本相關的操作包含以下函數(shù):
// 獲取版本號
int class_getVersion ( Class cls );
// 設置版本號
void class_setVersion ( Class cls, int version );

其它

runtime還提供了兩個函數(shù)來供CoreFoundation的tool-free bridging使用喳逛,即:
Class objc_getFutureClass ( const char *name );
void objc_setFutureClass ( Class cls, const char *name );
通常我們不直接使用這兩個函數(shù)瞧捌。

2. 實例相關操作函數(shù)

實例操作函數(shù)主要是針對我們創(chuàng)建的實例對象的一系列操作函數(shù),我們可以使用這組函數(shù)來從實例對象中獲取我們想要的一些信息润文,如實例對象中變量的值姐呐。這組函數(shù)可以分為三小類:

  1. 針對整個對象進行操作的函數(shù),這類函數(shù)包含
    // 返回指定對象的一份拷貝
    id object_copy ( id obj, size_t size );
    // 釋放指定對象占用的內(nèi)存
    id object_dispose ( id obj );
    有這樣一種場景典蝌,假設我們有類A和類B曙砂,且類B是類A的子類。類B通過添加一些額外的屬性來擴展類A】ハ疲現(xiàn)在我們創(chuàng)建了一個A類的實例對象鸠澈,并希望在運行時將這個對象轉(zhuǎn)換為B類的實例對象柱告,這樣可以添加數(shù)據(jù)到B類的屬性中。這種情況下笑陈,我們沒有辦法直接轉(zhuǎn)換际度,因為B類的實例會比A類的實例更大,沒有足夠的空間來放置對象涵妥。此時乖菱,我們就要以使用以上幾個函數(shù)來處理這種情況,如下代碼所示:
    NSObject *a = [[NSObject alloc] init];
    id newB = object_copy(a, class_getInstanceSize(MyClass.class));
    object_setClass(newB, MyClass.class);
    object_dispose(a);

  2. 針對對象實例變量進行操作的函數(shù)蓬网,這類函數(shù)包含:
    // 修改類實例的實例變量的值
    Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
    // 獲取對象實例變量的值
    Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
    // 返回指向給定對象分配的任何額外字節(jié)的指針
    void * object_getIndexedIvars ( id obj );
    // 返回對象中實例變量的值
    id object_getIvar ( id obj, Ivar ivar );
    // 設置對象中實例變量的值
    void object_setIvar ( id obj, Ivar ivar, id value );
    如果實例變量的Ivar已經(jīng)知道窒所,那么調(diào)用object_getIvar會比object_getInstanceVariable函數(shù)快,相同情況下拳缠,object_setIvar也比object_setInstanceVariable快墩新。

  3. 針對對象的類進行操作的函數(shù),這類函數(shù)包含:
    // 返回給定對象的類名
    const char * object_getClassName ( id obj );
    // 返回對象的類
    Class object_getClass ( id obj );
    // 設置對象的類
    Class object_setClass ( id obj, Class cls );

獲取類定義

Objective-C動態(tài)運行庫會自動注冊我們代碼中定義的所有的類窟坐。我們也可以在運行時創(chuàng)建類定義并使用objc_addClass函數(shù)來注冊它們海渊。runtime提供了一系列函數(shù)來獲取類定義相關的信息,這些函數(shù)主要包括:
// 獲取已注冊的類定義的列表
int objc_getClassList ( Class *buffer, int bufferCount );
// 創(chuàng)建并返回一個指向所有已注冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
// 返回指定類的類定義
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );
// 返回指定類的元類
Class objc_getMetaClass ( const char *name );

  • objc_getClassList函數(shù):獲取已注冊的類定義的列表哲鸳。我們不能假設從該函數(shù)中獲取的類對象是繼承自NSObject體系的臣疑,所以在這些類上調(diào)用方法是,都應該先檢測一下這個方法是否在這個類中實現(xiàn)徙菠。
    下面代碼演示了該函數(shù)的用法:
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
    classes = malloc(sizeof(Class) * numClasses);
    numClasses = objc_getClassList(classes, numClasses);
    NSLog(@"number of classes: %d", numClasses);
    for (int i = 0; i < numClasses; i++) {
    Class cls = classes[i];
    NSLog(@"class name: %s", class_getName(cls));
  }
  free(classes);
}

輸出結果如下:
2014-10-23 16:20:52.589 RuntimeTest[8437:188589] number of classes: 1282
2014-10-23 16:20:52.589 RuntimeTest[8437:188589] class name: DDTokenRegexp
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: _NSMostCommonKoreanCharsKeySet
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: OS_xpc_dictionary
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSFileCoordinator
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: NSAssertionHandler
2014-10-23 16:20:52.590 RuntimeTest[8437:188589] class name: PFUbiquityTransactionLogMigrator
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSNotification
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: NSKeyValueNilSetEnumerator
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: OS_tcp_connection_tls_session
2014-10-23 16:20:52.591 RuntimeTest[8437:188589] class name: _PFRoutines
......還有大量輸出

獲取類定義的方法有三個:objc_lookUpClass, objc_getClass和objc_getRequiredClass讯沈。如果類在運行時未注冊,則objc_lookUpClass會返回nil婿奔,而objc_getClass會調(diào)用類處理回調(diào)缺狠,并再次確認類是否注冊,如果確認未注冊萍摊,再返回nil挤茄。而objc_getRequiredClass函數(shù)的操作與objc_getClass相同,只不過如果沒有找到類冰木,則會殺死進程穷劈。

  • objc_getMetaClass函數(shù):如果指定的類沒有注冊,則該函數(shù)會調(diào)用類處理回調(diào)踊沸,并再次確認類是否注冊歇终,如果確認未注冊,再返回nil逼龟。不過评凝,每個類定義都必須有一個有效的元類定義,所以這個函數(shù)總是會返回一個元類定義腺律,不管它是否有效肥哎。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辽俗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子篡诽,更是在濱河造成了極大的恐慌崖飘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件杈女,死亡現(xiàn)場離奇詭異朱浴,居然都是意外死亡,警方通過查閱死者的電腦和手機达椰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門翰蠢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啰劲,你說我怎么就攤上這事梁沧。” “怎么了蝇裤?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵廷支,是天一觀的道長。 經(jīng)常有香客問我栓辜,道長恋拍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任藕甩,我火速辦了婚禮施敢,結果婚禮上,老公的妹妹穿的比我還像新娘狭莱。我一直安慰自己僵娃,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布腋妙。 她就那樣靜靜地躺著悯许,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辉阶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天瘩扼,我揣著相機與錄音谆甜,去河邊找鬼。 笑死集绰,一個胖子當著我的面吹牛规辱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播栽燕,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼罕袋,長吁一口氣:“原來是場噩夢啊……” “哼改淑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起浴讯,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤朵夏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后榆纽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仰猖,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年奈籽,在試婚紗的時候發(fā)現(xiàn)自己被綠了饥侵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡衣屏,死狀恐怖躏升,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情狼忱,我是刑警寧澤膨疏,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站藕赞,受9級特大地震影響成肘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斧蜕,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一双霍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧批销,春花似錦洒闸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掀宋,卻和暖如春深纲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背劲妙。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工湃鹊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镣奋。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓币呵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親侨颈。 傳聞我的和親對象是個殘疾皇子余赢,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容