Runtime奇技淫巧之class_copyIvarList class_copyPropertyList

今天我們來介紹這兩個神奇的方法,它們可以在一定程度上改變你對于系統控件的認識,也提供了你深入了解系統控件的一個小窗口,在開發中可能會帶來你意想不到的驚喜,但是風險性同樣也不是一般的大。

我們之前解析了Runtime中常見的數據結構(想看點這里)。我們知道對象的實例變量存在于Class結構體的一個ivars的鏈表中,同時runtime提供了豐富的函數對其進行操作。當然對于我們來說,私有變量才是感興趣的點,就像窺探別人隱私一樣。

問:你知道隔壁班有一個特別漂亮的小姑娘,但你只知道她們班只有她的名字是三個字,如果你想要找到她,攏共分幾步?
答:攏共分三步。步驟如下:
  • 首先你需要class_copyIvarList這個方法,獲取到他們班的花名冊,最終發現只有一個人的名字是三個字,她有一個美麗的名字叫做伍麗娟,具體操作如下:
/**
 *獲取當前類的所有實例變量
 */
+(void)getAllIvarNameWithClass:(Class)YSClass Completed:(void (^)(NSArray *ivarNameArray))completed{
    NSMutableArray *ivarNameArray = [NSMutableArray array];
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(YSClass, &count);
    for (int i = 0; i < count; i++){
        Ivar ivar = ivars[i];
        const char *ivarName = ivar_getName(ivar);
        NSString *ivarNameCode = [NSString stringWithUTF8String:ivarName];
#ifdef _YSDebugLog
        NSLog(@"%d : %@",i,ivarNameCode);
#endif
        [ivarNameArray addObject:ivarNameCode];
    }
    //由于ARC只適用于Foundation等框架,對runtime 等并不適用,所以ivars需要free()手動釋放。
    free(ivars);
    if (completed) completed(ivarNameArray);
}
  • 你找到了名字,下一步你就要通過這個名字找到這個人更多的信息,也就是Ivar結構體(其實找這個人的名字時候花名冊上就有她的信息,可以直接第三步,但是你就喜歡一步步來),你通過class_getInstanceVariable這個方法可以獲取到名字對應的Ivar信息,差不多你就已經知道了伍麗娟所有的外部信息了,比如身高啊,體重啊。具體操作如下:
Ivar WLJIvar = class_getInstanceVariable([BJ class], "伍麗娟");
  • 既然得到了他的全部信息,那你就要開始找這個人了,直接沖到他們班,根據你掌握的信息,一把把她拉出來。具體操作如下:
id WLJ = object_getIvar(BJ, WLJIvar);
  • 然后你就順利找到了 伍麗娟,就在你以為就要和她過上幸福生活的時候,她們班長站起來一腳把你踹了出去。甚至你連她們班長的臉都沒有看清,只知道他的名字好像是**三道杠
作為一個勇敢的男人,你怎么可能就這么認輸了呢,于是你要想辦法干掉她們班長,但是干掉一個人太明顯了,容易被懷疑,于是你想到了無敵暴力的KVCKVC雖然牛逼,但是性情不太穩點,要小心自取滅亡,但是你已經被愛情沖昏了頭腦,上去就是干!那么問題來了:干掉她們班長需要幾步?
答:干掉她們班長需要三步。首先按照之前的前兩步找到她們班長的名字無敵三道杠,然后讓KVC直接把你準備好的無敵四道杠本人替換掉無敵三道杠,具體操作如下:
[BJ setValue:無敵四道杠本人 forKey:@"無敵三道杠"];

她們班長換成了自己人,從此你和伍麗娟過上了幸福快樂的生活。

我們說下實際應用的場景:

問:如何給UITextView添加PlaceHolder?
答:創建一個UILabel,然后添加到UITextView上嘍。
剛才這么精彩的故事白講了!!!
正確回答應該是這樣的:
  • 首先我們先找到伍麗娟,然后... ...,我就不知道了。
  • 首先我們查找UITextView所有的實例變量,利用上面提到的方法:
[NSObject getAllIvarNameWithClass:[UITextView class] Completed:^(NSArray *ivarNameArray) {
    NSLog(@"ivars_%@",ivarNameArray);
}];

打印結果:

"_private",
"_textStorage",
... ...
"_preferredMaxLayoutWidth",
"_placeholderLabel",
"_inputAccessoryView",
... ...
"_inputView"

你會驚喜的發現,里面有一個叫做_placeholderLabel的實例變量,于是你按照找到伍麗娟的方式想要找到這個對象,不好意思,你得到的是nil,也就是說蘋果可能根本就沒有初始化這個東西,所以KVC閃亮登場:

