class_copyIvarList方法获取实例变量问题引发的思考

在runtime.h中,你可以通过其中的class_copyIvarList方法来获取实例变量。具体的实现如下(记得导入头文件<objc/runtime.h>):

- (NSArray *)ivarArray:(Class)cls {
    unsigned int stuIvarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &stuIvarCount);
    if (stuIvarCount == 0) {
        return nil;
    }
    NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:stuIvarCount];
    for (int i = 0;i<stuIvarCount;i++) {
        Ivar ivar = ivars[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
        NSLog(@"%@",ivarName);
        [arr addObject:ivarName];
    }
    free(ivars);
    return arr;
}

如上面代码。其中cls就是你要获取实例变量的类,stuIvarCount用来承载要获取类的实例变量的个数。打印出来的ivarName就是cls的实例变量。接下来对这个方法进行解析:

首先看一下里面的Ivar,先看一下定义:

/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;  //变量名字
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;   //变量类型
    int ivar_offset                                          OBJC2_UNAVAILABLE; //偏移量
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;  //存储空间
#endif
}    

Ivar是一个叫做objc_ivar的结构体指针,其中的 ifdef判断是判断当前设备是否是64位设备,这里可以延伸出一个方法:

//判断当前设备是否是64位设备,也可以用这个方法判断是否是32位设备
- (BOOL)is64Bit {
#if defined(__LP64__) && __LP64__
    return YES;
#else
    return NO;
#endif
}
OBJC_EXPORT Ivar _Nonnull * _Nullable
class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

class_copyIvarList的注释如下:

它返回的是一个Ivar的数组,这个数组里面包含了你要查看类的所有实例变量,但是不包括从父类继承过来的。如果你传入的类没有实例变量或者改class为Nil,那么该方法返回的就是NULL,count值也就变成了0。有一点需要注意:你必须使用free()方法将该数组释放

然后就是通过for循环遍历,通过ivar _ getName拿到ivarName。

以上便是对clas_copyIvarList的介绍。

它还有一个最常用的使用方式(在开发中经常用到的):根据字典或者json字符串转化为model,在网络请求返回数据时经常用到。使用方法就是自己写一个基类的model,然后让项目中用到的model都继承自此基类,基类中的关键代码如下:

+ (instancetype)zg_modelFromDic:(NSDictionary *)dataDic {
    id model = [[self alloc] init];
    unsigned int count = 0;

    Ivar *ivarsA = class_copyIvarList(self, &count);
    if (count == 0) {
        return model;
    }
    for (int i = 0;i < count; i++) {
        Ivar iv = ivarsA[i];
        NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(iv)];
        ivarName = [ivarName substringFromIndex:1];
        id value = dataDic[ivarName];
        [model setValue:value forKey:ivarName];
    }
    free(ivarsA);
    return model;
}

这里是把字典转成model,先用class_copyIvar获取该model的所有实例变量,然后通过kvc对属性进行赋值。最终返回model。这里有个点需要注意以下几点:

  1. 你的model的属性名称要和服务端返回的数据一致,比如你的model有个属性叫做name,那么你服务端返回的数据字典里面的对应属性也要叫做name,因为这个方法是根据属性从字典里面拿数据的。你也可以做一个映射,让自定义的实例变量名称映射到服务端提供的变量名称。
  2. 实现里面有个substringFromIndex:操作,其目的就是把使用该方法拿到的实例变量前面的" _ "去掉。所以你最好使用 @property 进行属性声明,并且不要去修改自动生成的实例变量。(@property = getter + setter + _ ivar,这里的 _ ivar其实就是编译器帮我们生成的实例变量)

接下来你可以尝试去获取UILabel的实例变量列表:

[self ivarArray:[UILabel class]]

你会发现拿到的结果是这样的:

