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

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

- (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,先看一下定义:objective-c

/// 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位设备,这里能够延伸出一个方法:json

//判断当前设备是不是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的注释以下: 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的实例变量列表:ui

[self ivarArray:[UILabel class]]

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

(
    "_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,因此没有在数组里面包含。3d

@synthsize:若是没有手动实现setter/getter方法那么会自动生成,自动生成_var变量。若是不写,默认生成getter/setter和_var。你也可使用该关键字本身设置自动变量的名称。
@dynamic告诉编译器:属性的setter/getter须要用户本身实现,不自动生成,并且也不会产生_var变量。指针

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

@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方法解释以下: class_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

相关文章
相关标签/搜索