//給TextView添加PlaceHolder
UITextView *textView = [[UITextView alloc]initWithFrame:CGRectMake(10, 50, CGRectGetWidth(self.view.frame) - 20, 200)];
[textView setBackgroundColor:[UIColor whiteColor]];
textView.font = [UIFont systemFontOfSize:16];
textView.delegate = self;
[self.view addSubview:textView];
UILabel *placeHolderLabel = [[UILabel alloc] init];
placeHolderLabel.text = @"我是PlaceHolder,不是伍麗娟。";
placeHolderLabel.numberOfLines = 0;
placeHolderLabel.textColor = [UIColor lightGrayColor];
[placeHolderLabel sizeToFit];
placeHolderLabel.font = textView.font;
[textView addSubview:placeHolderLabel];
[textView setValue:placeHolderLabel forKey:@"_placeholderLabel"];

運行結果:


然后再去獲取PlaceHolder,已經是你賦值的那個對象:

Ivar placeHolderIvar = class_getInstanceVariable([UITextView class], "_placeholderLabel");
id getPH = object_getIvar(textView, placeHolderIvar);
NSLog(@"placeHolder_%p_%p", placeHolderLabel,getPH);

打印結果:

TextViewDemo[2325:290654] 0x7ffa31e01d20_0x7ffa31e01d20

class_copyIvarList配合KVC這么用雖然有時候很方便,但是不免有風險,關于未公開的私有變量蘋果的改動沒必要寫到明面上,也許某天你的應用就會crash到你奔潰。當然你可以放心用到你自己定義的類上。


關于class_copyIvarList先說這么多,用法肯定不只是局限于我說的這些,下面我們說class_copyPropertyList這個方法。
類似于剛才找到伍麗娟的方式,我們同樣封裝一個獲取所有屬性的方法如下:

/**
 *獲取當前類的所有屬性
 */
+(void)getAllPropertyNameWithClass:(Class)YSClass Completed:(void (^)(NSArray *propertyNameArray))completed{
    NSMutableArray *propertyNameArray = [NSMutableArray array];
    unsigned int propertyCount = 0;
    objc_property_t *propertys = class_copyPropertyList(YSClass, &propertyCount);
    for (int i = 0; i < propertyCount; i++){
        objc_property_t property = propertys[i];
        const char *propertysName = property_getName(property);
        NSString *propertysNameCode = [NSString stringWithUTF8String:propertysName];
#ifdef _YSDebugLog
        NSLog(@"------%d : %@",i,propertysNameCode);
#endif
        [propertyNameArray addObject:propertysNameCode];
    }
    //由于ARC只適用于Foundation等框架,對runtime 等并不適用,所以propertys需要free()手動釋放。
    free(propertys);
    if (completed) completed(propertyNameArray);
}

我們同時調用獲取實例變量以及屬性的方法做一下對比:

[NSObject getAllIvarNameWithClass:[UITextView class] Completed:^(NSArray *ivarNameArray) {
    NSLog(@"ivars_%@",ivarNameArray);
}];
[NSObject getAllPropertyNameWithClass:[UITextView class] Completed:^(NSArray *propertyNameArray) {
    NSLog(@"propertys_%@",propertyNameArray);
}];

打印結果:

//實例變量
ivars_(
    "_private",
    "_textStorage",
    "_textContainer",
    "_layoutManager",
    "_containerView",
    "_inputDelegate",
    "_tokenizer",
    "_inputController",
    "_interactionAssistant",
    "_textInputTraits",
    "_autoscroll",
    "_tvFlags",
    "_contentSizeUpdateSeqNo",
    "_scrollTarget",
    "_scrollPositionDontRecordCount",
    "_scrollPosition",
    "_offsetFromScrollPosition",
    "_linkInteractionItem",
    "_dataDetectorTypes",
    "_preferredMaxLayoutWidth",
    "_placeholderLabel",
    "_inputAccessoryView",
    "_linkTextAttributes",
    "_streamingManager",
    "_characterStreamingManager",
    "_siriAnimationStyle",
    "_siriParameters",
    "_firstBaselineOffsetFromTop",
    "_lastBaselineOffsetFromBottom",
    "_cuiCatalog",
    "_beforeFreezingTextContainerInset",
    "_duringFreezingTextContainerInset",
    "_beforeFreezingFrameSize",
    "_unfreezingTextContainerSize",
    "_adjustsFontForContentSizeCategory",
    "_clearsOnInsertion",
    "_multilineContextWidth",
    "_inputView"
)
//屬性
propertys_(
    "_drawsDebugBaselines",
    hash,
    superclass,
    description,
    debugDescription,
    delegate,
    text,
    font,
    textColor,
    textAlignment,
    selectedRange,
    editable,
    selectable,
    dataDetectorTypes,
    allowsEditingTextAttributes,
    attributedText,
    typingAttributes,
    inputView,
    inputAccessoryView,
    clearsOnInsertion,
    textContainer,
    textContainerInset,
    layoutManager,
    textStorage,
    linkTextAttributes,
    hash,
    superclass,
    description,
    debugDescription,
    autocapitalizationType,
    autocorrectionType,
    spellCheckingType,
    keyboardType,
    keyboardAppearance,
    returnKeyType,
    enablesReturnKeyAutomatically,
    secureTextEntry,
    textContentType,
    recentInputIdentifier,
    validTextRange,
    PINEntrySeparatorIndexes,
    textTrimmingSet,
    insertionPointColor,
    selectionBarColor,
    selectionHighlightColor,
    selectionDragDotImage,
    insertionPointWidth,
    textLoupeVisibility,
    textSelectionBehavior,
    textSuggestionDelegate,
    isSingleLineDocument,
    contentsIsSingleValue,
    hasDefaultContents,
    acceptsEmoji,
    acceptsDictationSearchResults,
    forceEnableDictation,
    forceDisableDictation,
    forceDefaultDictationInfo,
    forceDictationKeyboardType,
    emptyContentReturnKeyType,
    returnKeyGoesToNextResponder,
    acceptsFloatingKeyboard,
    acceptsSplitKeyboard,
    displaySecureTextUsingPlainText,
    displaySecureEditsUsingPlainText,
    learnsCorrections,
    shortcutConversionType,
    suppressReturnKeyStyling,
    useInterfaceLanguageForLocalization,
    deferBecomingResponder,
    enablesReturnKeyOnNonWhiteSpaceContent,
    autocorrectionContext,
    responseContext,
    inputContextHistory,
    disablePrediction,
    disableInputBars,
    isCarPlayIdiom,
    textScriptType,
    devicePasscodeEntry,
    hasText,
    selectedTextRange,
    markedTextRange,
    markedTextStyle,
    beginningOfDocument,
    endOfDocument,
    inputDelegate,
    tokenizer,
    textInputView,
    selectionAffinity,
    insertDictationResultPlaceholder,
    adjustsFontForContentSizeCategory
)

