美文网首页iOS模块详解编程知识点杂的文
Runtime奇技淫巧之class_copyIvarList c

Runtime奇技淫巧之class_copyIvarList c

作者: 穿山甲救蛇精 | 来源:发表于2017-05-17 22:50 被阅读837次

    今天我们来介绍这两个神奇的方法,它们可以在一定程度上改变你对于系统控件的认识,也提供了你深入了解系统控件的一个小窗口,在开发中可能会带来你意想不到的惊喜,但是风险性同样也不是一般的大。

    我们之前解析了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实用技巧(不扯淡,不套路)

    相关文章

      网友评论

      • Mr卿:if (completed) completed(ivarNameArray); 差点没看懂 能不能把括号打上啊
      • Mr卿:好文!伍丽娟是谁?
      • 火星人巴莱:class_copyIvarList, class_copyPropertyList 需要配合 free 使用, 这个得提一下.
        Mr卿:还有 class_copyPropertyList 获取的是用 @property修饰的属性, class_copyIvarList 获取的是成员变量
      • HAORAN_Z:伍丽娟就是你把
        穿山甲救蛇精:你开心就好
      • 你只是个Baby:隔壁班的漂亮女同学
      • eed697919a45:不错,已收藏,期待伍丽娟!:joy:

      本文标题:Runtime奇技淫巧之class_copyIvarList c

      本文链接:https://www.haomeiwen.com/subject/feebtxtx.html