类别和类拓展
1.category:类别 分类
专门用来给类添加新的方法
不能给类添加成员变量,添加了也无法取到
-
分类中使用了@property给类添加属性,只会生成setter getter方法声明,没有实现,也不会生成带下划线的成员变量
可以通过
runtime
关联对象给分类添加属性
2.extension:类拓展
- 可以说是特殊的分类,也称作匿名分类
- 可以给类添加方法,但是是私有方法
- 可以给类添加成员变量,但是是私有变量
- 可以给类添加属性,但是是私有属性
类拓展
1.在.m中写一个类拓展
@interface LRTeacher : NSObject
@end
@implementation LRTeacher
@end
@interface LRTeacher ()
@end
编译会报错:类的拓展不能在实现implementation
之后
我们把类拓展写在interface
类的声明之前
@class LRTeacher;
@interface LRTeacher ()
@end
@interface LRTeacher : NSObject
@end
@implementation LRTeacher
@end
还是会报错,对于不明确的类不能添加类拓展
结论:类拓展(extension
)只能写在@interface
之后@implemention
之前
2.类拓展的本质
在main.m
中添加一下代码,然后用clang
编译。
@interface LRTeacher : NSObject
@end
@interface LRTeacher ()
@property (nonatomic,copy) NSString * name;
@property (nonatomic,strong) NSArray * array;
- (void)instanceMethod;
- (void)classMethod;
@end
@implementation LRTeacher
- (void)instanceMethod {
printf("%s",__func__);
}
- (void)classMethod {
printf("%s",__func__);
}
@end
打开main.cpp
文件,搜索LRTeacher
- 属性生成了下划线成员变量,和
setter、getter
方法
总结:
在extension
中添加的方法和属性与在.h
文件@interface
中添加的并无二致。
编译时,自动把extension
中的方法和属性并入了类中。
关联对象
category
添加属性时,只是添加了getter setter
的声明,并没有实现。
添加一个LRPerson
的category
,在分类里添加一个属性cate_name;
@interface LRPerson : NSObject
@end
@implementation LRPerson
@end
@interface LRPerson (LR)
@property (nonatomic,copy) NSString *cate_name;
@end
@implementation LRPerson (LR)
@end
在main.m
中调用一下cate_name
的setter
方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
LRPerson *lr = [LRPerson alloc];
lr.cate_name = @"LR";
}
return 0;
}
command+B
编译成功
command+R
运行时,发生了错误
-[LRPerson setCate_name:]: unrecognized selector sent to instance 0x10350a090
这充分说明了category
中添加的属性,只是声明了getter setter
,并没有实现。
- 通过关联对象实现
category
中的getter setter
在category
重写cate_name
的getter setter
方法
- (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");
}
command+R
运行程序,程序运行成功,并没有报错。
底层探索
-
在
setter
方法处加上断点,运行程序
-
点击进入
objc_setAssociatedObject
-
点击进入
get()
-
通过
step into
调试到下一步,进入了_base_objc_setAssociatedObject
这是为什么呢?
查看SetAssocHook
的源码
调用SetAssocHook
实际上就是调用_base_objc_setAssociatedObject
点击进入
_object_set_associative_reference
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
//传入的object 和 value 有一个为空就返回
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));
//包装了一下 对象
DisguisedPtr disguised{(objc_object *)object};
//包装了一下 policy 和 value ,初始化一个association
ObjcAssociation association{policy, value};
//当策略类似是retain或者copy的时候进行对应处理
association.acquireValue();
{
//构造方法 初始化一个manager
AssociationsManager manager;
// 获取hashMap 全场唯一
AssociationsHashMap &associations(manager.get());
if (value) {
//第一个参数是 对象包装后的结构
//第二个参数 创建一个空的map 用来接收查找结果
//try_emplace返回的是一个含有3个变量的结构
//通过对象在整个hashMap里查找该对象的关联map ObjectAssociationMap
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
//第一次进来时 设置isa的has_assoc位
object->setHasAssociatedObjects();
}
//获取该对象的关联map ObjectAssociationMap
auto &refs = refs_result.first->second;
//在ObjectAssociationMap中通过key去查找
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);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
try_emplace
template
std::pair try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;//创建一个空的BucketT 来接收查找结果
if (LookupBucketFor(Key, TheBucket)) //找到了的话,返回false
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
//没有找到的话就插入 返回true
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
- 点击进入
LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
template
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
const BucketT *ConstFoundBucket;//接收查询结果
bool Result = const_cast(this)
->LookupBucketFor(Val, ConstFoundBucket);//调用LookupBucketFor,参数不同 方法重载
FoundBucket = const_cast(ConstFoundBucket);
return Result;
}
- 点击进入
LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const
template
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
// If this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
InsertIntoBucket
template
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
TheBucket->getFirst() = std::forward(Key);
::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...);
return TheBucket;
}
InsertIntoBucketImpl
template
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
BucketT *TheBucket) {
// If the load of the hash table is more than 3/4, or if fewer than 1/8 of
// the buckets are empty (meaning that many are filled with tombstones),
// grow the table.
//
// The later case is tricky. For example, if we had one empty bucket with
// tons of tombstones, failing lookups (e.g. for insertion) would have to
// probe almost the entire table until it found the empty bucket. If the
// table completely filled with tombstones, no lookup would ever succeed,
// causing infinite loops in lookup.
unsigned NewNumEntries = getNumEntries() + 1;
unsigned NumBuckets = getNumBuckets();
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
this->grow(NumBuckets * 2);
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
NumBuckets/8)) {
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
// Only update the state after we've grown our bucket space appropriately
// so that when growing buckets we have self-consistent entry count.
// If we are writing over a tombstone or zero value, remember this.
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
incrementNumEntries();
decrementNumTombstones();
} else {
// we should be purging a zero. No accounting changes.
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
断点调试
从_object_set_associative_reference
如下断点开始
1.进入try_emplace
查找插入对象对应的ObjectAssociationMap
2.进入bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
3.进入bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const
没有找到返回false
4.回到bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
此时打印:
-
ConstFoundBucket
-
给
FoundBucket
赋值后打印
-
Result
为false
5.回到try_emplace
返回为false
,会走InsertIntoBucket
方法
6.进入InsertIntoBucket
打印此时的key 和 TheBucket
7.进入InsertIntoBucketImpl
又开始
LookupBucketFor
8.进入bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
9.进入bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const
返回
false
,打印FoundBucket
10.回到bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
11.回到InsertIntoBucketImpl
打印
TheBucket
,NumBuckets
为4
12.调用incrementNumEntries
13.返回InsertIntoBucket
打印TheBucket
-
getFirst()
后,打印TheBucket
,没有变化
-
getSecond()
后,打印TheBucket
,没有变化
14.返回try_emplace
打印
TheBucket
,没有变化
15.makeIterator
和make_pair
组装后返回_object_set_associative_reference
-
返回值为
ture
,设置isa的关联对象位
-
给
refs
赋值
-
打印
refs_result
-
打印
refs
16.进入try_emplace
查找插入key
对应的值ObjcAssociation
流程与前面大致相同
此时
TheBucket
是ObjcAssociation
类型
17.LookupBucketFor
返回false
,回到try_emplace
18.开始插入InsertIntoBucket
- 执行完
InsertIntoBucketImpl
,返回值为false
打印TheBucket
key、policy、value
的值已经设置进了TheBucket
20.返回try_emplace
,组装TheBucket
- 返回
_object_set_associative_reference
打印result
关联属性成功,setter
方法讲解完毕,下面我们分析getter
方法
-
点击进入
objc_getAssociatedObject
点击进入
_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//创建一个空的ObjcAssociation
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());//得到全局唯一的AssociationsHashMap
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//通过对象拿到该对象的关联map
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;//取到该对象的关联map ObjectAssociationMap
ObjectAssociationMap::iterator j = refs.find(key);//在对象的ObjectAssociationMap中通过key查找值
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();//执行retain操作,如果policy是retain的话
}
}
}
return association.autoreleaseReturnedValue();
}
总结
设值流程
1.创建一个AssociationsManager
管理类
2.获取全局唯一的静态哈希Map (AssociationsHashMap
)
3.判断插入的关联值value
是否存在
3.1 存在,走第4步
3.2 不存在,走关联对象插入空流程
4.创建一个空的 ObjectAssociationMap
去取查询的键值对
5.如果没有发现这个Key,就插入一个 空的 BucketT进去 返回
6.标记对象isa,存在关联对象
7.用当前的 policy和_value
组成一个ObjcAssociation
替换原来空的BucketT
8.根据policy
判断,setter方法结束是否需要release
关联对象插入空流程
1.根据 DisguisedPtr
找到 AssociationsHashMap
中的 iterator
迭代查询器
2.清理迭代器
3.插入空值相当于清除
取值流程
1.创建一个AssociationsManager
管理类
2.获取全局唯一的静态哈希Map (AssociationsHashMap
)
3.根据 DisguisedPtr
找到 AssociationsHashMap
中的 iterator
迭代查询器
4.如果这个迭代查询器不是最后一个,获取 ObjectAssociationMap
(这里有policy
和value
)
5.通过 ObjectAssociationMap
拿到他的policy
看是否需要retain