今天我們來介紹這兩個神奇的方法,它們可以在一定程度上改變你對于系統控件的認識,也提供了你深入了解系統控件的一個小窗口,在開發中可能會帶來你意想不到的驚喜,但是風險性同樣也不是一般的大。
我們之前解析了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);
- 然后你就順利找到了
伍麗娟
,就在你以為就要和她過上幸福生活的時候,她們班長站起來一腳把你踹了出去。甚至你連她們班長的臉都沒有看清,只知道他的名字好像是**三道杠
。
作為一個勇敢的男人,你怎么可能就這么認輸了呢,于是你要想辦法干掉她們班長,但是干掉一個人太明顯了,容易被懷疑,于是你想到了無敵暴力的KVC
,KVC
雖然牛逼,但是性情不太穩點,要小心自取滅亡,但是你已經被愛情沖昏了頭腦,上去就是干!那么問題來了:干掉她們班長需要幾步?
答:干掉她們班長需要三步。首先按照之前的前兩步找到她們班長的名字無敵三道杠
,然后讓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
)
對比一下,你會發現,并不是每一個屬性都對應了一個自己的實例變量,哎呀,平時自己寫的時候不是這樣的啊?并且就連公開的text
、delegate
都沒有對應的實例變量,這對于一些人可能會有些困惑,我們來看這么一個例子:
同樣,生成一個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
。但是,在特定情況下,屬性不會生成對應的實例變量,包括setter
和getter
方法也有特定生成原則。(可自行百度,應該有很多)
那這個東西有什么卵用嗎?之前我們說過objc_msgSend
這個方法可以無限制的調用公開哪怕私有的方法,并且我對此進行了封裝,那我們就可以兩者結合通過setter
和getter
方法給屬性賦值以及獲取屬性(就算它不生成實例變量又如何)。
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));
運行結果:
?? 關于
class_copyIvarList
class_copyPropertyList
兩個方法對于系統的類來說能少用就少用,自己寫的類放心大膽的用,出了問題你砍我。就這些,看完點個關注,點個贊就走吧,如果你要打賞,那還不如在評論區表達一下你對伍麗娟
的熱愛。