类扩展与分类的区别
category:类别、分类
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员变量,也无法获取到
- 可以通过runtime给分类添加属性
- 分类中用@property定义变量,只会生成变量的getter、setter方法的声明,不能自动生成方法实现和带下划线的成员变量
extension:类扩展
- 可以说成是特殊的分类,也称作匿名分类
- 可以给类添加成员属性,但是是私有变量
- 可以给类添加方法,也是私有方法
- 类扩展要写在类的声明之后,实现之前(如果在类中直接添加扩展)
示例代码
@interface FFBoys : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface FFBoys (BBLv)
@property (nonatomic, copy) NSString * ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation FFBoys
- (void)instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)classMethod {
NSLog(@"%s",__func__);
}
- (void)ext_instanceMethod {
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
FFPerson *objc = [FFPerson alloc];
[objc likeFood];
}
return 0;
}
通过.cpp文件查看extension的表现
分类在编译成c++的阶段已经被注释了
#ifndef _REWRITER_typedef_FFBoys
#define _REWRITER_typedef_FFBoys
typedef struct objc_object FFBoys;
typedef struct {} _objc_exc_FFBoys;
#endif
extern "C" unsigned long OBJC_IVAR_$_FFBoys$_name;
extern "C" unsigned long OBJC_IVAR_$_FFBoys$_age;
struct FFBoys_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
NSString *_name;
};
// @property (nonatomic, copy) NSString * name;
// @property (nonatomic, assign) int age;
// - (void)instanceMethod;
// + (void)classMethod;
/* @end */
// @interface FFBoys (BBLv)
// @property (nonatomic, copy) NSString * ext_name;
// @property (nonatomic, assign) int ext_age;
// - (void)ext_instanceMethod;
// + (void)ext_classMethod;
/* @end */
// @implementation FFBoys
实例方法和类方法直接被合成到类和元类的方法列表里面了
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[10];
} _OBJC_$_INSTANCE_METHODS_FFBoys __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
10,
{{(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_FFBoys_instanceMethod},
{(struct objc_selector *)"ext_instanceMethod", "v16@0:8", (void *)_I_FFBoys_ext_instanceMethod},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_FFBoys_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_FFBoys_setName_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_FFBoys_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_FFBoys_setAge_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_FFBoys_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_FFBoys_setName_},
{(struct objc_selector *)"age", "i16@0:8", (void *)_I_FFBoys_age},
{(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_FFBoys_setAge_}}
};
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_CLASS_METHODS_FFBoys __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"classMethod", "v16@0:8", (void *)_C_FFBoys_classMethod},
{(struct objc_selector *)"ext_classMethod", "v16@0:8", (void *)_C_FFBoys_ext_classMethod}}
};
通过lldb动态调试源码查看extension的表现
关联对象
当分类声明了属性的时候,是不会自动生成setter、getter方法和带有下划线的成员变量的,属性的赋值就无法完成,这个时候分类会报出警告,提示你缺少getter、setter方法
手动实现getter、setter方法
@implementation FFPerson (BBLv)
- (NSString *)cate_name {
return objc_getAssociatedObject(self, "cate_name");
}
- (void)setCate_name:(NSString *)cate_name {
objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_hobby {
return objc_getAssociatedObject(self, "cate_hobby");
}
- (void)setCate_hobby:(NSString *)cate_hobby {
objc_setAssociatedObject(self, "cate_age", cate_hobby, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
源码
通过找objc_setAssociatedObject找到了下一层的实现函数_object_set_associative_reference,这里在不同的Objc4源码里面的实现都有差异,例如779版本、756版本与当前818版本都是不一样的,这样做的道理做到了API稳定,在下层的实现过程中,源码都是在变的,但是在上层调用的过程中是不变的,这是一种简单的架构思维。
objc_setAssociatedObject
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
_object_set_associative_reference
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// 当为对象和键传递 nil 时,此代码曾经起作用。一些代码可能依赖于它不会崩溃。明确检查和处理。
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};
ObjcAssociation association{policy, value};
// 在锁外保留新值(如果有)。
association.acquireValue();
bool isFirstAssociation = false;
//关联对象的核心代码
{
//针对这个{}作用域通过构造与析构函数进行锁操作
AssociationsManager manager;
//单例的方式获取AssociationsHashMap关联表
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 */
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);
}
}
}
}
}
/**
在锁外调用 setHasAssociatedObjects ,因为如果它有一个,
这将调用对象的 _noteAssociatedObjects 方法,
这可能会触发 +initialize ,这可能会做任意的事情,包括设置更多关联的对象。
*/
if (isFirstAssociation)
object->setHasAssociatedObjects();
// 释放旧值(锁外)。
association.releaseHeldValue();
}
_object_set_associative_reference包含了4个参数:
- id object:被关联者
- const void *key:key值,比如cate_name
- id value:value值
- objc_AssociationPolicy policy:缓存策略,对应声明属性设定的retain、copy等
try_emplace
在AssociationsHashMap中找到ObjectAssociationMap,判断值是否在映射表中,如果不在则将键插入,如果键不在,则创建一个bucket,一个空的空间。这个bucket就是ObjcAssociation,里面包含了_policy、_value。
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);
}
AssociationsManager
AssociationsManager manager;manager并不是单例,只是针对这个{}作用域通过构造与析构函数进行锁操作,负责管理AssociationsHashMap这个哈希表单例。
AssociationsHashMap这个哈希表才是单例!!!
static Storage _mapStorage;这是一个全局静态变量_mapStorage的声明,跟是否写AssociationsManager内并无任何关系,写在内部意味着需要用AssociationsManager去调用,A创建AssociationsManager与B创建AssociationsManager的对象调用_mapStorage是一模一样的,因为AssociationsHashMap是通过return _mapStorage.get()得到的,所以AssociationsHashMap这张表是全局唯一的。
// 类 AssociationsManager 管理锁/哈希表单例对。
// 分配实例获取锁
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
模仿操作
struct BBLvManager {
BBLvManager() {NSLog(@"开始");}
~BBLvManager() {NSLog(@"结束");}
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
BBLvManager bblvManager;
NSLog(@"Hello, World!");
}
return 0;
}
执行结果
2021-08-03 16:27:17.876511+0800 KCObjcBuild[6325:172143] 开始
2021-08-03 16:27:17.876601+0800 KCObjcBuild[6325:172143] Hello, World!
2021-08-03 16:27:21.097917+0800 KCObjcBuild[6325:172143] 结束
疑问点二:为什么底层源码总是喜欢在调用的地方再次嵌套一层实现,而不是直接实现?
类的关联对象结构图
根据图可以得知关联对象是双层hashmap的结构:
- 第一层:AssociationsHashMap包含多个对象的ObjectAssociationMap
- 第二层:ObjectAssociationMap包含多个ObjcAssociation
- 每一个ObjcAssociation都有policy与value
解决探索过程中的疑问点
疑问点一:关联对象存在的意义是什么?
用于存储,由于关联对象没有getter、setter方法,无法像正常的类声明的对象,可以通过指针偏移找到其具体的地址进行getter、setter操作,由于关联对象是动态添加的属性,只能通过关联对象来进行存储,模拟getter、setter操作。
疑问点二:为什么底层源码总是喜欢在调用的地方再次嵌套一层实现,而不是直接实现?
通过一段时间对底层源码探索,发现不同版本的源码改动是有一些的,但是并不对上层暴露给开发者的API有影响,也就是说不管底层代码怎样改动,都保证了上层API的稳定。这也方便了改动下层代码的时候,控制中间层参数就可以搞定了。在设计上最大程度上保证开发者不需要每次都需要跟随系统api的更细而改动自己的代码。SDK的设计思维也是如此,要最大程度上的保证使用者的便利性。
类扩展写在声明之后,实现之前