對比一下,你會發現,并不是每一個屬性都對應了一個自己的實例變量,哎呀,平時自己寫的時候不是這樣的啊?并且就連公開的textdelegate都沒有對應的實例變量,這對于一些人可能會有些困惑,我們來看這么一個例子:
同樣,生成一個Person類:

.h
@interface Person : NSObject{
    NSInteger age;
}
@property(nonatomic,strong)NSString *name;
@end
-------------------------------------------------------------
.m
@implementation Person
-(void)setName:(NSString *)name{
    _name = name;
}

-(NSString *)name{
    return _name;
}
@end

你會發現報錯了,沒有發現這個實例變量:


我們創建一個Person類的分類如下:

.h
@interface Person (Character)
@property(nonatomic,strong)NSString* name;
@end
-------------------------------------------------------------
.m
@implementation Person (Character)
@end

下面我們打印這個類的實例變量和屬性:

[NSObject getAllIvarNameWithClass:[Person class] Completed:^(NSArray *ivarNameArray) {
    NSLog(@"ivars_%@",ivarNameArray);
}];
[NSObject getAllPropertyNameWithClass:[Person class] Completed:^(NSArray *propertyNameArray) {
    NSLog(@"propertys_%@",propertyNameArray);
}];

打印結果:

ivars_(
    age
)
propertys_(
    name
)

是不是發現了什么?一般情況下,聲明一個屬性相當于Ivar + setter方法 + getter方法(但是Ivar + setter方法 + getter方法并不代表它就是屬性),也就是相當于在Class結構體中Ivars鏈表中添加一個Ivar,同時在methodLists添加兩個Method。但是,在特定情況下,屬性不會生成對應的實例變量,包括settergetter方法也有特定生成原則。(可自行百度,應該有很多)
那這個東西有什么卵用嗎?之前我們說過objc_msgSend這個方法可以無限制的調用公開哪怕私有的方法,并且我對此進行了封裝,那我們就可以兩者結合通過settergetter方法給屬性賦值以及獲取屬性(就算它不生成實例變量又如何)。

UITextView *textView = [[UITextView alloc]initWithFrame:CGRectMake(10, 50, CGRectGetWidth(self.view.frame) - 20, 200)];
[textView setBackgroundColor:[UIColor whiteColor]];
textView.font = [UIFont systemFontOfSize:16];
textView.delegate = self;
[self.view addSubview:textView];
((void (*) (id , SEL, id)) (void *)objc_msgSend) (textView, sel_registerName("setText:"), @"我是伍麗娟");
bool acceptsEmoji = ((bool (*) (id, SEL)) (void *)objc_msgSend)(textView, sel_registerName("acceptsEmoji"));
NSLog(@"acceptsEmoji_%@",@(acceptsEmoji));

運行結果:


打印結果:acceptsEmoji_1

?? 關于class_copyIvarList class_copyPropertyList兩個方法對于系統的類來說能少用就少用,自己寫的類放心大膽的用,出了問題你砍我。就這些,看完點個關注,點個贊就走吧,如果你要打賞,那還不如在評論區表達一下你對伍麗娟的熱愛。

傳送門 : Runtime實用技巧(不扯淡,不套路)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372

推薦閱讀更多精彩內容