iOS手把手带你探索Category
- 移动端
- 会骑车的iOSer
- 0
在我们的实际开发中Category
分类的使用必不可少,那么我们通过以下几个方面来探索一下分类
- 1.什么是分类
Category
- 2.
Category
的作用 - 3.
Category
和Exension
的区别 - 4.
Category
底层探究 - 5.关联对象的探索
什么是分类(Category)
Category
是Ovjective-C 2.0
之后添加的语言特性,Category
作用是为已经存在的类添加方法
Category的作用
- 1.可以减少单个文件的体积
- 2.可以把不同的功能组织到不同的Category中
- 3.可以按需加载
- 4.声明私有方法
- 5.把
framework
的私有方法公开
Category和Exension的区别
- 1.
Category
依托当前类存在,Exension
则是类的一部分 - 2.
Category
在运行时期确定,Exension
在编译器确定 - 3.
Category
不能添加变量,Exension
可以添加变量(编译时期对象的内存布局已经确定)
Category底层探究
4.1 Category
的数据结构
Category
的数据结构如下
struct category_t {
//当前类的名称
const char *name;
//需要扩展的类对象,在编译期没有值
classref_t cls;
//实例方法
WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
//对象方法
WrappedPtr<method_list_t, method_list_t::Ptrauth> 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);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
-
const char *name;
: 当前类的名称 -
classref_t cls;
:需要扩展的类对象,在编译期没有值 -
WrappedPtr<method_list_t, method_list_t::Ptrauth> instanceMethods;
:实例方法列表 -
WrappedPtr<method_list_t, method_list_t::Ptrauth> classMethods;
:对象方法列表 -
struct protocol_list_t *protocols;
:协议列表 -
struct property_list_t *instanceProperties;
:实例属性 -
struct property_list_t *_classProperties;
:类属性
4.2 Category
在编译时期做了什么
#import "Person+A.h"
@implementation Person (A)
- (void)funcA {
NSLog(@"A");
}
@end
首先我们创建这样一个分类,下面我们通过clang
看下在编译时期分类到底做了些什么
clang -rewrite-objc "文件名.m/h"
我们可以通过上面这个命令在终端
中获取到.cpp
文件
static struct _category_t _OBJC_$_CATEGORY_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A,
0,
0,
0,
};
我们编译后的东西都存放在Mach-o
的可执行文件中,通过字段标识当前内容。上述内容就是存在__DATA
中的__objc_const
的字段中
-
Person
->name
字段 类的名称 -
0, // &OBJC_CLASS_$_Person,
->cls 需要扩展的类对象,在编译期没有值 -
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A,
-> instanceMethods 在分类中声明的实例方法
我们再来看下实例方法列表这个结构体里有些啥
_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_A __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"funcA", "v16@0:8", (void *)_I_Person_A_funcA}}
};
-
funcA
方法编号 -
v16@0:8
方法签名 -
(void *)_I_Person_A_funcA
真实的函数地址
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_Person_$_A,
};
将编译期生成的分类结构体存放在__DATA
中的__objc_catlist
字段中
总结 Category
在编译期做了什么
- 1.把分类编译成结构体
- 2.在对应的字段中填充相应的数据
- 3.把所有的分类存放在
__DATA
数据段中的__objc_catlist
字段中
4.3 Category
在运行时期做了什么
要想探索Category
在运行时期做了什么,首先我们来倒app的入口函数_objc_init
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();
//线程寄存器初始化
tls_init();
//静态初始化
static_init();
//runtime初始化
runtime_init();
//异常初始化
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
首先进行了一些初始化的操作,environ_init ()
,tls_init ()
,static_init ()
,runtime_init ()
,exception_init
.
然后调用_dyld_objc_notify_register(&map_images, load_images, unmap_image);
函数,通过dyld注册三个函数:map_images
(map映射,当前dyld把images(镜像)加载进内存时,会出发map_images
的回调), load_images
(当dyld初始化images(镜像)模块时调用,+load
方法也会在这里被调用), unmap_image
(dyld把image移除内存时调用)
这里我们重点关注map_images
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
//头文件信息
header_info *hList[mhCount];
//头文件数
uint32_t hCount;
size_t selrefCount = 0;
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
//类的数量
int totalClasses = 0;
if (hCount > 0) { //读取镜像文件,读取OC相关的Section
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
由于map_images_nolock
函数中代码太多,截取了部分关键代码
_read_images
函数中做了什么
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (didInitialAttachCategories) {
//遍历整个头文件列表
for (EACH_HEADER) {
//加载分类
load_categories_nolock(hi);
}
}
}
由于_read_images
函数中代码太多,截取了部分关键代码
-
EACH_HEADER
是一个宏定义,用来遍历整个头文件列表 -
load_categories_nolock
加载分类
接着往下看 load_categories_nolock
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
//把分类中声明的属性,方法添加到累的属性,方法列表中
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
//把当前分类加载到对应的类中
objc::unattachedCategories.addForClass(lc, cls->ISA());
//addForClass底层数据结构的映射
}
}
}
}
};
//读取分类列表
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
在这个函数中做了三件事
- 1.
processCatlist(hi->catlist(&count));
,processCatlist(hi->catlist2(&count));
读取分类列表 -
objc::unattachedCategories.addForClass(lc, cls->ISA());
当前分类加载到对应的类中
-
-
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
把分类中声明的属性,方法,协议添加到类的属性,方法,协议列表中
-
attachCategories
函数的实现
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
//声明 方法列表,属性列表,协议列表所用的空间
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
if (mcount > 0) {
//准备当前的方法列表
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
//把分类方法追加进类的方法列表中
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
由于attachCategories
函数中代码太多,截取了部分关键代码
- 1.
prepareMethodLists
准备当前的方法列表,核心:把方法名称放到方法列表中 - 2.
attachLists
分类和本类相应的对象方法,属性,和协议进行了合并。
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; //得到添加后的方法个数
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); //开辟新的空间
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i]; //把原来的方法放在后面
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i]; //把新添加的方法放在数组的前面
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
这里会分三种情况:多对多,0对1,1对多,这里就以多对多为例
-
uint32_t oldCount = array()->count;
获取原来方法列表中的方法个数 -
uint32_t newCount = oldCount + addedCount
得到添加后的方法个数 -
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
开辟新的空间 -
newArray->lists[i + addedCount] = array()->lists[i];
把原来的方法放在后面 -
newArray->lists[i] = addedLists[i]
把新添加的方法放在数组的前面
总结Category
在运行时做了什么
1.通过catlist
,catlist2
(_getObjc2CategoryList
)读取分类列表
2.通过addForClass
当前分类加载到对应的类中
3.attachCategories
把分类中声明的属性,方法,协议添加到类的属性,方法,协议列表中
4.prepareMethodLists
准备当前的方法列表,核心:把方法名称放到方法列表中
5.attachLists
分类和本类相应的对象方法,属性,和协议进行了合并
6.合并的顺序,先为分类中的方法,再是原类中的方法(这就是同名方法分类会优先于本类调用的原因)
多个分类/类也有同名方法,方法查找,本质上调用Runtime遍历方法列表,通过二分查找算法,不会立马返回imp指针,往前移动,找是否有同名方法。
关联对象的探索
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end
在类中声明一个属性,系统会自动帮我们生成getter
setter
ivar
.
当我们使用分类想为原类添加属性时,我们则需要使用关联对象的方式,那么关联对象和直接声明数据原理是否一样呢,我们接下来分析下
分类中我们使用objc_setAssociatedObject
,objc_getAssociatedObject
这两函数添加属性
首先我们来看下关联对象怎么存的objc_setAssociatedObject
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.
// rdar://problem/44094390
if (!object && !value) return;
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));
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value}; //把外部传入的value和策略存起来
// retain the new value (if any) outside the lock.
//根据上面存的policy,设置内存管理语义
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager; //声明一个管理者,用来管理下面的AssociationsHashMap
AssociationsHashMap &associations(manager.get());
if (value) {
//<first, second> -> <disguised, ObjectAssociationMap{}>
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) {
associations.erase(refs_it);
}
}
}
}
}
// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();
// release the old value (outside of the lock).
association.releaseHeldValue();
}
-
DisguisedPtr<objc_object> disguised{(objc_object *)object}
按位取反 -
ObjcAssociation association{policy, value};
把外部传入的value和策略存起来 -
association.acquireValue();
根据上面存的policy,设置内存管理语义,copy
,retain
-
AssociationsManager manager;
声明一个管理者,用来管理下面的AssociationsHashMap -
AssociationsHashMap &associations(manager.get());
初始化HashMap -
object->setHasAssociatedObjects();
与关联对象做绑定,方便释放
根据上面的代码我们就明白了关联对象是怎么存对象的了
AssociationsManager -> <disguised , ObjectAssociationMap >
ObjectAssociationMap -> <key(外部传入的), ObjcAssociation>
ObjcAssociation -> 有俩属性 外部传入的uintptr_t _policy
,id _value
objc_getAssociatedObject
获取关联对象实现
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
我们探索了objc_setAssociatedObject
的实现,objc_getAssociatedObject
的实现就一幕了然了
通过AssociationsManager
获取到AssociationsHashMap
,再通过获取的AssociationsHashMap
得到association
。
下面我们再来看下关联对像的释放
- (void)dealloc {
_objc_rootDealloc(self);
}
void
_objc_rootDealloc(id obj)
{
ASSERT(obj);
obj->rootDealloc();
}
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
delloac
-> _objc_rootDealloc
-> rootDealloc
(首先判断是否有关联对象等,如果没有则直接释放)如果没有 -> object_dispose
-> objc_destructInstance
-> _object_remove_assocations
(释放关联对象)
免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。