class_copylvarList&class_cop
写在开头
通过前面的介绍,我们可以知道对象的实例变量存在于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 *ivarNameCode = [NSString stringWithUTF8String:ivarName];
[ivarNameArray addObject:ivarNameCode];
}
if(completed) completed(ivarNameArray);
}
- 通过要找的变量名,查找这个变量的更多信息,也就是Ivar结构体,通过
class_getInstanceVariable
这个方法就可以获得对应变量的Ivar信息。
Ivar ivar = class_getInstanceVariable([BJ class]"要找的变量名");
下面就要通过object_getIvar()
这个方法拿到它了
id xxx = object_getIvar(BJ,ivar);
还可以改变里面的一个变量的值 用
[BJ setValue:aaa forKey:@"bbb"];//用 aaa替换bbb
实战来袭
这里我们以给UITextView添加PlaceHolder为例
- 首先,我们查找
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);
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];
[propertyNameArray addObject:propertysNameCode];
}
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
)
仔细看一下上面的输出,会发现,并不是每一个属性都对应一个自己的实例变量,那这是为啥呢?下面大家看一个🌰:
我们创建一个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:"), @"我是acceptsEmoji");
bool acceptsEmoji = ((bool (*) (id, SEL)) (void *)objc_msgSend)(textView, sel_registerName("acceptsEmoji"));
NSLog(@"acceptsEmoji_%@",@(acceptsEmoji));