OC的动态特征允许使用类别为现有的类添加新方法并且不需要创建子类,不需要访问原有类的源代码。通过使用类别即可动态为现有的类添加新方法,而且可以将类定义模块化分布到多个相关文件。
示例代码如下:
@interface NSString (MyCategory)
- (NSString *)reverseString;
@end
@implementation NSString (MyCategory)
- (NSString *)reverseString {
NSMutableString *reversedString = [[NSMutableString alloc] init];
for (NSInteger i = self.length - 1; i >= 0; i--) {
[reversedString appendString:[self substringWithRange:NSMakeRange(i, 1)]];
}
return reversedString;
}
@end
分类应用场景:
NSString
类添加 reverseString
方法来反转字符串。Networking
的分类中。AFNetworking
添加一个方法来处理特定类型的 API 请求。分类的定义如下:
struct category_t {
// 分类名称
const char *name;
// 分类所属的类
classref_t cls;
// 实例方法列表
struct method_list_t *instanceMethods;
// 类方法列表
struct method_list_t *classMethods;
// 协议列表
struct protocol_list_t *protocols;
// 实例属性列表
struct property_list_t *instanceProperties;
// 类属性列表(可能不存在)
struct property_list_t *_classProperties;
// 获取实例方法或类方法列表
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
// 获取实例属性或类属性列表
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
从结构体可以看出,分类能
但是不能添加实例变量,即无法自动生成实例变量的setter和getter方法
扩展与类别相似,有时候被称为匿名分类。但是两者实质上不是一个内容。
扩展是在编译阶段与该类同时编译的,是类的一部分。扩展中声明的方法只能在该类的@implementation中实现。所以这也就意味着我们无法对系统的类使用扩展。
同时与分类不同,扩展不但可以声明方法,还可以声明成员变量,这是分类所做不到的。
示例代码如下:
#import "Car1.h"
@interface Car1 ()
@property (nonatomic, copy) NSString *color;
- (void)drive:(NSString *)owner;
@end
#import "Car1.h"
#import "Car1+drive.h"
@implementation Car1
- (void)drive {
NSLog (@"%@汽车在路上跑", self);
}
- (void)drive:(NSString*)owner {
NSLog(@"%@正在驾驶%@汽车在路上跑", owner, self);
}
- (NSString*)description {
return [NSString stringWithFormat:@"",self.brand, self.model, self.color];
}
@end
关联对象允许你为一个对象添加额外的属性,即使这个对象本身没有定义这些属性。
因此,可以使用关联对象给分类添加属性
下面是关联对象的API
//添加关联对象
void objc_setAssociatedObject(id object, const void * key, id value, objc AssociationPolicy policy)
//获得关联对象
id objc_getAssociatedObject(id object, const void * key)
//移除所有的关联对象
void objic_removeAssociatedObjects(id object)
object
: 要添加属性的对象。key
: 用于标识属性的键,通常使用一个 NSString
对象。value
: 要添加的属性值。policy
: 关联策略,用于指定属性的生命周期和访问权限。关联策略如下:
typedef OBJC ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ ASSOCIATION_ASSIGN = 0, //指定个弱引用相关联的对象
OBJC_ ASSOCIATION_RETAIN_NONATOMIC = 1; //指定相关对象的强引用, 非原子性
OBJC_ ASSOCIATION_COPY_NONATOMIC = 3; //指定相关的对象被复制, 非原子性
OBJC_ ASSOCIATION_RETAIN = 01401; //指定相关对象的强引用,原子性
OBJC_ ASSOCIATION_COPY = 01403; //指定相关的对象被复制, 原子性
};
给key设置值一般来说有三种方法
static const void *NameKey = &NameKey;
static const void *WeightKey = &WeightKey;
#define NameKey = @"name";
#define WeightKey = @"weight";
@selector(name)//直接用属性名对应的get方法的selector,有提示不容易写错。并且get方法隐藏参数cmd 可以直接用,看上去就会更加简洁
下面是示例代码:
#import
#import "objc/runtime.h"
NS_ASSUME_NONNULL_BEGIN
@interface UIView (defaultColor)
@property (nonatomic, strong)UIColor* defaultColor;
@end
NS_ASSUME_NONNULL_END
#import "UIView+defaultColor.h"
@implementation UIView (defaultColor)
@dynamic defaultColor;
static char kDefaultColorKey;
- (void)setDefaultColor:(UIColor *)defaultColor {
//objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(self, @selector(defaultColor), defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)defaultColor {
//return objc_getAssociatedObject(self, &kDefaultColorKey);
return objc_getAssociatedObject(self, _cmd);
}
@end
通过上面的关联对象我们就给系统提供的UIView设置了默认颜色的属性
_cmd
是一个特殊的参数,它代表当前正在执行的方法的选择器(selector)。选择器是一个字符串,它标识了方法的名称。
_cmd
可以让方法在执行过程中获取自身的信息,例如方法名称
实现关联对象技术的核心对象有
首先来看创建关联对象的函数,源码如下:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
//isa有一位信息为禁止关联对象,如果设置了,直接报错
if (!object && !value) return;
// 判断runtime版本是否支持关联对象
if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
// 将 object 封装成 DisguisedPtr 目的是方便底层统一处理
DisguisedPtr disguised{(objc_object *)object};
// 将 policy和value 封装成ObjcAssociation,目的是方便底层统一处理
ObjcAssociation association{policy, value};
// (如果有新值)保留锁外的新值。
// retain the new value (if any) outside the lock.
// 根据传入的缓存策略,创建一个新的value对象
association.acquireValue();
bool isFirstAssociation = false;
{
//调用构造函数,构造函数内加锁操作
AssociationsManager manager;
// 创建一个管理对象管理单例,类AssociationsManager管理一个锁/哈希表单例对。分配一个实例将获得锁
// 并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
//获取全局的HasMap
// 全场唯一
AssociationsHashMap &associations(manager.get());
if (value) {
//去关联表中找对象对应的关联对象表,如果没有内部会重新生成一个
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
//如果没有找到
if (refs_result.second) {
/* it's the first association we make */
// 这是我们建立的第一个关联
//说明是第一次设置关联对象,把是否关联对象设置为YES
isFirstAssociation = true;
}
// 建立或替换关联
/* establish or replace the association */
// 获取ObjectAssociationMap中存储值的地址
auto &refs = refs_result.first->second;
// 移除之前的关联,根据key
// 将需要存储的值存放在关联表中存储值的地址中
// 同时会根据key去查找,如果查找到`result.second` = false ,如果找不到就创建`result.second` = true
// 创建association时,当(association的个数+1)超过3/4,就会进行两倍扩容
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
// 交换association和查询到的`association`
// 其实可以理解为更新查询到的`association`数据,新值替换旧值
association.swap(result.first->second);
}
} else {
// 这里相当于传入的nil,移除之前的关联
// 到AssociationsHashMap找到ObjectAssociationMap,将传入key对应的值变为空。
// 查找disguised 对应的ObjectAssociationMap
auto refs_it = associations.find(disguised);
// 如果找到对应的 ObjectAssociationMap 对象关联表
if (refs_it != associations.end()) {
// 获取 refs_it->second 里面存放了association类型数据
auto &refs = refs_it->second;
// 根据key查询对应的association
auto it = refs.find(key);
if (it != refs.end()) {
// 如果找到,更新旧的association里面的值
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
// 如果该对象关联表中所有的关联属性数据被清空,那么该对象关联表会被释放
associations.erase(refs_it);
}
}
}
}
}
// 在锁外面调用setHasAssociatedObjects,因为如果对象有一个,这个//将调用对象的noteAssociatedObjects方法,这可能会触发initialize,这可能会做任意的事情,包括设置更多的关联对象。
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
// 释放旧的值(在锁外部)
association.releaseHeldValue();
}
object
和 value
是否都为 nil
,检查 object
的类是否允许关联对象)object
封装成 DisguisedPtr
类型,将 policy
和 value
封装成 ObjcAssociation
类型)association.acquireValue()
保留新值,确保新值不会被释放AssociationsManager
对象,并通过它来获取全局的 AssociationsHashMap
对象AssociationsHashMap
中查找 object
对应的关联表 ObjectAssociationMap
ObjectAssociationMap
,则创建一个新的 ObjectAssociationMap
,并将其插入到 AssociationsHashMap
中。ObjectAssociationMap
,则在ObjectAssociationMap
接着查找 key
对应的关联信息ObjectAssociationMap
中。value
为 nil
,则表示要移除关联。系统会查找 ObjectAssociationMap
中 key
对应的关联信息,并将其移除。获取关联对象的源码如下:
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//创建空的关联对象
{
AssociationsManager manager;//创建一个AssociationsManager管理类
AssociationsHashMap &associations(manager.get());//获取全局唯一的静态哈希map
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//找到迭代器,即获取buckets
if (i != associations.end()) {//如果这个迭代查询器不是最后一个 获取
ObjectAssociationMap &refs = i->second; //找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
ObjectAssociationMap::iterator j = refs.find(key);//根据key查找ObjectAssociationMap,即获取bucket
if (j != refs.end()) {
association = j->second;//获取ObjcAssociation
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();//返回value
}
AssociationsHashMap
AssociationsHashMap
中查找object
对应的ObjectAssociationMap
ObjectAssociationMap
,则在ObjectAssociationMap
中接着查找key对应的关联信息并赋值给value移除关联对象的源码如下:
// 与设置/获取关联引用不同,此函数对性能敏感,因为原始isa对象(如OS对象)不能跟踪它们是否有关联对象。
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
//如果我们没有回收,那么SYSTEM_OBJECT关联会被保留。
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
associations.erase(i);
}
}
// Associations to be released after the normal ones.
// 在正常关联之后释放关联。
SmallVector laterRefs;
// release everything (outside of the lock).
// 释放锁外的所有内容。
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
//如果我们不是在释放,那么RELEASE_LATER关联不会被释放
if (deallocating)
laterRefs.append(&i.second);
} else {
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
AssociationsManager
获取全局的 AssociationsHashMap
,并查找 object
对应的关联表 ObjectAssociationMap
。refs
变量中,以便在锁外进行操作。AssociationsHashMap
中移除 object
对应的关联表refs
中的所有关联对象,并根据关联对象的策略进行释放。OBJC_ASSOCIATION_SYSTEM_OBJECT
,则将其添加到 laterRefs
列表中,以便在所有其他关联对象释放后进行释放。OBJC_ASSOCIATION_SYSTEM_OBJECT
,则立即释放关联对象。laterRefs
中的所有关联对象,并释放它们Category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果Category和原来类都有methodA,那么Category附加完成之后,类的方法列表里会有两个methodA
Category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的Category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就返回了,不会在理会后面的同名方法。
关联对象存放在名为ObjectAssociationMap的哈希表中,存放关联对象的哈希表又被存放在名为AssociationsHashMap的哈希表中,通过AssociationsManager来管理。也就是说所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址,而这个map的value又是另外一个全局map,里面保存了关联对象的key。
关联对象的释放时机与移除时机并不总是一致。关联对象的生命周期取决于关联策略和目标对象的生存期。弱引用策略的关联对象会随着目标对象的释放而被释放,而强引用策略和复制引用策略的关联对象会继续存在,直到它们的引用计数降为 0。