【iOS开发】——Category底层原理、Extension、关联对象

Category是什么?它可以用来干什么?

Category是Objective-C 2.0之后添加的语言特性,别人口中的分类、类别其实都是指的Category。Category的主要作用是为已经存在的类添加方法。

通常情况下我们可以把类的实现分开在几个不同的文件里面,这样做的好处是:

  • 可以减少单个文件的体积
  • 可以把不同的功能组织到不同的category里
  • 可以由多个开发者共同完成一个类
  • 可以按需加载想要的category
  • 声明私有方法, apple 的SDK中就大面积的使用了category这一特性。比如说:UIKit中的UIView。apple把不同的功能API进行了分类,这些分类包括UIViewGeometryUIViewHierarchyUIViewRendering等。

除此之外,Category还有几个应用场景:

  • 模拟多继承(另外可以模拟多继承的还有protocol
  • framework私有方法公开:什么叫把framework的私有方法公开呢?首先我们都知道类中的私有方法是外界调用不到的,所以假如我们有一个类,类中有一个私有方法A外界不能直接调用,但是假如我们写一个分类并在分类中也声明了一个方法A(我们只声明,但是没有实现)。现在我们如果import这个分类category的话是不是就可以调用分类中的方法A了,实际上这时候就会调用私有方法这个A,我们也就通过分类将私有方法公开化了。

Category特点

  • category只能给某个已有的类扩充方法,不能扩充成员变量。
  • category中也可以添加属性,只不过@property只会生成settergetter的声明,不会生成settergetter的实现以及成员变量。
  • 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。 也就是,category中的方法会覆盖掉类中原有的方法。 所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。 比如category_。
  • 如果多个category中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。

所以关于方法的调用优先级:
分类(category) > 本类 > 父类。即,优先调用cateory中的方法,然后调用本类方法,最后调用父类方法。
注意⚠️:category是在运行时加载的,不是在编译时。

Category的实质以及实现过程

Category的使用方法十分简单,如果感兴趣可以看看iOS——Category的使用方法,所以我们这里重点讲一下Category的实质以及实现过程,首先我们需要定义一个分类:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface People : NSObject
- (void)talk;

@end

#import "People.h"

@implementation People
- (void)talk {
   
    NSLog(@"我还不太会说话");
}
@end


#import "People.h"

NS_ASSUME_NONNULL_BEGIN

@interface People (Speak)
-(void)speak;

@end
NS_ASSUME_NONNULL_END



#import "People+Speak.h"

@implementation People (Speak)
- (void)speak {
   
    NSLog(@"我已经学会说话了");
}
@end

我们将People+Speak.m文件转成C++代码:
首先我们需要cd /进入People+Speak.m文件的上一级文件夹,然后再xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc People+Speak.m这样就会产生一个C++文件

接着我们就可以分析对应的C++源码了

#ifndef _REWRITER_typedef_People
#define _REWRITER_typedef_People
typedef struct objc_object People;
typedef struct {
   } _objc_exc_People;
#endif

struct People_IMPL {
   
	struct NSObject_IMPL NSObject_IVARS;
};

// - (void)talk;
/* @end */

#pragma clang assume_nonnull end

#pragma clang assume_nonnull begin
// @interface People (Speak)
// -(void)speak;
/* @end */

#pragma clang assume_nonnull end

// @implementation People (Speak)

static void _I_People_Speak_speak(People * self, SEL _cmd) {
   
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_hq_s56n6rsn2kq_8lvr0p4_2fvr0000gn_T_People_Speak_2adca8_mi_0);
}
// @end

struct _prop_t {
   
	const char *name;
	const char *attributes;
};

struct _protocol_t;

struct _objc_method {
   
	struct objc_selector * _cmd;
	const char *method_type;
	void  *_imp;
};

struct _protocol_t {
   
	void * isa;  // NULL
	const char *protocol_name;
	const struct _protocol_list_t * protocol_list; // super protocols
	const struct method_list_t *instance_methods;
	const struct method_list_t *class_methods;
	const struct method_list_t *optionalInstanceMethods;
	const struct method_list_t *optionalClassMethods;
	const struct _prop_list_t * properties;
	const unsigned int size;  // sizeof(struct _protocol_t)
	const unsigned int flags;  // = 0
	const char ** extendedMethodTypes;
};

struct _ivar_t {
   
	unsigned long int *offset;  // pointer to ivar offset location
	const char *name;
	const char *type;
	unsigned int alignment;
	unsigned int  size;
};

struct _class_ro_t {
   
	unsigned int flags;
	unsigned int instanceStart;
	unsigned int instanceSize;
	const unsigned char *ivarLayout;
	const char *name;
	const struct _method_list_t *baseMethods;
	const struct _objc_protocol_list *baseProtocols;
	const struct _ivar_list_t *ivars;
	const unsigned char *weakIvarLayout;
	const struct _prop_list_t *properties;
};

struct _class_t {
   
	struct _class_t *isa;
	struct _class_t *superclass;
	void *cache;
	void *vtable;
	struct _class_ro_t *ro;
};

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;
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)

static struct /*_method_list_t*/ {
   
	unsigned int entsize;  // sizeof(struct _objc_method)
	unsigned int method_count;
	struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = {
   
	sizeof(_objc_method),
	1,
	{
   {
   (struct objc_selector *)"speak", "v16@0:8", (void *)_I_People_Speak_speak}}
};

extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_People;

static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
   
	"People",
	0, // &OBJC_CLASS_$_People,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
	0,
	0,
	0,
};
static void OBJC_CATEGORY_SETUP_$_People_$_Speak(void ) {
   
	_OBJC_$_CATEGORY_People_$_Speak.cls = &OBJC_CLASS_$_People;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
   
	(void *)&OBJC_CATEGORY_SETUP_$_People_$_Speak,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
   
	&_OBJC_$_CATEGORY_People_$_Speak,
};
static struct IMAGE_INFO {
    unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = {
    0, 2 };

我们看一下比较重要的几个地方

Category结构体

【category结构体】
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;//属性列表
};

通过分类结构我们可以看到,分类里可以添加实例方法,类方法,遵循协议,定义属性。此时我们应该会产生一个疑问,为什么没有成员变量列表?这个问题我们后面会解答。

Category结构体赋值

static struct _category_t _OBJC_$_CATEGORY_People_$_Speak __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
   
	"People",
	0, // &OBJC_CLASS_$_People,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_People_$_Speak,
	0,
	0,
	0,
};

我们可以看到如果分类没有添加的东西此时并不会赋值,或者说赋值为0, 因为这个分类我只添加了一个对象方法,所以只给对应的对象方法进行了赋值。

然后就是一个结构体数组

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
   
	&_OBJC_$_CATEGORY_People_$_Speak,
};

由此可见分类的底层结构也是结构体,那么我们来看看类的对象调用分类的方法的过程是怎么样的呢

Category的加载处理过程

首先我们要知道Category的加载处理过程是什么样的,主要分为三步,如下所示:

  1. 通过Runtime加载某个类的所有Category数据
  2. 把所有Category的方法、属性、协议数据,合并到一个大数组中 后面参与编译的Category数据,会在数组的前面
  3. 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

一步一步来看
首先通过Runtime加载某个类的所有Category数据,那我们就从Runtime初始化函数开始看

void _objc_init(void)
{
   
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?Fixme延迟初始化直到找到一个使用objc的图像?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值