赞
踩
在上周中学习JSONModel源码的过程中,源码里用到了许多关联对象的知识,之前在学小蓝书的时候对这一部分内容学习并不完整,在这作以补充:
typedef struct category_t *Category;
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; //实例属性
// Fields below this point are not always present on disk.
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);
};
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (testCategory)
@property (nonatomic, strong) id associatedObject;
@end
NS_ASSUME_NONNULL_END
首先在分类的.h文件里声明一个属性,然后在main中给属性赋值:
#import <Foundation/Foundation.h>
#import "NSObject+testCategory.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSObject *obj = [[NSObject alloc] init];
obj.associatedObject = @"1";
NSLog(@"%@",obj.associatedObject);
[obj setAssociatedObject:@"2"];
NSLog(@"%@",obj.associatedObject);
}
return 0;
}
我们发现在编译中并没有报错,然而在运行的时候出现崩溃:
添加关联对象
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)
set方法的参数:
参数二:
给key设值有三种比较好的的方法
因为key值类型为const void*,任何类型的值,其实就是给个唯一的标识
1.我们针对每个属性,定义-个全局的key名, 然后取其地址,这一定是唯一的加上static,只在文件内部有效
static const void *NameKey = &NameKey;
static const void *WeightKey = &WeightKey;
2.针对每个属性,因为类中的属性名是唯一的,直接拿属性名作为key
#define NameKey = @"name";
#define WeightKey = @"weight";
3.使用@selector作为key
直接用属性名对应的get方法的selector,有提示不容易写错
并且get方法隐藏参数cmd 可以直接用,看上去就会更加简洁
以下实例代码就是用的第三种方式
参数四枚举类型:
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; //指定相关的对象被复制, 原子性
};
#import "NSObject+testCategory.h"
#import <objc/runtime.h> // 关联对象相关api在runtime库中
@implementation NSObject (testCategory)
- (void)setAssociatedObject:(id)object {
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)associatedObject {
return objc_getAssociatedObject(self, _cmd);
}
@end
这时候运行main代码:
在get方法中我们看到了_cmd参数,这是个什么东西呢?
实现关联对象的核心对象有
objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);实际调用下面的方法:
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
//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<objc_object> 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();
}
objc_getAssociatedObject(self, _cmd)实际是调用下面的方法:
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
}
最后来看void objic_removeAssociatedObjects(id object):
// Unlike setting/getting an associated reference,
// this function is performance sensitive because of
// raw isa objects (such as OS Objects) that can't track
// whether they have associated objects.
// 与设置/获取关联引用不同,此函数对性能敏感,因为原始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<ObjcAssociation *, 4> 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();
}
}
除此之外还看了一些分类的运行期之类的东西,但是没太看懂,后面再补充。
4月4日补充:
_objc_init这个函数是runtime的初始化函数,我们从_objc_init开始入手
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init();
static_init();
runtime_init();
exception_init();
cache_t::init();
_imp_implementationWithBlock_init();
_dyld_objc_callbacks_v1 callbacks = {
1, // version
&map_images,
load_images,
unmap_image,
_objc_patch_root_of_class
};
_dyld_objc_register_callbacks((_dyld_objc_callbacks*)&callbacks);
didCallDyldNotifyRegister = true;
}
map_images读取资源(images代表资源模块),我们点进去看看:
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
bool takeEnforcementDisableFault;
{
mutex_locker_t lock(runtimeLock);
map_images_nolock(count, paths, mhdrs, &takeEnforcementDisableFault);
}
if (takeEnforcementDisableFault) {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
bool objcModeNoFaults = DisableFaults
|| DisableClassROFaults
|| getpid() == 1
|| is_root_ramdisk()
|| !os_variant_has_internal_diagnostics("com.apple.obj-c");
if (!objcModeNoFaults) {
os_fault_with_payload(OS_REASON_LIBSYSTEM,
OS_REASON_LIBSYSTEM_CODE_FAULT,
NULL, 0,
"class_ro_t enforcement disabled",
0);
}
#endif
}
}
然后再点进去map_images_nolock方法:
这个方法实现太长了,就不放出来了,咱们重点看的是这个方法里面的_read_images方法:
这个方法也很长,就放出分类相关这一部分代码:
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
我们来看看这个方法都干了什么:
4、5主要是分类对应的主类是元类对象还是类对象
然后我们来看看上面方法中用到的addUnattachedCategoryForClass方法:
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertWriting();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
static NXMapTable *unattachedCategories(void)
{
runtimeLock.assertWriting();
//全局对象
static NXMapTable *category_map = nil;
if (category_map) return category_map;
// fixme initial map size
category_map = NXCreateMapTable(NXPtrValueMapPrototype, 16);
return category_map;
}
我们来看看这个方法都干了什么:
这段代码对于我们来说,对分类的实现部分关系并不大,其仅仅是把类和category做一个关联映射,而remethodizeClass才是真正去处理添加事宜的功臣
static void remethodizeClass(Class cls)
{
//分类数组
category_list *cats;
bool isMeta;
runtimeLock.assertWriting();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
还是没有得到我们需要的信息,其核心是调用了attachCategories函数把我们的分类信息附加到该类中
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 创建方法列表、属性列表、协议列表,用来存储分类的方法、属性、协议
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0; // 记录方法的数量
int propcount = 0; // 记录属性的数量
int protocount = 0; // 记录协议的数量
int i = cats->count; // 从分类数组最后开始遍历,保证先取的是最新的分类
bool fromBundle = NO; // 记录是否是从 bundle 中取的
while (i--) { // 从后往前依次遍历
auto& entry = cats->list[i]; // 取出当前分类
// 取出分类中的方法列表。如果是元类,取得的是类方法列表;否则取得的是对象方法列表
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist; // 将方法列表放入 mlists 方法列表数组中
fromBundle |= entry.hi->isBundle(); // 分类的头部信息中存储了是否是 bundle,将其记住
}
// 取出分类中的属性列表,如果是元类,取得的是 nil
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// 取出分类中遵循的协议列表
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 取出当前类 cls 的 class_rw_t 数据
auto rw = cls->data();
// 存储方法、属性、协议数组到 rw 中【注意是rw哦】
// 准备方法列表 mlists 中的方法【为什么需要准备方法列表这一步?】
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 将新方法列表添加到 rw 中的方法列表中
rw->methods.attachLists(mlists, mcount);
// 释放方法列表 mlists
free(mlists);
// 清除 cls 的缓存列表
if (flush_caches && mcount > 0) flushCaches(cls);
// 将新属性列表添加到 rw 中的属性列表中
rw->properties.attachLists(proplists, propcount);
// 释放属性列表
free(proplists);
// 将新协议列表添加到 rw 中的协议列表中
rw->protocols.attachLists(protolists, protocount);
// 释放协议列表
free(protolists);
}
先创建方法列表、属性列表、协议列表的新列表并且给它们分配内存,然后存储该cls所有的分类的方法、属性、协议,然后转交给了attachMethodLists方法
attachLists方法保证其添加到列表的前面
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。