iOS开发的正确姿势iOS Developer - RuntimeiOS进阶指南

Objective-C Runtime: 变量和属性

2016-08-24  本文已影响99人  繁华落尽丶lee

本文只是整理Runtime中,成员变量、属性、关联对象、方法交换使用实例。不会很细致的讲解Runtime的内容,如果想了解Runtime更多内容,可以移步这里,查看大神们关于Runtime博文。

成员变量和属性

成员变量(Ivar)

定义

1、Ivar:实例变量类型, 其实是一个指向objc_ivar结构体的指针

typedef struct objc_ivar *Ivar;
操作方法
// 获取成员变量名字
const char * ivar_getName(Ivar v);
// 获取成员变量类型编码
const char * ivar_getTypeEncoding(Ivar v);
//获取成员变量的偏移量
ptrdiff_t ivar_getOffset(Ivar v);
//注:对于id类型或其他类型对象的实例变量,可以调用object_getIvar和object_setIvar直接访问成员变量,而不使用偏移量。
使用实例

User.h

@interface User : NSObject {
    NSString *_name;
}
@property NSString *sex;
@property (nonatomic, copy) NSDictionary *dict;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) double tall;
@property (nonatomic, assign) NSUInteger height;
- (void)getIvar;
- (void)getProperty;
@end

User.m

- (void)getIvar{
    unsigned int count = 0;
    //获取所有的成员变量
    Ivar *ivars = class_copyIvarList([User class], &count);
     for (unsigned i = 0; i < count;  i ++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar); //成员变量名称
        const char *type = ivar_getTypeEncoding(ivar); //成员变量类型
        NSLog(@"类型 %s 的 %s", type, name);
    }
    free(ivars); //手动释放
}

注: class_copyIvarList()方法,请移步在另一篇文章:Objective-C Runtime:类和对象

输出结果:

LearnRuntime[836:9888] 类型 @"NSString" 的 _name
LearnRuntime[836:9888] 类型 @"NSString" 的 _sex
LearnRuntime[836:9888] 类型 @"NSDictionary" 的 _dict
LearnRuntime[836:9888] 类型 q 的 _age
LearnRuntime[836:9888] 类型 d 的 _tall
LearnRuntime[836:9888] 类型 Q 的 _height

注:关于输出结果为什么是_name, 请移步《招聘一个靠谱的 iOS》—参考答案(上)- 14题

属性(property)

定义

1、objc_property_t:是表示Objective-C声明的属性的类型,其实是指向objc_property结构体的指针。

typedef struct objc_property *objc_property_t;

2、objc_property_attribute_t: 定义了属性的特性,结构体如下:

typedef struct {
    const char *name;           /**< The name of the attribute */
    const char *value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;
操作方法
// 获取属性名
const char * property_getName(objc_property_t property);
// 获取属性特性描述字符串
const char * property_getAttributes(objc_property_t property);
// 获取属性中指定的特性
char * property_copyAttributeValue ( objc_property_t property, const char *attributeName );
// 获取属性的特性列表
objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount );

注:property_copyAttributeValue和property_copyAttributeList, 返回值在使用后需要手动释放free();

使用实例

User.m 添加

- (void)getProperty {
    unsigned int count = 0;
    // 获取属性列表
    objc_property_t * properties = class_copyPropertyList([User class], &count);
     for (unsigned i = 0; i < count;  i ++) {
        objc_property_t property = properties[i];
        const char *name = property_getName(property);
        const char *propertyAttr = property_getAttributes(property);
        NSLog(@"属性描述为%s的%s", propertyAttr, name);
        unsigned int  attrCount = 0;
        //属性的特性列表
        objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
        for (unsigned i = 0; i < attrCount; i ++) {
            objc_property_attribute_t attr = attrs[i];
            const char *name = attr.name;
            const char *value = attr.value;
            NSLog(@"属性的特性描述:%s值:%s", name, value);
        }
        free(attrs);
        NSLog(@"\n");
    }
    free(properties);
}

输出结果

LearnRuntime[1190:51044] 属性描述为T@"NSString",&,V_sex的sex
LearnRuntime[1190:51044] 属性的特性描述:T值:@"NSString"
LearnRuntime[1190:51044] 属性的特性描述:&值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_sex
LearnRuntime[1190:51044] 
LearnRuntime[1190:51044] 属性描述为T@"NSDictionary",C,N,V_dict的dict
LearnRuntime[1190:51044] 属性的特性描述:T值:@"NSDictionary"
LearnRuntime[1190:51044] 属性的特性描述:C值:
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_dict
LearnRuntime[1190:51044] 
LearnRuntime[1190:51044] 属性描述为Tq,N,V_age的age
LearnRuntime[1190:51044] 属性的特性描述:T值:q
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_age
LearnRuntime[1190:51044] 
LearnRuntime[1190:51044] 属性描述为Td,N,V_tall的tall
LearnRuntime[1190:51044] 属性的特性描述:T值:d
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_tall
LearnRuntime[1190:51044] 
LearnRuntime[1190:51044] 属性描述为TQ,N,V_height的height
LearnRuntime[1190:51044] 属性的特性描述:T值:Q
LearnRuntime[1190:51044] 属性的特性描述:N值:
LearnRuntime[1190:51044] 属性的特性描述:V值:_height

注:objc_property_getAttribute_t结构体包含name和value,属性如下:

属性类型  name值:T                                       value:变化
编码类型  name值:C(copy) &(strong) W(weak) 空(assign) 等  value:无
非/原子性 name值:空(atomic) N(Nonatomic)                    value:无
变量名称  name值:V                                        value:变化

关联对象(Associated objects)

定义

Associated objects是Objective-C 2.0运行时一个特性。<objc/runtime.h>中定义三个允许将任何键值在运行时关联到对象上的函数:

objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects

使用三个函数,开发者可以对已经存在的类扩展中添加自定义的属性

删除属性

如果你尝试使用objc_removeAssociatedObjects()进行删除操作,但官方文档告诉我们不应该手动调用这个函数,通常使用objc_setAssocatedObject方法传入nil值清除关联。关于AssociatedObjects相关知识,请看NShipster上的文章Associated Objects

使用实例

NSObject+AssociatedObject.h

@interface NSObject (AssociatedObject)
@property (nonatomic, strong) id associatedObject;
@end

NSObject+AssociatedObject.m

@implementation NSObject (AssociatedObject)
@dynamic associatedObject;
- (void)setAssociatedObject:(id)object {
     objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
    return objc_getAssociatedObject(self, @selector(associatedObject));
}

方法交换(Method swizzling)

定义

Method Swizzling是改变selector的实际实现的技术,通过它可以在运行时通过修改类的分发表中selector对应的函数,来修改方法的实现。

操作方法

//获取实例方法
class_getInstanceMethod
//获取方法的实现
method_getImplementation
//获取实现的编码类型
method_getTypeEncoding
//给方法添加实现
class_addMethod
//用一个方法的实现替换另一个方法的实
class_replaceMethod
//交换两个方法的实现
method_exchangeImplementations

使用实例

将UIViewController中的ViewWillAppear:方法替换成自定义方法

#import <objc/runtime.h>
@implementation UIViewController (tracking)

/**
 *  理解: [self class] 和 object_getClass(self)区别和联系?
 */
 
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    
        Class class = [self class]; //注意:如果是类方法,则需要 Class class = object_getClass((id)self);
        
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        //注意: 在交换方法实现,需要判断原有方法实现是否存在,存在才能交换
        // 如何判断?添加原有方法,如果成功,表示原有方法不存在,失败,表示原有方法存在
        // 原有方法可能没有实现,所以这里添加方法实现,用自己方法实现
        // 这样做的好处:方法不存在,直接把自己方法的实现作为原有方法的实现,调用原有方法,就会来到当前方法的实现
        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
    });
}
/**
 *  添加自定义的方法
 *
 *  @param animated 
 */
- (void)xxx_viewWillAppear: (BOOL) animated {
    [self xxx_viewWillAppear:animated]; //这里不会造成死循环,为什么呢?
    NSLog(@"viewWillAppear: %@", self);
}
@end

使用method swizzling 修改UIViewController的@selector(viewWillAppear:)对应的函数指针,指向自定义的xxx_viewWillAppear:的实现。具体讲解请看Method Swizzling

小结

文章作为学习笔记,目的是方便以后查看。另外,本人能力有限,如有错误欢迎指正。

参考资料

这里整理关于Runtime经典博文。

上一篇下一篇

猜你喜欢

热点阅读