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阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容