(
    "_size",
    "_highlightedColor",
    "_numberOfLines",
    "_measuredNumberOfLines",
    "_baselineReferenceBounds",
    "_lastLineBaseline",
    "_previousBaselineOffsetFromBottom",
    "_firstLineBaseline",
    "_previousFirstLineBaseline",
    "_minimumScaleFactor",
    "_content",
    "_synthesizedAttributedText",
    "_defaultAttributes",
    "_fallbackColorsForUserInterfaceStyle",
    "_minimumFontSize",
    "_lineSpacing",
    "_layout",
    "_scaledMetrics",
    "_cachedIntrinsicContentSize",
    "_contentsFormat",
    "_cuiCatalog",
    "_cuiStyleEffectConfiguration",
    "_textLabelFlags",
    "_adjustsFontForContentSizeCategory",
    "__textColorFollowsTintColor",
    "_preferredMaxLayoutWidth",
    "_multilineContextWidth",
    "__visualStyle"
)

但是跳转到UILabel.h,你会发现里面有好多的属性不包含在我们根据该方法得出的属性数组里面,而且使用该方法得到的属性在UILabel.h里面并没有。这个是什么原因呢?

先看一下好多UILabel里面的属性没有在数组里面打印问题:猜想应该是在UILabel.m里面使用了 @dynamic。导致没有自动生成getter、setter和ivar,所以没有在数组里面包含。

@synthsize:如果没有手动实现setter/getter方法那么会自动生成,自动生成_var变量。如果不写,默认生成getter/setter和_var。你也可以使用该关键字自己设置自动变量的名称。

@dynamic告诉编译器:属性的setter/getter需要用户自己实现,不自动生成,而且也不会产生_var变量。

也就是说在UILabel里面虽然有个text的属性,也许在UILabel.m里面已经包含:

@dynamic text;

这样的话在实现里面没有产生实例变量,只是手动实现了getter和setter,所以就不会显示text属性在刚才得到的数组里面了。

至于数组中有UILabel.h里面没有的变量,这个就好理解了,有可能在UILabel.m里面添加了一些实例变量或者在运行时添加了这些实例变量。

除此方法之外,你还可以使用class_copyPropertyList方法,这个是拿到的所有用 @property 声明的属性,包括在.m里面添加的属性(所以打印出来的可能要比真实在.h里面看到的多),具体实现和上面的获取方法类似:

- (NSArray *)propertyArr:(Class)cls {
    unsigned count = 0;
    objc_property_t *properties = class_copyPropertyList(cls, &count);
    if (count == 0) {
        return nil;
    }
    NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:count];
    for (int i = 0; i < count; i ++) {
        objc_property_t property = properties[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)] ;
        [arr addObject:propertyName];
    }
    free(properties);
    return arr;
}

其中的copyPropertyList方法解释如下:

记得使用过后也要调用free去释放数组。(PS:在源代码中暂未找到objc_property结构体的说明)因此,你可以通过使用该方法来实现字典或者json字符串转model操作:

+ (instancetype)zg_modelFromDic:(NSDictionary *)dataDic {
    id model = [[self alloc] init];
    unsigned int count = 0;

    objc_property_t *properties = class_copyPropertyList([self class], &count);
    if (count == 0) {
        return model;
    }
    for (int i = 0;i < count; i++) {
        objc_property_t property = properties[i];
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        id value = dataDic[propertyName];
        [model setValue:value forKey:propertyName];
    }
    free(properties);
    return model;
}

两种方式均可实现model转换操作。

以上便是由class_copyIvarList所引发的思考。

转载请标明来源:http://www.cnblogs.com/zhanggui/p/8177400.html

原文地址:https://www.cnblogs.com/zhanggui/p/8177400.html

时间: 2024-10-08 02:45:17

class_copyIvarList方法获取实例变量问题引发的思考的相关文章

对象的行为——方法操作实例变量

对象有:[状态] 和 [行为] 两种属性:分别由[实例变量]和[方法]类表示. 类所描述的是[对象知道什么]:对象所知者 就是实例变量 [对象执行什么]:对象所为者 就是方法 类的每个实例(也就是特定类型 的 每个对象 ),可以维持自己的实例变量. 状态影响行为<===>行为影响状态: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 调用参数的时候 实参的传递:形参的接收: //Java是通过值传递的,也就是通过拷贝传递(不论传递的是对象还是主数据类型的变量), //方

关于php的getenv()方法获取环境变量的问题

