1. 类拓展和分类
category 类别/分类:
- 专门用来给类添加新的方法。
- 不能给类添加成员属性,添加了成员变量,也无法取到。
- 可通过
runtime
给分类添加属性。 - 分类中用
@proprty
定义变量,不生成getter
和setter
方法和带下划线的成员变量。
extension 类拓展:
- 可以说是特殊的分类,也称作匿名分类。
- 可以添加属性和方法,但都是私有的。
举个栗子🌰,在main.m
中做如下声明
#import <Foundation/Foundation.h>
/**********************主类声明***************************/
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
- (void)kc_instanceMethod1;
- (void)kc_instanceMethod2;
- (void)kc_instanceMethod3;
@end
/**********************类拓展***************************/
/// 只能放这里
@interface LGPerson()
@property (nonatomic, copy) NSString *ex_name;
- (void)kc_exinstanceMethod1;
@end
/**********************主类实现***************************/
@implementation LGPerson
- (void)kc_instanceMethod3{
}
- (void)kc_instanceMethod1{
}
- (void)kc_instanceMethod2{
}
- (void)kc_exinstanceMethod1
{
}
@end
/**********************分类声明***************************/
@interface LGPerson (CA)
@property (nonatomic, copy) NSString *cate_name;
- (void)kc_cateMethod1;
- (void)kc_cateMethod2;
- (void)kc_cateMethod3;
@end
/**********************分类实现***************************/
@implementation LGPerson (CA)
- (void)kc_cateMethod1
{
}
- (void)kc_cateMethod2
{
}
- (void)kc_cateMethod3
{
}
@end
/**********************main函数***************************/
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [[LGPerson alloc] init];
// person.cate_name = @"";
NSLog(@"");
// Setup code that might create autoreleased objects goes here.
}
return 0;
}
通过下面终端命令进行编译,查看main.cpp
文件。
clang -rewrite-objc main.m -o main.cpp
编译后的LGPerson
属性包括本类声明中的kc_name
和扩展中的ex_name
,还有这两个属性的getter
和setter
方法,也包括分类中的kc_exinstanceMethod1
方法。
LGPerson (CA)
分类编译后_prop_list_t
生成cate_name
属性,与_method_list_t
方法列表生成kc_cateMethod1/2/3
三个方法,并没有生成cate_name
的getter
和setter
方法,也就是没法直接访问cate_name
属性,一般分类中都是通过objc_setAssociatedObject()
方法关联属性给分类添加属性。
main.m
中声明的类
和分类
都是非懒加载分类,结合上一篇类、分类的加载文章探索的结果可知:LGPerson
类会在此类调用的第一个方法时进行类的初始化,属性
和方法
列表在编译时就放入data()
中,在methodizeClass
方法下断点来验证下。
(lldb) p list
(method_list_t *) $0 = 0x0000000100008038
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 12
first = {
name = "kc_cateMethod1"
types = 0x0000000100003f86 "v16@0:8"
imp = 0x0000000100003cf0 (KCObjc`-[LGPerson(CA) kc_cateMethod1] at main.m:57)
}
}
}
(lldb) p $1.get(0)
(method_t) $2 = {
name = "kc_cateMethod1"
types = 0x0000000100003f86 "v16@0:8"
imp = 0x0000000100003cf0 (KCObjc`-[LGPerson(CA) kc_cateMethod1] at main.m:57)
}
(lldb) p $1.get(1)
(method_t) $3 = {
name = "kc_cateMethod2"
types = 0x0000000100003f86 "v16@0:8"
imp = 0x0000000100003d00 (KCObjc`-[LGPerson(CA) kc_cateMethod2] at main.m:61)
}
(lldb) p $1.get(2)
(method_t) $15 = {
name = "kc_instanceMethod2"
types = 0x0000000100003f86 "v16@0:8"
imp = 0x0000000100003bb0 (KCObjc`-[LGPerson kc_instanceMethod2] at main.m:35)
}
(lldb) p $1.get(3)
(method_t) $16 = {
name = "kc_exinstanceMethod1"
types = 0x0000000100003f86 "v16@0:8"
imp = 0x0000000100003bc0 (KCObjc`-[LGPerson kc_exinstanceMethod1] at main.m:39)
}
(lldb) p $1.get(11)
(method_t) $4 = {
name = ".cxx_destruct"
types = 0x0000000100003f86 "v16@0:8"
imp = 0x0000000100003cb0 (KCObjc`-[LGPerson .cxx_destruct] at main.m:28)
}
(lldb) p ro
(const class_ro_t *) $5 = 0x00000001000081e0
(lldb) p $5 ->ivars
(const ivar_list_t *const) $6 = 0x0000000100008228
(lldb) p *$6
(const ivar_list_t) $7 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x0000000100008288
name = 0x0000000100003ea6 "_kc_name"
type = 0x0000000100003f7a "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
offset = 0x0000000100008288
name = 0x0000000100003ea6 "_kc_name"
type = 0x0000000100003f7a "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
offset = 0x0000000100008290
name = 0x0000000100003eaf "_ex_name"
type = 0x0000000100003f7a "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p proplist
(property_list_t *) $10 = 0x0000000100008160
(lldb) p *$10
(property_list_t) $11 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 3
first = (name = "cate_name", attributes = "T@\"NSString\",C,N")
}
}
(lldb) p $11.get(0)
(property_t) $12 = (name = "cate_name", attributes = "T@\"NSString\",C,N")
(lldb) p $11.get(1)
(property_t) $13 = (name = "ex_name", attributes = "T@\"NSString\",C,N,V_ex_name")
(lldb) p $11.get(2)
(property_t) $14 = (name = "kc_name", attributes = "T@\"NSString\",C,N,V_kc_name")
baseMethods()
中已经包含kc_exinstanceMethod1
类拓展方法,ivars
中没有cate_name
,proplist
中包含。
我们知道类拓展中的方法是不能直接调用的,编译器会报错,那用[person performSelector:@selector(kc_exinstanceMethod1)];
底层是objc_msgSend
,会能调用到方法吗?方法慢速查找流程中分析过,会从cls->data()->methods()
中通过二分查找寻找方法,kc_exinstanceMethod1
方法已经在data()->methods()
中,来验证下。
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [[LGPerson alloc] init];
[person performSelector:@selector(kc_exinstanceMethod1)];
NSLog(@"");
}
return 0;
}
// LGPerson中实现
- (void)kc_exinstanceMethod1
{
NSLog(@"%s",__func__);
}
2.关联对象
一般都是通过下面方法实现分类添加属性,这篇文章主要研究下objc_setAssociatedObject
是怎么实现关联对象的。
@implementation LGPerson (CA)
- (void)setCate_name:(NSString *)cate_name
{
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)cate_name
{
return objc_getAssociatedObject(self, "cate_name");
}
@end
查看源码:
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
// SetAssocHook声明
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
SetAssocHook
其实就是_base_objc_setAssociatedObject
,来验证下。
_object_set_associative_reference源码:
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));
// 包装object
DisguisedPtr<objc_object> disguised{(objc_object *)object};
// 包装policy, value
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock. // 对 OBJC_ASSOCIATION_SETTER_RETAIN 和OBJC_ASSOCIATION_SETTER_COPY策略的关联对象进行处理
association.acquireValue();
{
AssociationsManager manager; // 非全局变量
AssociationsHashMap &associations(manager.get()); // 全局map
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{}); // 返回的是类对,第二个参数为bool
if (refs_result.second) {
/* it's the first association we make */ //标记 isa has_assoc设置为true
object->setHasAssociatedObjects();
}
/* establish or replace the association 建立或者替换关联*/
auto &refs = refs_result.first->second; //得到空的桶子,找到引用对象类型。
auto result = refs.try_emplace(key, std::move(association)); //查找当前的key是否有association关联对象
if (!result.second) {
association.swap(result.first->second);
}
} else { // 如果 value为空则移除关联。
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);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
主要流程为:
- 1: 创建一个
AssociationsManager
管理类 - 2: 获取唯一的全局静态哈希
Map
- 3: 判断是否插入的关联值是否存在:
3.1: 存在走第4步
3.2: 不存在就走 : 关联对象插入空流程 - 4: 创建一个空的
ObjectAssociationMap
去取查询的键值对 - 5: 如果发现没有这个
key
就插入一个 空的BucketT
进去 返回 - 6: 标记对象存在关联对象
- 7: 用当前 修饰策略和值 组成了一个
ObjcAssociation
替换原来 BucketT 中的空 - 8: 标记一下
ObjectAssociationMap
的第一次为false
关联对象插入空流程:
- 1: 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 2: 清理迭代器
- 3: 其实如果插入空值相当于清除
取值流程:
- 1: 创建一个
AssociationsManager
管理类 - 2: 获取唯一的全局静态哈希
Map
- 3: 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 4: 如果这个迭代查询器不是最后一个 获取 :
ObjectAssociationMap
(这里有策略和value
) 5: 找到ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value
- 6: 返回
_value