死磕Objective-C runtime執行時之一

天才小飛貓發表於2016-06-03

說到Objc執行時,如果你還不清楚,那可要看仔細了,如果你是靠顏值而不是才華能夠順利通過面試,喵了個咪的,我也想去試試

Objc執行時2.0

iOS出現時就是執行時2.0版本了,和舊的相比擁有兩大特性:
第一,就是修改,增加或刪除一個類的例項變數,不用再重新編譯子類就可以用了。
第二,就是加為@property加入了Synthesize

實戰問題一(答案在最後):

請將屬性property(atomic,nonatomic, assign, weak, strong, copy, readonly, readwrite blah!blah!)按照功能分組

實戰問題二(答案在最後):

請回答下列@property變數cool和cat的預設屬性

@interface ViewController : UIViewController

@property BOOL cool;
@property NSObject *cat;

@end

Objc執行時

Objc執行時相當於Objective-C的作業系統,當我們編譯程式碼時,編譯器把原始碼編譯成執行時能夠懂得資料結構(比如isa包括類的繼承資訊)和執行時方法(比如objc_msgSend), 剩下的就交給執行時動態處理了。

NSObject成員變數isa

@interface NSObject{
    objc_class *isa
}

//點開isa連結搜尋struct objc_class : objc_object能夠查到其宣告
struct objc_class : objc_object {
    // Class ISA;
    objc_class *superclass;
    ....
    方法派遣表   selector對應的C語言函式
    ....
}

從objc_class宣告可以看出,每一個isa->superclass同樣是objc_class型別,
這樣就組成了一個繼承的連結串列結構

執行時3種使用方式

1.寫Objective-C程式碼必然用到,雖然沒這種感覺
2.呼叫NSObject執行時方法
3.直接呼叫執行時API

執行時objc_msgSend

objc方法只不過是C語言方方法,加上兩個特殊的引數第一個是receiver(self),第二個引數是selector(_cmd)

比如以下的方法呼叫
[receiver message:arg1] //假設定義arg1為BOOL型別
具體實現:
(void (*)(id, SEL, BOOL))[target methodForSelector:@selector(message:)];
執行時:

`objc_msgSend(receiver, selector, arg1)`

objc_msgSend做的事情如下:

  1. 根據receiver和selector先在receiver的方法派遣表裡面查詢是否有selector這個方法實現, 如果沒找到,receiver->isa->superclass去查詢,以此類推,直到找到對應的方法實現,若直到NSObject都沒有找到對應實現,中間過程在下文解釋,最後掉用[receiver doesNotRecognizeSelector:_cmd]就拋異常出錯了

  2. 將所有引數傳給具體實現方法呼叫

  3. 將具體實現結果反回來

跳過執行時objc_msgSend

儘管objc_msgSend在成功呼叫一次方法之後,每個Class會有快取,下次重複呼叫該方法時,查詢速度會大大提高,但是還是有執行時訊息傳送的時間,如果一個函式被呼叫成千上萬次,為了減少訊息派遣時間,可以跳過執行時objc_msgSend,直接呼叫方法實現

void (*message)(id, SEL, BOOL);
int i;
 
message = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(message:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(message:), YES);

問題一答案:

原子性:atomic,nonatomic
讀寫性: readwrite, readonly
ARC版記憶體管理: assign, strong, copy, weak

問題二答案:

@property cool = TB,V_cool
@property cat = T@"NSObject",&,V_cat

子問題1: 你是怎麼得到答案的?答案就是用執行時啦!!!

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    
    id LenderClass = objc_getClass("ViewController");
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        fprintf(stdout, "@property %s = %s
", property_getName(property), property_getAttributes(property));
    }
}

子問題2: 如何解讀?首先說明這奇怪的字元時編譯器乾的好事啦,參考主要是這裡還有這裡

The string returned by |property_getAttributes| starts with a T followed by the @encode type and a comma, and finishes with a V followed by the name of the backing instance variable.

上面一坨就是說,property_getAttributes獲得的字串的格式,以T開頭,然後是<變數型別>,然後是一個逗號,然後是<屬性>,最後是一個V,再往後就是下劃線_和變數名了


/*根據文件:
如果是readonly應該有R屬性, 所以預設應該是readwrite
如果是weak,strong,copy應該分別有W, &, C, 所以預設應該是assign
若果有nonatomic應該有N, 所以預設是atomic       

TB,V_cool B代表BOOL, 中間什麼屬性都沒有對不對,根據以上分析,預設是atomic, assign, readwrite 
T@"NSObject",&,V_cat 則是atomic, strong, readwrite
*/

相關文章