1.runtime体验
1.1.引言
运行时作为面试题中高阶问题必不可少的一个技能点。经常面试官会问道是否使用过运行时,以及如何使用运行时。并且我认为这个技术算是高阶开发里面一个投机的技巧,绝大多数的UI开发都不会使用runtime,容易出现很严重的问题,并且官方也不是特别推荐使用。所以会导致很多人对运行时没有一个清晰全面的认识,网上资料也很少。所以你一定会有这样的疑问:什么是运行时?在我们的项目中怎么使用运行时?哪些时候可以使用运行时?等等。。
问题:当想在一个分类(category)中添加一个属性时,并且iOS是不允许给扩展类扩展属性的(category相对于子类来说,如果需要添加一个新的变量,则需添加子类。如果只是添加一个新的方法,用Category是比较好的选择。)
答案:使用runtime。
1.2.runtime机制
我们都知道Objective-C是C的超集,我们平时写的Objective-C都是最终转成了runtime的C语言代码,所以runtime是一套比较底层的纯C的API,属于C语言库,包含了很多底层C语言的API。
所以,在我们编译时,其实runtime是不存在。只有在运行过程中才去确定对象的类型,方法等等。而我们就可以根据这个特性再程序运行时动态的修改类、对象中的所有属性、方法。
我们在使用运行时的地方,都需要包含头文件:#import 。如果是Swift就不需要包含头文件,就可以直接使用了。
1.3.runtime头文件
在iOS 9.3 user/include>objc文件夹下有如下的文件
message.h
NSObjCRuntime.h
NSObject.h
objc-api.h
objc-auto.h
objc-exception.h
objc-sync.h
objc.h
runtime.h
这些都是runtime相关的头文件,其中我们主要使用的是message.h和runtime.h这2个文件。
message.h:主要包含了一些向对象发送消息的函数,这是OC对象方法调用的底层实现。
runtime.h:运行时最重要的文件,其中包含了对运行时进行操作的方法。
1.3.1.runtime.h
/// An opaque type that represents a method in a class definition. 一个类型,代表着类定义中的一个方法
typedefstructobjc_method*Method;
/// An opaque type that represents an instance variable.代表实例(对象)的变量
typedefstructobjc_ivar*Ivar;
/// An opaque type that represents a category.代表一个分类
typedefstructobjc_category*Category;
/// An opaque type that represents an Objective-C declared property.代表OC声明的属性
typedefstructobjc_property*objc_property_t;
// Class代表一个类,它在objc.h中这样定义的 typedef struct objc_class *Class;
structobjc_class{
ClassisaOBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Classsuper_classOBJC2_UNAVAILABLE;
constchar*nameOBJC2_UNAVAILABLE;
longversionOBJC2_UNAVAILABLE;
longinfoOBJC2_UNAVAILABLE;
longinstance_sizeOBJC2_UNAVAILABLE;
structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;
structobjc_method_list**methodListsOBJC2_UNAVAILABLE;
structobjc_cache*cacheOBJC2_UNAVAILABLE;
structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;
#endif
}OBJC2_UNAVAILABLE;
这些类型的定义,对一个类进行了完全的分解,将类定义或者对象的每一个部分都抽象为一个类型type,对操作一个类属性和方法非常方便。OBJC2_UNAVAILABLE标记的属性是Ojective-C 2.0不支持的,但实际上可以用响应的函数获取这些属性,例如:如果想要获取Class的name属性,可以按如下方法获取:
ClassclassPerson=Person.class;
// printf("%s\n", classPerson->name);
//用这种方法已经不能获取name了 因为OBJC2_UNAVAILABLE
constchar*cname=class_getName(classPerson);
printf("%s",cname);// 输出:Person
/* 返回正在工作的Class */
/**
* Returns the name of a class.
* @param cls A class object.
* @return The name of the class, or the empty string if \e cls is \c Nil.
*/
// OBJC_EXPORT const char *class_getName(Class cls)
// __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
1.3.1.1.函数的定义
对对象进行操作的方法一般以object_开头
对类进行操作的方法一般以class_开头
对类或对象的方法进行操作的方法一般以method_开头
对成员变量进行操作的方法一般以ivar_开头
对属性进行操作的方法一般以property_开头开头
对协议进行操作的方法一般以protocol_开头
根据以上的函数的前缀 可以大致了解到层级关系。
对于以objc_开头的方法,则是runtime最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。
例如:使用runtime对当前的应用中加载的类进行打印。
-(void)touchesBegan:(NSSet*)toucheswithEvent:(UIEvent*)event{
unsignedintcount=0;
Class*classes=objc_copyClassList(&count);
for(inti=0;i
constchar*cname=class_getName(classes[i]);
printf("%s\n",cname);
}}
1.4.获取对象所有属性名
利用运行时获取对象的所有属性名是可以的,但是变量名获取就得用另外的方法了。我们可以通过class_copyPropertyList方法获取所有的属性名称。
下面我们通过一个Person类来学习,这里的方法没有写成扩展,只是为了简化,将获取属性名的方法直接作为类的实例方法:
#import
@interfacePerson: NSObject{
NSString*_variableString;
}
// 默认会是什么呢?
@property(nonatomic,copy)NSString*name;
// 默认是strong类型
@property(nonatomic,strong)NSMutableArray*array;
// 默认是assign类型
@property(nonatomic,assign)NSUIntegerage;
// 默认是assign类型
@property(nonatomic,assign)BOOLsex;
// 获取所有的属性名
-(NSArray*)allProperties;
@end
下面主要是写如何获取类的所有属性名的方法。
#import "Person.h"
#import
@implementationPerson
-(NSArray*)allProperties{
unsignedintcount;
// 获取类的所有属性
// 如果没有属性,则count为0,properties为nil
objc_property_t*properties=class_copyPropertyList([selfclass],&count);
NSMutableArray*propertiesArray=[NSMutableArrayarrayWithCapacity:count];
for(NSUIntegeri=0;i
// 获取属性名称
constchar*propertyName=property_getName(properties[i]);
NSString*name=[NSStringstringWithUTF8String:propertyName];
[propertiesArrayaddObject:name];
}
// 注意,这里properties是一个数组指针,是C的语法,
// 我们需要使用free函数来释放内存,否则会造成内存泄露
free(properties);
returnpropertiesArray;
}
@end
现在,我们来测试一下,我们的方法是否正确获取到了呢?看下面的打印结果就明白了吧!
-(void)test1_2{
Person*p=[[Personalloc]init];
p.name=@"BirdMichael";
size_tsize=class_getInstanceSize(p.class);
NSLog(@"classSize = %ld",size);
for(NSString*propertyNameinp.allProperties){
NSLog(@"%@",propertyName);
}}
输出结果:
2016-05-0517:27:47.667runtime[2368:257719]classSize=48
2016-05-0517:27:47.667runtime[2368:257719]name
2016-05-0517:27:47.668runtime[2368:257719]array
2016-05-0517:27:47.668runtime[2368:257719]age
2016-05-0517:27:47.668runtime[2368:257719]sex
注意:上面提到的objc_property_t是一个结构体指针objc_property *,因此我们声明的properties就是二维指针。所以在使用完毕以后,一定要释放内存,否则会造成内存泄露。并且由于runtime使用的是C语言的API,所以我们也需要使用C语言释放内存的方法:free。
/**
* Describes the properties declared by a class.
* @param cls The class you want to inspect.
* @param outCount On return, contains the length of the returned array.
* If \e outCount is \c NULL, the length is not returned.
* @return An array of pointers of type \c objc_property_t describing the properties
* declared by the class. Any properties declared by superclasses are not included.
* The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
* If \e cls declares no properties, or \e cls is \c Nil, returns \c NULL and \c *outCount is \c 0.
*/
OBJC_EXPORTobjc_property_t*class_copyPropertyList(Classcls,unsignedint*outCount)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
官方文档中也明确指出:The array contains \c *outCount pointers followed by a \c NULL terminator. You must free the array with \c free().
1.5.获取对象的所有属性名和属性值
对于获取对象的所有属性名,在上面的-allProperties方法已经可以拿到了,但是并没有处理获取属性值,下面的方法就是可以获取属性名和属性值,将属性名作为key,属性值作为value。
@interfacePerson:
NSObject{
NSString*_variableString;
}
// 默认会是什么呢?
@property(nonatomic,copy)NSString*name;
// 默认是assign类型
@property(nonatomic,copy)NSString*firtName;
// 默认是strong类型
@property(nonatomic,strong)NSMutableArray*array;
// 默认是assign类型
@property(nonatomic,assign)NSUIntegerage;
// 默认是assign类型
@property(nonatomic,assign)BOOLsex;
// 获取对象的所有属性名和属性值
-(NSDictionary*)allPropertyNamesAndValues;
@end
下面主要是写如何获取对象的所有属性名和属性值的方法。
-(NSDictionary*)allPropertyNamesAndValues{
NSMutableDictionary*resultDict=[NSMutableDictionarydictionary];
unsignedintoutCount;
objc_property_t*properties=class_copyPropertyList([selfclass],&outCount);
for(inti=0;i
objc_property_tproperty=properties[i];
constchar*name=property_getName(property);
// 得到属性名
NSString*propertyName=[NSStringstringWithUTF8String:name];
// 获取属性值
idpropertyValue=[selfvalueForKey:propertyName];
if(propertyValue&&propertyValue!=nil){
[resultDictsetObject:propertyValueforKey:propertyName];
}}
// 记得释放
free(properties);
returnresultDict;
}
继续测试一下:
-(void)test1_3{
Person*p=[[Personalloc]init];
p.name=@"BirdMichael";
p.age=8;
NSDictionary*dict=p.allPropertyNamesAndValues;
for(NSString*propertyNameindict.allKeys){
NSLog(@"propertyName:
%@ propertyValue: %@",propertyName,dict[propertyName]);}
}
输出结果:
2016-05-0518:27:10.345runtime[2618:313291]propertyName: namepropertyValue: BirdMichael
2016-05-0518:27:10.347runtime[2618:313291]propertyName: agepropertyValue:8
2016-05-0518:27:10.347runtime[2618:313291]propertyName: sexpropertyValue:0
1.6.获取对象的所有方法名
通过class_copyMethodList方法就可以获取所有的方法。并且我们知道,每一个属性都会自动生成一个成员变量和setter以及getter方法。(省略部分相同代码)
-(void)allMethods{
unsignedintoutCount=0;
Method*methods=class_copyMethodList([selfclass],&outCount);
for(inti=0;i
Methodmethod=methods[i];
// 获取方法名称,但是类型是一个SEL选择器类型
SELmethodSEL=method_getName(method);
// 需要获取C字符串
constchar*name=sel_getName(methodSEL);
// 将方法名转换成OC字符串
NSString*methodName=[NSStringstringWithUTF8String:name];
// 获取方法的参数列表
intarguments=method_getNumberOfArguments(method);
NSLog(@"方法名:%@,
参数个数:%d",methodName,arguments);
}
// 记得释放
free(methods);
}
测试一下:
-(void)test1_4{
Person*p=[[Personalloc]init];
[pallMethods];
}
输出结果:
2016-05-0518:35:39.886runtime[2709:323818]方法名:allProperties,参数个数:2
2016-05-0518:35:39.886runtime[2709:323818]方法名:setAge:,参数个数:3
2016-05-0518:35:39.887runtime[2709:323818]方法名:allPropertyNamesAndValues,参数个数:2
2016-05-0518:35:39.887runtime[2709:323818]方法名:allMethods,参数个数:2
2016-05-0518:35:39.887runtime[2709:323818]方法名:firtName,参数个数:2
2016-05-0518:35:39.887runtime[2709:323818]方法名:setFirtName:,参数个数:3
2016-05-0518:35:39.888runtime[2709:323818]方法名:age,参数个数:2
2016-05-0518:35:39.888runtime[2709:323818]方法名:sex,参数个数:2
2016-05-0518:35:39.888runtime[2709:323818]方法名:setSex:,参数个数:3
2016-05-0518:35:39.888runtime[2709:323818]方法名:setArray:,参数个数:3
2016-05-0518:35:39.888runtime[2709:323818]方法名:.cxx_destruct,参数个数:2
2016-05-0518:35:39.888runtime[2709:323818]方法名:name,参数个数:2
2016-05-0518:35:39.889runtime[2709:323818]方法名:array,参数个数:2
2016-05-0518:35:39.889runtime[2709:323818]方法名:setName:,参数个数:3
调用打印结果如下,为什么参数个数看起来不匹配呢?比如-allProperties方法,其参数个数为0才对,但是打印结果为2。根据打印结果可知,无参数时,值就已经是2了。这个在后面会详细讲解。
/* Working with Methods */
/**
* Returns the name of a method.
* @param m The method to inspect.
* @return A pointer of type SEL.
* @note To get the method name as a C string, call \c sel_getName(method_getName(method)).
*/
OBJC_EXPORTSELmethod_getName(Methodm)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
/**
* Returns the number of arguments accepted by a method.
* @param m A pointer to a \c Method data structure. Pass the method in question.
* @return An integer containing the number of arguments accepted by the given method.
*/
OBJC_EXPORTunsignedintmethod_getNumberOfArguments(Methodm)
__OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);
1.7.获取对象的成员变量名称
要获取对象的成员变量,可以通过class_copyIvarList方法来获取,通过ivar_getName来获取成员变量的名称。并且我们知道,每一个属性都会自动生成一个成员变量和setter以及getter方法。(省略部分相同代码)
-(NSArray*)allMemberVariables{
unsignedintcount=0;
Ivar*ivars=class_copyIvarList([selfclass],&count);
NSMutableArray*results=[[NSMutableArrayalloc]init];
for(NSUIntegeri=0;i
Ivarvariable=ivars[i];
constchar*name=ivar_getName(variable);
NSString*varName=[NSStringstringWithUTF8String:name];
[resultsaddObject:varName];
}
free(ivars);
returnresults;
}
测试一下:
-(void)test1_4{
Person*p=[[Personalloc]init];
for(NSString*varNameinp.allMemberVariables){
NSLog(@"%@",varName);
}}
打印结果:
2016-05-0518:48:19.969runtime[2846:343854]_variableString
2016-05-0518:48:19.970runtime[2846:343854]_sex
2016-05-0518:48:19.970runtime[2846:343854]_name
2016-05-0518:48:19.970runtime[2846:343854]_firtName
2016-05-0518:48:19.970runtime[2846:343854]_array
2016-05-0518:48:19.970runtime[2846:343854]_age
1.8.运行时发消息
iOS中,可以在运行时发送消息,让接收消息者执行对应的动作。可以使用objc_msgSend方法,发送消息。因为objc_msgSend是因为只有对象才能发送消息,所以肯定是以objc开头的。
另外:使用运行时发送消息前,必须导入#import
-(void)test1_5{
Person*p=[[Personalloc]init];
p.name=@"BirdMichael";
p.age=8;
objc_msgSend(p,@selector(allMethods));
}
但是这样就等于把[p allMethods]这个方法用底层的方法表示出来,其实[p allMethods]也会转成这句代码。此时你会发现很尴尬的一个现象是:编译器会报错。
问题出在:期望的参数为空,但是实际上是有2个参数的。所以我们需要来关闭严格检查来解决这个问题。
问题提示期望的参数为0,但是实际上有两个参数。解决办法是,关闭严格检查:
1.9.扩展属性(Associative)
1.9.1.Associative概念
在日常的开发中,objective-c有两个扩展的机制:category和associative。而我们日常使用中百分之90都是使用的category作为日常的扩展方法,但是这个方法有很大的局限性:因为它并不能扩展属性。于是我们就需要借助于另外一个属性的扩展机制:
objective-c有两个扩展机制:category和associative。我们可以通过category来扩展方法,但是它有个很大的局限性,不能扩展属性。于是,就有了专门用来扩展属性的机制:associative。
1.9.2.Associative介绍及使用
1.9.2.1.associative方法
associative的原理就是把两个对象对象互相的捆绑、关联起来,使的其中的一个对象成为另外一个对象的一部分。并且我们可以不用使用修改类的定义而为其对象增加储存空间。这就有一个非常大的好处:在我们无法访问到类的源码的时候或者是考虑到二进制兼容性的时候是非常有用的。为什么我说这个很有用呢?因为这允许开发者对已经存在的类在扩展中添加自定义的属性,这几乎弥补了Objective-C最大的缺点。
在使用上,associative是基于key的。所以,我们可以为任何的对象增加无数个不同key的associative,每个都使用上不同的key就好了。并且associative是可以保证能被关联的对象在关联对象的整个生命周期都是可用。
注意:由于正常的扩展是不可以扩展属性的,所以我们在使用associative的时候童谣需要到入runtime.h来实现。
/* Associative References */
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
typedefOBJC_ENUM(uintptr_t,objc_AssociationPolicy){
OBJC_ASSOCIATION_ASSIGN=0,/**<
Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC=1,/**<
Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC=3,/**<
Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN=01401,/**<
Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY=01403/**<
Specifies that the associated object is copied.
* The association is made atomically. */
};
/**
* Sets an associated value for a given object using a given key and association policy.
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORTvoidobjc_setAssociatedObject(idobject,constvoid*key,idvalue,objc_AssociationPolicypolicy)
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_1);
/**
* Returns the value associated with a given object for a given key.
* @param object The source object for the association.
* @param key The key for the association.
* @return The value associated with the key \e key for \e object.
* @see objc_setAssociatedObject
*/
OBJC_EXPORTidobjc_getAssociatedObject(idobject,constvoid*key)
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_1);
/**
* Removes all associations for a given object.
* @param object An object that maintains associated objects.
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORTvoidobjc_removeAssociatedObjects(idobject)
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_3_1);
其中有3个方法:
objc_setAssociatedObject
objc_getAssociatedObject
objc_removeAssociatedObjects
其中通过objc_getAssociatedObject取得关联的值,通过objc_setAssociatedObject设置关联。
1.9.2.2.创建associative
创建associative使用的是:objc_setAssociatedObject。它把一个对象与另外一个对象进行关联。该函数需要四个参数:
object(源对象): The source object for the association.
key(关键字): The key for the association.
value(关联的对象) :The value to associate with the key key for object. Pass nil to clear an existing association.
policy(关联策略) :The policy for the association. For possible values, see “Associative Object Behaviors.”
1.9.2.2.1.key关键字
关于前两个函数中的 key 值是我们需要重点关注的一个点,这个 key 值必须保证是一个对象级别(为什么是对象级别?看完下面的章节你就会明白了)的唯一常量。一般来说,有以下三种推荐的 key 值:
声明 static char kAssociatedObjectKey; ,使用 &kAssociatedObjectKey 作为 key 值。
声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用 kAssociatedObjectKey 作为 key 值。
用 selector ,使用 getter 方法的名称作为 key 值。
关键字是一个void类型的指针。每一个关联的关键字必须是唯一的。通常都是会采用静态变量来作为关键字,但是其中3个方法中一般1,2基本可以忽略,只使用第三种就好了。因为这样可以优雅地解决了计算科学中的两大世界难题之一:命名(另一难题是缓存失效 )。
1.9.2.2.2.关联策略
关联策略表明了相关的对象是通过赋值,保留引用还是复制的方式进行关联的;还有这种关联是原子的还是非原子的。这里的关联策略和声明属性时的很类似。这种关联策略是通过使用预先定义好的常量来表示的。
根据上面引入的文档可以看出属性可以根据定义在枚举类型 objc_AssociationPolicy 上的行为被关联在对象上。 其中有5种枚举值。
OBJC_ASSOCIATION_ASSIGN@property (assign)或@property (unsafe_unretained)指定一个关联对象的弱引用。
OBJC_ASSOCIATION_RETAIN_NONATOMIC@property (nonatomic, strong)指定一个关联对象的强引用,不能被原子化使用。
OBJC_ASSOCIATION_COPY_NONATOMIC@property (nonatomic, copy)指定一个关联对象的copy引用,不能被原子化使用。
OBJC_ASSOCIATION_RETAIN@property (atomic, strong)指定一个关联对象的强引用,能被原子化使用。
OBJC_ASSOCIATION_COPY@property (atomic, copy)指定一个关联对象的copy引用,能被原子化使用。
其中,第 2 种与第 4 种、第 3 种与第 5 种关联策略的唯一差别就在于操作是否具有原子性。
以 OBJC_ASSOCIATION_ASSIGN 类型关联在对象上的弱引用不代表0 retian的 weak 弱引用,行为上更像unsafe_unretained 属性,所以当在你的视线中调用weak的关联对象时要相当小心。具体知识点可以看这篇文章:点击查看
现在比如要给person添加一个属性:sonName.
Person+associative.h
#import "Person.h"
@interfacePerson(associative)
@property(nonatomic,strong)NSString*sonName;
@end
Person+associative.m
#import "Person+associative.h"
#import
@implementationPerson(associative)
-(void)setSonName:(NSString*)sonName{
objc_setAssociatedObject(self,@selector(sonName),sonName,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString*)sonName{
NSObject*obj=objc_getAssociatedObject(self,@selector(sonName));
if(obj&&[objisKindOfClass:[NSStringclass]]){
return(NSString*)obj;
}
returnnil;
}
@end
现在我们来测试一下:
-(void)test1_6{
Person*p=[[Personalloc]init];
p.name=@"BirdMichael";
p.sonName=@"BirdMichael's son";
NSLog(@"%@ > %@",p.name,p.sonName);
}
打印结果:
2016-05-0616:15:00.888runtime[4837:801744]BirdMichael>BirdMichael'sson
1.9.3.断开associative
断开或者说是删除associative是associative中3个方法中最不常用的一个方法,可以说基本上都不会使用。因为他会断开所有的associative关联。文档有这样的解释:点击进入
所以:我们可以这样理解objc_removeAssociatedObjects是一个格式化操作,并且非常不见。一般的删除操作我们使用objc_setAssociatedObject来传入nil值来清楚关联。
1.9.4.应用场景
1.9.4.1.优秀案例
添加私有属性用于更好地去实现细节。当扩展一个内建类的行为时,保持附加属性的状态可能非常必要。注意以下说的是一种非常教科书式的关联对象的用例:AFNetworking在UIImageView的category上用了关联对象来保持一个operation对象,用于从网络上某URL异步地获取一张图片。
添加public属性来增强category的功能。有些情况下这种(通过关联对象)让category行为更灵活的做法比在用一个带变量的方法来实现更有意义。在这些情况下,可以用关联对象实现一个一个对外开放的属性。回到上个AFNetworking的例子中的UIImageViewcategory,它的imageResponseSerializer方法允许图片通过一个滤镜来显示、或在缓存到硬盘之前改变图片的内容。
创建一个用于KVO的关联观察者。当在一个category的实现中使用KVO时,建议用一个自定义的关联对象而不是该对象本身作观察者。ng an associated observer for KVO**. When usingKVOin a category implementation, it is recommended that a custom associated-object be used as an observer, rather than the object observing itself.
为NSObject子类添加任何信息。这是一个方便,强大,并且简单的类。利用associative机制,为任何Object,添加你所需要的信息。比如用户登录,向服务端发送用户名/密码时,可以将这些信息绑定在请求的项之中。等请求完成后,再取出你所需要的信息,进行逻辑处理。而不需要另外设置成员,保存这些数据。
1.9.4.2.反例
当值不需要的时候建立一个关联对象。一个常见的例子就是在view上创建一个方便的方法去保存来自model的属性、值或者其他混合的数据。如果那个数据在之后根本用不到,那么这种方法虽然是没什么问题的,但用关联到对象的做法并不可取。
当一个值可以被其他值推算出时建立一个关联对象。例如:在调用cellForRowAtIndexPath: 时存储一个指向view的 UITableViewCell 中accessory view的引用,用于在 tableView:accessoryButtonTappedForRowWithIndexPath: 中使用。
用关联对象替代X,这里的X可以代表下列含义:
当继承比扩展原有的类更方便时用子类化。
为事件的响应者添加响应动作。
当响应动作不方便使用时使用的手势动作捕捉。
行为可以在其他对象中被代理实现时要用代理(delegate)。
用NSNotification 和 NSNotificationCenter进行松耦合化的跨系统的事件通知。
比起其他解决问题的方法,关联对象应该被视为最后的选择(事实上category也不应该作为首选方法)。
和其他精巧的trick、hack、workaround一样,一般人都会在刚学习完之后乐于寻找场景去使用一下。尽你所能去理解和欣赏它在正确使用时它所发挥的作用,同时当你选择这个解决办法时,也要避免当被轻蔑地问起“这是个什么玩意?”时的尴尬。
1.9.5.associative总结
以上便是几种associative机制的使用例子。这只是强大的associative功能中,小小的几个缩影。有了associative,就能用简单的几行代码,解决曾经困扰我们许久的问题。
在开发中,我们比较常用的是使用关联属性的方式来扩展我们的“属性”,以便在开发中简单代码。我们在开发中使用关联属性扩展所有响应事件、将代理转换成block版等。比如,我们可以将所有继承于UIControl的控件,都拥有block版的点击响应,那么我们就可以给UIControl扩展一个TouchUp、TouchDown、TouchOut的block等。
对于动态获取属性的名称、属性值使用较多的地方一般是在使用第三方库中,比如MJExtension等。这些三方库都是通过这种方式将Model转换成字典,或者将字典转换成Model。