今天我们来介绍这两个神奇的方法,它们可以在一定程度上改变你对于系统控件的认识,也提供了你深入了解系统控件的一个小窗口,在开发中可能会带来你意想不到的惊喜,但是风险性同样也不是一般的大。
我们之前解析了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
两个方法对于系统的类来说能少用就少用,自己写的类放心大胆的用,出了问题你砍我。就这些,看完点个关注,点个赞就走吧,如果你要打赏,那还不如在评论区表达一下你对伍丽娟
的热爱。
作者:MrHippo
链接:https://www.jianshu.com/p/cb55dbb72c84
来源:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。