php文件的执行 我们比较常用的执行php文件方式有两种 一种是通过浏览器访问服务器(比如apache),然后由服务器来运行相应的php文件 另一种就是通过shell直接运行php命令或者在shell脚本文件中运行php命令的方式来执行php文件 直接运行php命令 php /var/www/borg/oil refine test 在shell脚本文件中运行php命令 bash /var/www/borg/fuel/app/tasks/test.sh test.sh #!/bin/bash e

第二讲、实例变量可见度、方法

一.实例变量可见度 可见度         特点 public          实例变量可以在类的外部和内部操作 protected    实例变量只能在该类和其子类内操作     默认 private        实例变量只能在该类内访问 内部:相应类的@implementation和@end之间 @interface Person : NSObject { @public NSString*_name; @protected NSString*_gender; int_age; //年龄

OC中实例变量可见度、setter、getter方法和自定义初始化方法

在对类和对象有一定了解之后,我们进一步探讨实例变量的可见度等相关知识 实例变量的可见度分为三种情况:public(共有),protected(受保护的,默认),private(私有的),具体的不同和特点如下: 具体的使用示例如下: 编程时默认的可见度是@protectde,为什么不使用@public呢? 因为OC是面向对象编程,使用@public关键字,暴漏了类内部的细节,从而不符合面向对象语言的三大特性之一——封装 实例变量经过@protectde修饰之后,我们就不能在该类和其子类外操作,例如

OC实例变量初始化方法

OC实例变量初始化方法1. 使用实例setter方法 默认初始化方法 + setName:xxx setAge:xxx2. 使用实例功能类方法,默认初始化方法 + setName:xxx age:xxx3.使用实例初始化方法 initWith开头的方法4.使用构造器 类名+With…以Person  Student两个类为例1. 使用实例 setter方法这是最麻烦的方法 @interface Person : NSObject { NSString *_name; NSString *_sex

JNI/NDK开发指南(七)——C/C++访问Java实例变量和静态变量

转载请注明出处:http://blog.csdn.net/xyang81/article/details/42836783 在上一章中我们学习到了如何在本地代码中访问任意Java类中的静态方法和实例方法,本章我们也通过一个示例来学习Java中的实例变量和静态变量,在本地代码中如何来访问和修改.静态变量也称为类变量(属性),在所有实例对象中共享同一份数据,可以直接通过[类名.变量名]来访问.实例变量也称为成员变量(属性),每个实例都拥有一份实例变量数据的拷贝,它们之间修改后的数据互不影响.下面看一

JAVA类与对象---实例变量与类变量的区别,实例方法和类方法的区别

实例变量 实例变量声明在一个类中,但在方法.构造方法和语句块之外: 当一个对象被实例化之后,每个实例变量的值就跟着确定: 实例变量在对象创建的时候创建,在对象被销毁的时候销毁: 实例变量的值应该至少被一个方法.构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息: 实例变量对于类中的方法.构造方法或者语句块是可见的.一般情况下应该把实例变量设为私有.通过使用访问修饰符可以使实例变量对子类可见 实例变量具有默认值.数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默

iOS开发中 类、对象、实例变量、成员变量、属性变量等区别和关系

类(class):具有相同属性和行为等同一类元素等总称,类是一个抽象的概念. 区分是类还是对象,看它能否继续被细分. 在OC中,类是表示对象类型的结构体,对象通过类来获取自身的各种信息.类由两个部分组成:*.h和*.m文件组成. *.m文件中 implemention部分是类的实现部分,内部包含类中的各种信息,包括各种实例方法或类方法. 类别( category):是为现有的类添加新方法的方式,通常以"类名称+类别名称"来命名. 类别中不能添加新的实例变量.但是可以在类别中添加属性.

属性存取、直接访问实例变量

属性的读取采用点语法,访问对应的set和get方法.而直接访问是直接访问的对象实例的内存.这两者有什么区别?在什么情况应该使用哪种方法呢? 一.区别 直接访问实例变量有如下几种特质. 1.不经过Objective-C的方法派发,直接访问实例变量的内存,速度快. 2.由于没调用set方法,所以绕过了属性定义时声明的"内存管理语义",只会保留新值,释放旧值. 3.不会触发"键值观测"KVO机制. 4.无法通过给set和get打断点来进行调试. 二.使用场景 通用情况: