上篇介绍了Runtime类和对象的相关知识点,在4.5和4.6小节,也介绍了成员变量和属性的一些方法应用。本篇将讨论实现细节的相关内容。

在讨论之前,我们先来介绍一个很冷僻但又很有用的一个关键字:@encode

1.类型编码

为了协助运行时系统,编译器用字符串为每个方法的返回值、参数类型和方法选择器编码,使用的编码方案在其他情况下也很有用。在 Objective-C 运行时的消息发送机制中,传递参数时,由于类型信息的缺失,需要类型编码进行辅助以保证类型信息也能够被传递。在实际的应用开发中,使用案例比较少:某些 API 中 Apple 建议使用 NSValue 的 valueWithBytes:objCType: 来获取值 (比如 CIAffineClamp 的文档里) ,这时的 objCType就需要类型的编码值;另外就是在类型信息丢失时我们可能需要用到这个特性。在上篇的函数介绍过程中,有几个函数用到了类型编码,types参数需要用 Objective-C 的编译器指令 @encode() 来创建,@encode() 返回的是 Objective-C 类型编码,这是一种内部表示的字符串(例如,@encode(int)→ i),类似于 ANSI C 的 typeof 操作,苹果的 Objective-C 运行时库内部利用类型编码帮助加快消息分发

//添加方法
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp,
const char *types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//替代方法的实现
OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp,
const char *types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//添加成员变量
OBJC_EXPORT BOOL class_addIvar(Class cls, const char *name, size_t size,
uint8_t alignment, const char *types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//为协议添加方法
OBJC_EXPORT void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

那么常用的类型编码都有哪些呢?

    NSLog(@"基本数据类型:");
NSLog(@"short \t\t %s", @encode(short));
NSLog(@"int \t\t %s", @encode(int));
NSLog(@"long \t\t %s", @encode(long));
NSLog(@"ong long \t\t %s", @encode(long long));
NSLog(@"float \t\t %s", @encode(float));
NSLog(@"double \t\t %s", @encode(double));
NSLog(@"char \t\t %s", @encode(char)); NSLog(@"\n"); NSLog(@"指针和数组类型:");
NSLog(@"int * \t\t %s", @encode(int *));
NSLog(@"int ** \t\t %s", @encode(int **));
NSLog(@"int *** \t\t %s", @encode(int ***));
NSLog(@"int [] \t\t %s", @encode(int []));
NSLog(@"int [2] \t\t %s", @encode(int []));
NSLog(@"int [][3] \t\t %s", @encode(int [][]));
NSLog(@"int [3][3] \t\t %s", @encode(int [][]));
NSLog(@"int [][4][4] \t\t %s", @encode(int [][][]));
NSLog(@"int [4][4][4] \t\t %s", @encode(int [][][])); NSLog(@"\n"); NSLog(@"空类型:");
NSLog(@"void \t\t %s", @encode(void));
NSLog(@"void * \t\t %s", @encode(void *));
NSLog(@"void ** \t\t %s", @encode(void **));
NSLog(@"void *** \t\t %s", @encode(void ***)); NSLog(@"\n"); NSLog(@"结构体类型:");
struct Person {
char *anme;
int age;
char *birthday;
};
NSLog(@"struct Person \t\t %s", @encode(struct Person));
NSLog(@"CGPoint \t\t %s", @encode(CGPoint));
NSLog(@"CGRect \t\t %s", @encode(CGRect)); NSLog(@"\n"); NSLog(@"OC类型:");
NSLog(@"BOOL \t\t %s", @encode(BOOL));
NSLog(@"SEL \t\t %s", @encode(SEL));
NSLog(@"id \t\t %s", @encode(id));
NSLog(@"Class \t\t %s", @encode(Class));
NSLog(@"Class * \t\t %s", @encode(Class *));
NSLog(@"NSObject class \t\t %s", @encode(typeof([NSObject class])));
NSLog(@"[NSObject class] * \t\t %s", @encode(typeof([NSObject class]) *));
NSLog(@"NSObject \t\t %s", @encode(NSObject));
NSLog(@"NSObject * \t\t %s", @encode(NSObject *));
NSLog(@"NSArray \t\t %s", @encode(NSArray));
NSLog(@"NSArray * \t\t %s", @encode(NSArray *));
NSLog(@"NSMutableArray \t\t %s", @encode(NSMutableArray));
NSLog(@"NSMutableArray * \t\t %s", @encode(NSMutableArray *));
NSLog(@"UIView \t\t %s", @encode(UIView));
NSLog(@"UIView * \t\t %s", @encode(UIView *));
NSLog(@"UIImage \t\t %s", @encode(UIImage));
NSLog(@"UIImage * \t\t %s", @encode(UIImage *)); 

打印结果:

其他类型可参考Type Encoding,在此不细说。

对于属性而言,还会有一些特殊的类型编码,以表明属性是只读、拷贝、retain等等,详情可以参考Property Type String

2.成员变量

Ivar是表示实例变量的类型,其实际是指向objc_ivar结构体的指针,其定义如下:

typedef struct objc_ivar *Ivar;

struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE; //变量名
char *ivar_type OBJC2_UNAVAILABLE; //变量类型
int ivar_offset OBJC2_UNAVAILABLE; //基地址偏移字节
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}

对应的操作函数有如下几个:

//获取成员变量名
OBJC_EXPORT const char *ivar_getName(Ivar v)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//设置成员变量值
OBJC_EXPORT void object_setIvar(id obj, Ivar ivar, id value)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
//获取成员变量值
OBJC_EXPORT id object_getIvar(id obj, Ivar ivar)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
// 获取成员变量类型编码
OBJC_EXPORT const char *ivar_getTypeEncoding(Ivar v)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
// 获取成员变量的偏移量 对于类型id或其它对象类型的实例变量,可以调用object_getIvar和object_setIvar来直接访问成员变量,而不使用偏移量
OBJC_EXPORT ptrdiff_t ivar_getOffset(Ivar v)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

这几个方法在上篇中已经使用,这里就不提供示例了。

3.属性

objc_property_t是表示属性的类型,其实际是指向objc_property结构体的指针,其定义如下:

typedef struct objc_property *objc_property_t;

//来自objc-private.h
struct objc_property {
const char *name;
const char *attributes;
};

objc_property_attribute_t定义了属性的特性(attribute),它也是一个结构体,定义如下:

typedef struct {
const char *name; //特性名
const char *value; //特性值
} objc_property_attribute_t;

对应的操作函数有如下几个:

//获取属性名
OBJC_EXPORT const char *property_getName(objc_property_t property)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
// 获取属性特性描述字符串
OBJC_EXPORT const char *property_getAttributes(objc_property_t property)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
// 获取属性中指定的特性
OBJC_EXPORT char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
// 获取属性的特性列表
OBJC_EXPORT objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

这几个方法在上篇中已经使用,这里就不提供示例了。

4.关联对象

我们知道,在 Objective-C 中可以通过 Category 给一个现有的类添加属性,但是却不能添加实例变量。Objective-C针对这一问题,提供了一个解决方案:即关联对象。

关联对象类似于成员变量,不过是在运行时添加的。我们通常会把成员变量(Ivar)放在类声明的头文件中,或者放在类实现的@implementation后面。但这有一个缺点,我们不能再分类中添加成员变量。如果我们尝试在分类中添加新的成员变量,编译器会报错。

与关联对象相关的函数有如下三个:

//给对象添加关联对象,传入 nil 则可以移除已有的关联对象
OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
//获取关联对象
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);
//移除一个对象的所有关联对象
OBJC_EXPORT void objc_removeAssociatedObjects(id object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0);

【注意】:objc_removeAssociatedObjects 函数我们一般是用不上的,因为这个函数会移除一个对象的所有关联对象,将该对象恢复成“原始”状态。这样做就很有可能把别人添加的关联对象也一并移除,这并不是我们所希望的。所以一般的做法是通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。

在给一个对象添加关联对象时有五种关联策略可供选择:

关联策略 等价属性 说明
OBJC_ASSOCIATION_ASSIGN

@property (assign)

@property (unsafe_unretained)

弱引用关联对象
OBJC_ASSOCIATION_RETAIN_NONATOMIC @property (strong, nonatomic) 强引用关联对象,非原子操作
OBJC_ASSOCIATION_COPY_NONATOMIC @property (copy, nonatomic) 复制关联对象,非原子操作
OBJC_ASSOCIATION_RETAIN @property (strong, atomic) 强引用关联对象,原子操作
OBJC_ASSOCIATION_COPY @property (copy, atomic) 复制关联对象,原子操作

5.应用讲解

5.1归档解档

首先我们先来思考一下我们常规的归档解档方案:

//第一步:实现协议NSCoding
@interface GofUser : NSObject<NSCoding> @property (nonatomic, strong) NSString *name; //!<姓名
@property (nonatomic, strong) NSString *phone; //!<电话 @end @implementation GofUser
//第二步:实现协议的两个方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
     //需要解码的属性
self.name = [aDecoder decodeObjectForKey:@"name"];
self.phone = [aDecoder decodeObjectForKey:@"phone"];
}
return self;
} - (void)encodeWithCoder:(NSCoder *)aCoder {
//需要编码的属性
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeObject:self.phone forKey:@"phone"];
} @end //第三步:归档
GofUser *user = [[GofUser alloc] init];
user.name = @"LeeGof";
user.phone = @""; [NSKeyedArchiver archiveRootObject:user toFile:[GofUser cacheMetadataFilePath]]; //第四步:解档
GofUser *user1 = [NSKeyedUnarchiver unarchiveObjectWithFile:[GofUser cacheMetadataFilePath]];
NSLog(@"name : %@ phone : %@", user1.name, user1.phone);

这里的GofUser类只有两个属性,如果要归档的类有100个属性怎么办?难道在NSCoding协议的两个方法各写100次吗?答案是否定的,我们可以用runtime的成员变量来实现。

