类扩展与分类的区别。
category: 类别,分类
- 专门用来给类添加新的方法
- 不能给类添加成员属性,添加了成员变量,也无法取到(注意:其实可以通过
runtime给分类添加属性) - 分类中用
property定义变量,只会生成变量的getter,setter方法的声明,不能生成方法实现和带下划线的成员变量。
extension: 类扩展
- 可以说成是特殊的分类,也称作匿名分类
- 可以给类添加成员属性,但是是私有变量
- 可以给类添加方法,也是私有方法
类扩展底层代码实现
首先我们在 main.m 文件中定义一个 LGPerson 类,并在类扩展中添加属性与方法。然后通过 clang -rewrite-objc main.m 生成 cpp 文件来看一下底层 c++ 代码的实现。
@interface LGStudent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
- (void)instanceMethod;
+ (void)classMethod;
@end
@interface LGStudent ()
@property (nonatomic, copy) NSString *ext_name;
@property (nonatomic, assign) int ext_age;
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGStudent
- (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 {
LGPerson * person = [LGPerson alloc];
[person saySomething];
}
return 0;
}
生成 cpp 文件之后我们可以看到在类扩展里面添加的方法 ext_instanceMethod 一样被加载到 method_list 中来了。
类扩展是否影响类的加载和编译
前面我们讲分类的时候知道分类会影响类的加载和编译,那么类扩展是否也会影响呢?这里我们为 LGPerson 类添加类扩展并添加方法,然后执行源码并在 realizeClassWithoutSwift 函数中打印 ro 来看一下。
@interface LGPerson ()
- (void)ext_instanceMethod;
+ (void)ext_classMethod;
@end
@implementation LGPerson
+ (void)load{}
- (void)saySomething{
NSLog(@"%s",__func__);
}
+ (void)sayHappy{
NSLog(@"LGPerson say : Happy!!!");
}
- (void)sayHello1{
NSLog(@"sayHello1 %s", __func__);
}
+ (void)say6661{
NSLog(@"say6661 %s", __func__);
}
- (void)ext_instanceMethod{
NSLog(@"%s",__func__);
}
+ (void)ext_classMethod{
NSLog(@"%s",__func__);
}
@end
我们在这里不断 p $1.get(n).big(),当 p $1.get(2).big() 的时候可以看到输出了类扩展中添加的方法 ext_instanceMethod,可以看出类扩展中数据会作为类的一部分被一块加载。
关联对象
- (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");
}
我们通常通过 runtime 的 objc_setAssociatedObject 方法来添加分类的关联对象,那么我们来看一下 objc_setAssociatedObject 方法的底层是如何实现的(这里是 779 版本)。
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 方法,不同版本的源码这里实现会有不同,但是最上层都是调用 objc_setAssociatedObject 方法,这里就提现了苹果的分层思想,也是值得我们学习的,这里就保证了最上层 api 的稳定性。
接着我们继续分析 _object_set_associative_reference 方法。
首先我们先看下 _object_set_associative_reference 方法的简单注释及这张结构体图。通过结构图我们可以看到,ObjcAssociation 跟 ObjcAssociation 会被包装成 ObjcAssociation 的形式,然后通过键值匹配的形式被存储到 ObjectAssociationMap,每个对象会对应一张 ObjectAssociationMap 表,最后每个对象跟对应的ObjectAssociationMap 又会以键值的形式存到 AssociationsHashMap,这里是双层 hashMap 结构。AssociationsHashMap 是一张全局唯一的表。具体原因我们可以看下 AssociationsManager。
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
// _mapStorage 是一个静态变量,写到这里代表只有 AssociationsManager 才能调起 _mapStorage
// 所以不同的 AssociationsManager 这里调用的都是同一个 _mapStorage
static Storage _mapStorage;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
// 所以得到的 AssociationsHashMap 是唯一的
AssociationsHashMap &get() {
return _mapStorage.get();
}
static void init() {
_mapStorage.init();
}
};
通过打印我们也可以看到 AssociationsHashMap 及 refs_result 的数据结构。这里我们大致了解了关联对象的数据结构,那么 key , value 具体是如何存储的呢?我们接着继续往下看。
if (value) {
// 第一次调用的时候 try_emplace 会创建一个空的 TheBucket
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;
// 第二次再执行的时候会对 TheBucket 的 value 赋值为 association
auto result = refs.try_emplace(key, std::move(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);
}
}
}
}
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
// 这里会先创建一个空的 BucketT
BucketT *TheBucket;
// 这里 key 是包装的对象,被关联对象,会判断能不能找到对应的 TheBucket
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// 找不到就会来到这里会插入一个空的 TheBucket
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
最后总结一下关联对象的设值流程:
关联对象:设值流程
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:其实如果插入空置相当于清除\