iOS Runtime《五》objc_property or objc_property_t (属性)

 

      属性应该是我们最熟悉的了,相当于给实例变量加了修饰符,自动生成setget方法,用起来很方便。 runtime里面关于属性的结构体是objc_property或者objc_property_t

      objc_property_t是系统文件~objc/runtime.h下的一个结构体,功能是将两个对象的所有属性动态的取出,并绑定值,关于objc_property_t的常用例子就是给Model的属性赋值,与KVC赋值有着相同的作用,一般来说掌握了KVC方式就足够了,但是有些开发者会在代码中使用objc_property_t,因此大家必须要能看懂这样的代码。此外这是一项程序员装逼技能,了解一下还是有必要的。

一、objc_property_attribute_t 来间接获得关于属性的一些信息。 而这个方法property_copyAttributeList方法就是通过传入objc_property_t来获得objc_property_attribute_t

// 获取 1 objc_property_t
OBJC_EXPORT objc_property_t _Nullable
class_getProperty(Class _Nullable cls, const char * _Nonnull name)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//2. objc_property_attribute_t 列表
OBJC_EXPORT objc_property_attribute_t * _Nullable
property_copyAttributeList(objc_property_t _Nonnull property,
                           unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

//3、获取  objc_property_attribute_t  name  和value 
typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;


例:Cat类有个name属性

 

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Cat : NSObject
{
    int _age;
}

@property (nonatomic,copy)NSString *name;

@property (nonatomic,assign)BOOL sex;
@end

NS_ASSUME_NONNULL_END


 // 1、获取nameProperty 的objc_property_t
 objc_property_t nameProperty=class_getProperty(objc_getClass("Cat"), "name");
    
    unsigned  int count=0;
    //2、获取属性列表
    objc_property_attribute_t *attributeList=property_copyAttributeList(nameProperty, &count);
    
    for (int i=0; i<count; i++) {
        
         //3、获取属性
        objc_property_attribute_t t= attributeList[i];
        
        NSLog(@"name=%s value=%s",t.name,t.value);
        
    }

//打印结果 
2019-04-08 16:37:59.939214+0800 property[1772:232480] name=T value=@"NSString"
2019-04-08 16:37:59.939397+0800 property[1772:232480] name=C value=
2019-04-08 16:37:59.939482+0800 property[1772:232480] name=N value=
2019-04-08 16:37:59.939564+0800 property[1772:232480] name=V value=_name

我们可以看到有value有值的是nameTV,T代表type,属性的类型,V代表ivar,代表属性的ivar的是_name。其他没有值的代表,那些修饰符,C代表copyN代表nonatomic

二、获取一个类的属性列表。

 1、获取一个类的所有属性 

//获取类中所有的属性列表
OBJC_EXPORT objc_property_t _Nonnull * _Nullable
class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

2、获取属性的名称

//获取属性的名称
OBJC_EXPORT const char * _Nonnull
property_getName(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

3、获取属性的类型

//方法一:获取 属性的类型,返回的是字符串
OBJC_EXPORT const char * _Nullable
property_getAttributes(objc_property_t _Nonnull property) 
    OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//方法二:获取指定类型的类型,attributeName 类型的名字

OBJC_EXPORT char * _Nullable
property_copyAttributeValue(objc_property_t _Nonnull property,
                            const char * _Nonnull attributeName)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

例:

NS_ASSUME_NONNULL_BEGIN

@interface Dog : NSObject


@property (nonatomic,copy)NSString *name;

@property (nonatomic,assign)NSInteger age;


@end

NS_ASSUME_NONNULL_END

 unsigned int count=0;
    objc_property_t *list = class_copyPropertyList(objc_getClass("Dog"), &count);
    
    for (int i=0; i<count; i++) {
        
        objc_property_t t=list[i];
        
        NSLog(@"name=%s att=%s",property_getName(t),property_getAttributes(t));
        NSLog(@"T=%s",property_copyAttributeValue(t, "T"));
        
    }

2019-04-08 23:11:25.911994+0800 aaaa[1178:164305] name=name att=T@"NSString",C,N,V_name
2019-04-08 23:11:25.912185+0800 aaaa[1178:164305] T=@"NSString"
2019-04-08 23:11:25.912296+0800 aaaa[1178:164305] name=age att=Tq,N,V_age
2019-04-08 23:11:25.912401+0800 aaaa[1178:164305] T=q

属性名称,这里要和ivar区分一下,如果通过已知属性去找ivar,那么找到的是带有下划线的

三、动态添加属性

//添加属性
OBJC_EXPORT BOOL
class_addProperty(Class _Nullable cls, const char * _Nonnull name,
                  const objc_property_attribute_t * _Nullable attributes,
                  unsigned int attributeCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

//替换属性
OBJC_EXPORT void
class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,
                      const objc_property_attribute_t * _Nullable attributes,
                      unsigned int attributeCount)
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0, 2.0);

1、以Dog为类,增加一个属性,

增加一个@property(nonatomic, copy,readonly)NSString* mood形式的属性。 传参需要传objc_property_attribute_t的列表,分析一下,TV是必有的,TvalueNSStringVvalue_mood,然后nonatomic代表有Ncopy代表有Creadonly代表有R,所以我们可以获知attributeT,V,C,N,

例:

#pragma mark- 添加属性
-(void)addProperty
{
    unsigned int count=5;
    
    objc_property_attribute_t attribute[count];
    
    //添加类型
    objc_property_attribute_t t1;
    t1.name="T";
    t1.value="NSString";
    
    //添加ivar
    objc_property_attribute_t t2;
    t2.name="V";
    t2.value="_mood";
    
    //添加  nonatomic
    objc_property_attribute_t t3;
    t3.name="N";
    t3.value="";
    
    //添加  copy
    objc_property_attribute_t t4;
    t4.name="C";
    t4.value="";
    
    //添加 readonly
    objc_property_attribute_t t5;
    t5.name="R";
    t5.value="";
    
    attribute[0]=t1;
    attribute[1]=t2;
    attribute[2]=t3;
    attribute[3]=t4;
    attribute[4]=t5;
    
    bool isSuccess=class_addProperty(objc_getClass("Dog"), "mood", attribute, count);
    
    NSLog(@"添加%@",isSuccess?@"成功":@"失败");
    
    //打印类的所有的属性
    unsigned int count1=0;
    objc_property_t *list=class_copyPropertyList(objc_getClass("Dog"), &count1);
    
    for (int i=0; i<count1; i++) {
        
        objc_property_t t=list[i];
        NSLog(@"name=%s",property_getName(t));
        NSLog(@"att=%s",property_getAttributes(t));
        
        
    }
    
}

2、修改属性

我打算把name这个属性的属性名改成cName。 同样我们还是先分析下objc_property_attribute_t的列表,name的属性是@property(nonatomic, copy)NSString* name,只改变名字的话,T,C,N都不变,变得是VVvalue变成_cName

例:

//修改属性
-(void)replaceProperty
{
    unsigned int count=4;
    objc_property_attribute_t  attribute[count];
    
    objc_property_attribute_t t1;
    t1.name="T";
    t1.value="NSString";
    
    objc_property_attribute_t t2;
    t2.name="V";
    t2.value="_cName";
    
    objc_property_attribute_t t3;
    t3.name="N";
    t3.value="";
    
    objc_property_attribute_t t4;
    t4.name="C";
    t4.value="";
    
    attribute[0]=t1;
    attribute[1]=t2;
    attribute[2]=t3;
    attribute[3]=t4;
    
    class_replaceProperty(objc_getClass("Dog"), "name", (const objc_property_attribute_t*)&attribute, count);
    
    objc_property_t property=class_getProperty(objc_getClass("Dog"), "name");
    
    NSLog(@"%s",property_getName(property));
    NSLog(@"%s",property_getAttributes(property));
    
    
}
//打印
2019-04-09 15:14:29.639955+0800 aaaa[3037:480180] name
2019-04-09 15:14:29.640175+0800 aaaa[3037:480180] TNSString,V_cName,N,C

打印结果完全出乎我的意料,打印出来的属性完全没有cName,但是打印attributes却是改变的attributes。为什么呢?我们要从源码看起来了:

struct property_t {
    const char *name;
    const char *attributes;
};
BOOL 
class_addProperty(Class cls, const char *name, 
                  const objc_property_attribute_t *attrs, unsigned int n)
{
    return _class_addProperty(cls, name, attrs, n, NO);
}

void 
class_replaceProperty(Class cls, const char *name, 
                      const objc_property_attribute_t *attrs, unsigned int n)
{
    _class_addProperty(cls, name, attrs, n, YES);
}

class_addPropertyclass_replaceProperty的底层都调用了_class_addProperty方法,只是里面的布尔值传的不一样。我们再看下_class_addProperty这个方法,

static bool 
_class_addProperty(Class cls, const char *name, 
                   const objc_property_attribute_t *attrs, unsigned int count, 
                   bool replace)
{
    if (!cls) return NO;
    if (!name) return NO;

    property_t *prop = class_getProperty(cls, name);
    if (prop  &&  !replace) {
        // already exists, refuse to replace
        return NO;
    } 
    else if (prop) {
        // replace existing
        rwlock_writer_t lock(runtimeLock);
        try_free(prop->attributes);
        prop->attributes = copyPropertyAttributeString(attrs, count);
        return YES;
    }
    else {
        rwlock_writer_t lock(runtimeLock);
        
        assert(cls->isRealized());
        
        property_list_t *proplist = (property_list_t *)
            malloc(sizeof(*proplist));
        proplist->count = 1;
        proplist->entsizeAndFlags = sizeof(proplist->first);
        proplist->first.name = strdupIfMutable(name);
        proplist->first.attributes = copyPropertyAttributeString(attrs, count);
        
        cls->data()->properties.attachLists(&proplist, 1);
        
        return YES;
    }
}

里面这一句property_t *prop = class_getProperty(cls, name);是取出要替换的属性,接着后面就是一系列判断,因为prop存在,并且replaceYES,所以会走到下面这一段:

else if (prop) {
        // replace existing
        rwlock_writer_t lock(runtimeLock);
        try_free(prop->attributes);
        prop->attributes = copyPropertyAttributeString(attrs, count);
        return YES;
    }


从这一段我们可以看到这一部分只改变了 prop->attributes。也没有改变 prop->name。所以,我们打印属性的name自然没有改变。那么,class_replaceProperty的用途最好是修改类型或者修饰符。`

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值