//核心代码
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int count = ;
Ivar *ivars = class_copyIvarList([self class], &count); for (int i = ; i < count; i++) {
//取出成员变量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
//获取KEY
NSString *key = [NSString stringWithUTF8String:name];
//解档
id value = [aDecoder decodeObjectForKey:key];
//KVC赋值
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
} - (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = ;
Ivar *ivars = class_copyIvarList([self class], &count); for (int i = ; i < count; i++) {
//取出成员变量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
//获取KEY
NSString *key = [NSString stringWithUTF8String:name];
//归档
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
free(ivars);
}

【思考】:使用这种方式,成员变量能否正常的归档和解档?

5.2关联对象

假设现在有这么一个应用场景:需要动态的给一个GofPerson类添加属性workSpace。

@interface GofPerson (GofWork)

@property (nonatomic, strong) NSString *workSpace;  //!<工作空间

@end

static const void *s_WorkSpace = "s_WorkSpace";
@implementation GofPerson (GofWork) - (void)setWorkSpace:(NSString *)workSpace {
objc_setAssociatedObject(self, s_WorkSpace, workSpace, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} - (NSString *)workSpace {
return objc_getAssociatedObject(self, s_WorkSpace);
} @end

6.小结

本篇讨论了Runtime中成员变量、属性、关联对象相关的内容。成员变量与属性是类的数据基础,合理地使用Runtime中的相关操作能让我们更加灵活地来处理与类数据相关的工作。

Runtime之成员变量&属性&关联对象的更多相关文章

  1. static成员变量与返回对象的引用

    (1)用static修饰类成员变量(属性),表明该变量是静态的,无论创建多少对象,都只创建一个一个静态属性副本,也就是对象们共享同一个静态属性,这个方法常用的一个用途就是用来计算程序调用了多少次这个类 ...

  2. iOS开发--Runtime的简单使用之关联对象

    一.Runtime关联对象的方法简介: 在<objc/runtime.h>中,有三个关联的方法,分别是: objc_setAssociatedObject objc_getAssociat ...

  3. C++ 虚指针、成员变量与类对象的偏移地址

    先给出一段代码实现 #include <iostream> using namespace std; class animal { protected: int age; public: ...

  4. php 对象赋值后改变成员变量影响赋值对象

    话不多说看代码 打印结果 对obj1的操作 直接影响了obj2 , 对obj2的操作 直接影响了obj1

  5. Objective-C Runtime 运行时之二:成员变量与属性(转载)

    在前面一篇文章中,我们介绍了Runtime中与类和对象相关的内容,从这章开始,我们将讨论类实现细节相关的内容,主要包括类中成员变量,属性,方法,协议与分类的实现. 本章的主要内容将聚集在Runtime ...

  6. 刨根问底Objective-C Runtime(4)- 成员变量与属性

    http://chun.tips/blog/2014/11/08/bao-gen-wen-di-objective[nil]c-runtime(4)[nil]-cheng-yuan-bian-lian ...

  7. java类(Class)的概念;对象的概念,声明类的属性 和方法,局部变量和成员变量,面向对象编程思维,抽象的概念

    类(Class)的概念 类是对一组具有相同特征和行为的对象的抽象描述. 理解: [1] 类包含了两个要素:特性和行为 => 同一类事物具有相同的特征和行为. [2] 类是一个群体性概念.例如:网 ...

  8. runtime-对成员变量和属性的操作

    成员变量 首先我们来看看成员变量在runtime中是什么样的 在runtime中成员变量是一个objc_ivar类型的结构体,结构体定义如下 struct objc_ivar { char *ivar ...

  9. Java中字段、属性、成员变量、局部变量、实例变量、静态变量、类变量、常量

    首先看个例子: package zm.demo; public class Demo { private int Id;//成员变量(字段).实例变量(表示该Id变量既属于成员变量又属于实例变量) p ...

随机推荐

  1. SpringMVC 请求全过程漫谈

    SpringMVC 请求全过程漫谈 SpringMVC 跟其他的mvc框架一样,如 struts,webwork, 本质上都是 将一个 http 请求(request)进行各种处理, 然后返回resp ...

  2. Git 版本管理使用说明。

    1.回滚: git log :查看log日志 commit_id:  git reset –-soft <commit_id>:回退到某个版本,只回退了commit的信息,不会恢复到ind ...

  3. python3 不知文件编码情况下打开文件代码记录

    import chardet path='test.txt' bytes = min(100, os.path.getsize(path)) raw = open(path, 'rb').read(b ...

  4. python3 列表去除重复项保留原序

    l1 = ['a',1,'c','b',2,'b','c','d','a'] l2= sorted(set(l1),key=l1.index) print('l2:',l2) print('l1:', ...

  5. python3中一句话定义函数

    import math as marea=lambda r:r**2*m.pi #定义一个计算圆的面积的函数area(8) 显示结果 201.06192982974676

  6. python学习笔记(三)- 字典、集合

    字典:key-value形式 1)取数据方便   #字典里面没有重复的key 2)查询速度快 #字典是无序的 一.定义一个字典 infos = { 'name':'王小明', 'sex':'male' ...

  7. Tomcat常用设置及安全管理规范

    前言 随着公司内部使用Tomcat作为web应用服务器的规模越来越大,为保证Tomcat的配置安全,防止信息泄露,恶性攻击以及配置的安全规范,特制定此Tomcat安全配置规范.注意:  本文章从htt ...

  8. 使用 Composer 安装Laravel扩展包的几种方法

    使用 Composer 安装Laravel扩展包的几种方法 以下的三种方法都是需要你在项目的根目录运行 第一种:composer install 如有 composer.lock 文件,直接安装,否则 ...

  9. 你确定你真的懂Nginx与PHP的交互?

    Nginx是俄国人最早开发的Webserver,现在已经风靡全球,相信大家并不陌生.PHP也通过二十多年的发展来到了7系列版本,更加关注性能.这对搭档在最近这些年,叱咤风云,基本上LNMP成了当下的标 ...

  10. 【转载】Windows上那些值得推荐的良心软件-整理 easybcd 引导工具 easyuefi 引导工具

    您查询的关键词是:清理dism知乎 以下是该网页在北京时间 2019年03月17日 21:56:16 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. 百度和网页 htt ...