一、分类的本质
1、我们先准备一个.m
文件包含主类和分类
#import <Foundation/Foundation.h>
//类
@interface MCStudyCate : NSObject
@property(nonatomic, strong) NSString* nickName;
@end
@implementation MCStudyCate
@end
//分类
@interface MCStudyCate (KC)
@property(nonatomic, strong) NSString* cate_pro1;
@property(nonatomic, strong) NSString* cate_pro2;
-(void)cate_instanceMethod1;
-(void)cate_instanceMethod2;
-(void)cate_instanceMethod3;
+(void)cate_classMethod;
@end
@implementation MCStudyCate (KC)
-(void)cate_instanceMethod1{
NSLog(@"%s", __func__);
}
-(void)cate_instanceMethod2{
NSLog(@"%s", __func__);
}
+(void)cate_classMethod{
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
MCStudyCate *studyCate = [MCStudyCate alloc];
NSLog(@"%p",studyCate);
}
return 0;
}
使用clang
工具将.m
文件转化为.cpp
,其对应命令行为$ clang -rewrite-objc main.m -o main.cpp
2、在.cpp
文件中有_category_t
具体实现,名字解析
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
并且会拿着这个结构体去实例化具体分类的对象,其每个字段的解释具体含义如下
-
name
:分类的名称; -
cls
:分类的所属类 -
instance_methods
:分类中添加的实例方法 -
class_methods
:分类中添加的类方法 -
protocols
:分类中添加的协议列表 -
properties
:分类中添加的属性列表
3、接下分类实现的具体结构
其中的实例方法和类方法我也明确标出,发现
- 3.1 分类的属性不会自动实现
setter/getter
方法,但属性列表中存在 - 3.2 从
cate_instanceMethod3
可以看出,分类中申明但未实现的方法不存在于方法列表中
二、分类的加载
1、分类绑定到类当中
我们知道objc源码中初始化函数_objc_init
,代码文件存在于objc-os.mm
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_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
而在_dyld_objc_notify_register
中map_images
存在如下调用逻辑,大家可以根据源码查看:
map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
而 attachCategories
里面就是分类给主类添加属性、方法、协议的具体实现
其实这里是根据主类
+load
非懒加载去分析的,不过在一般的懒加载情况下其后面的调用逻辑realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
殊途同归
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
// mlists -> 二维数组
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
我们去看看方法的添加过程methods.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 {
// 算法 list + list - 分类
// list + newlist
// array
// newlist list
// 1 list -> many lists 1 + 3
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; // 4
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
我们在这里我可以发现
- 1.1、 category方法的添加不是覆盖,而是将老方法平移到数组的后方,将新方法插入到前面
- 1.2、category方法越后加入,就会排到前面去,而方法的查找是顺序的,所以后加入的方法先找到.
2、分类加载到内存
经过上面的分析我们已经知道分类中的方法、属性、协议如何绑定到类,但我们还不知道分类是如何加载到内存的,不过在上面的过程中我们知道分类需要在UnattachedCategories
才能得到,其实在UnattachedCategories
中除了attachToClass
还有addForClass
方法
void addForClass(locstamped_category_t lc, Class cls)
{
runtimeLock.assertLocked();
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: found category %c%s(%s)",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), lc.cat->name);
}
auto result = get().try_emplace(cls, lc);
if (!result.second) {
result.first->second.append(lc);
}
}
通过try_emplace
方法得知
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
这个创建一个存储桶的结构(这是一个键值对形式的结构),向里面存内容,结合上面的函数调用get().try_emplace(cls, lc)
得知,以cls
为key
,lc
为value
进行存储。现在就分析到这,再走下去就跟我们本来的研究方向走偏了。
我们现在看下addForClass
方法是哪里调用的
很清晰的看到,调用
addForClass
的方法其实都在objc-runtime-new.mm
中的load_categories_nolock
内,顺藤摸瓜我们很容易找到其调用逻辑为addForClass <- load_categories_nolock <- loadAllCategories <- load_images
,竟然回到了_objc_init
中向dyld
注册的load_images
方法总结一下流程图为