類別和類拓展
1.category:類別 分類
專門用來給類添加新的方法不能給類添加成員變量,添加了也無法取到-
分類中使用了@property給類添加屬性,只會生成setter getter方法聲明,沒有實現(xiàn),也不會生成帶下劃線的成員變量可以通過
runtime關(guān)聯(lián)對象給分類添加屬性
2.extension:類拓展
- 可以說是特殊的分類,也稱作匿名分類
- 可以給類添加方法,但是是私有方法
- 可以給類添加成員變量,但是是私有變量
- 可以給類添加屬性,但是是私有屬性
類拓展
1.在.m中寫一個類拓展
@interface LRTeacher : NSObject
@end
@implementation LRTeacher
@end
@interface LRTeacher ()
@end
編譯會報錯:類的拓展不能在實現(xiàn)implementation之后

我們把類拓展寫在interface類的聲明之前
@class LRTeacher;
@interface LRTeacher ()
@end
@interface LRTeacher : NSObject
@end
@implementation LRTeacher
@end
還是會報錯,對于不明確的類不能添加類拓展

結(jié)論:類拓展(extension)只能寫在@interface之后@implemention之前
2.類拓展的本質(zhì)
在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方法
總結(jié):
在extension中添加的方法和屬性與在.h文件@interface中添加的并無二致。
編譯時,自動把extension中的方法和屬性并入了類中。
關(guān)聯(lián)對象
category添加屬性時,只是添加了getter setter的聲明,并沒有實現(xiàn)。
添加一個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中調(diào)用一下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運行時,發(fā)生了錯誤
-[LRPerson setCate_name:]: unrecognized selector sent to instance 0x10350a090
這充分說明了category中添加的屬性,只是聲明了getter setter,并沒有實現(xiàn)。
- 通過關(guān)聯(lián)對象實現(xiàn)
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調(diào)試到下一步,進入了_base_objc_setAssociatedObject
這是為什么呢?
查看SetAssocHook的源碼
調(diào)用SetAssocHook實際上就是調(diào)用_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<objc_object> disguised{(objc_object *)object};
//包裝了一下 policy 和 value ,初始化一個association
ObjcAssociation association{policy, value};
//當策略類似是retain或者copy的時候進行對應(yīng)處理
association.acquireValue();
{
//構(gòu)造方法 初始化一個manager
AssociationsManager manager;
// 獲取hashMap 全場唯一
AssociationsHashMap &associations(manager.get());
if (value) {
//第一個參數(shù)是 對象包裝后的結(jié)構(gòu)
//第二個參數(shù) 創(chuàng)建一個空的map 用來接收查找結(jié)果
//try_emplace返回的是一個含有3個變量的結(jié)構(gòu)
//通過對象在整個hashMap里查找該對象的關(guān)聯(lián)map ObjectAssociationMap
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
//第一次進來時 設(shè)置isa的has_assoc位
object->setHasAssociatedObjects();
}
//獲取該對象的關(guān)聯(lián)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是空值 擦除里面數(shù)據(jù)
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 <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;//創(chuàng)建一個空的BucketT 來接收查找結(jié)果
if (LookupBucketFor(Key, TheBucket)) //找到了的話,返回false
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
//沒有找到的話就插入 返回true
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
- 點擊進入
LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket)
template <typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
const BucketT *ConstFoundBucket;//接收查詢結(jié)果
bool Result = const_cast<const DenseMapBase *>(this)
->LookupBucketFor(Val, ConstFoundBucket);//調(diào)用LookupBucketFor,參數(shù)不同 方法重載
FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
return Result;
}
- 點擊進入
LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const
template<typename LookupKeyT>
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 <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
TheBucket->getFirst() = std::forward<KeyArg>(Key);
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
InsertIntoBucketImpl
template <typename LookupKeyT>
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;
}
斷點調(diào)試
從_object_set_associative_reference如下斷點開始

1.進入try_emplace查找插入對象對應(yīng)的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)

此時打?。?/p>
-
ConstFoundBucket
-
給
FoundBucket賦值后打印
image.png -
Result為false
image.png
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.調(diào)用incrementNumEntries

13.返回InsertIntoBucket
打印TheBucket

-
getFirst()后,打印TheBucket,沒有變化
-
getSecond()后,打印TheBucket,沒有變化
14.返回try_emplace

打印
TheBucket,沒有變化
15.makeIterator和make_pair組裝后返回_object_set_associative_reference

-
返回值為
ture,設(shè)置isa的關(guān)聯(lián)對象位
-
給
refs賦值
-
打印
refs_result
-
打印
refs
16.進入try_emplace查找插入key對應(yīng)的值ObjcAssociation
流程與前面大致相同

此時
TheBucket是ObjcAssociation類型
17.LookupBucketFor返回false,回到try_emplace

18.開始插入InsertIntoBucket
- 執(zhí)行完
InsertIntoBucketImpl,返回值為false
打印TheBucket
key、policy、value的值已經(jīng)設(shè)置進了TheBucket
20.返回try_emplace,組裝TheBucket
- 返回
_object_set_associative_reference
打印result
關(guān)聯(lián)屬性成功,setter方法講解完畢,下面我們分析getter方法
-
點擊進入
objc_getAssociatedObject
點擊進入
_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};//創(chuàng)建一個空的ObjcAssociation
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());//得到全局唯一的AssociationsHashMap
AssociationsHashMap::iterator i = associations.find((objc_object *)object);//通過對象拿到該對象的關(guān)聯(lián)map
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;//取到該對象的關(guān)聯(lián)map ObjectAssociationMap
ObjectAssociationMap::iterator j = refs.find(key);//在對象的ObjectAssociationMap中通過key查找值
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();//執(zhí)行retain操作,如果policy是retain的話
}
}
}
return association.autoreleaseReturnedValue();
}
總結(jié)
設(shè)值流程
1.創(chuàng)建一個AssociationsManager管理類
2.獲取全局唯一的靜態(tài)哈希Map (AssociationsHashMap)
3.判斷插入的關(guān)聯(lián)值value是否存在
3.1 存在,走第4步
3.2 不存在,走關(guān)聯(lián)對象插入空流程
4.創(chuàng)建一個空的 ObjectAssociationMap 去取查詢的鍵值對
5.如果沒有發(fā)現(xiàn)這個Key,就插入一個 空的 BucketT進去 返回
6.標記對象isa,存在關(guān)聯(lián)對象
7.用當前的 policy和_value組成一個ObjcAssociation替換原來空的BucketT
8.根據(jù)policy判斷,setter方法結(jié)束是否需要release
關(guān)聯(lián)對象插入空流程
1.根據(jù) DisguisedPtr 找到 AssociationsHashMap 中的 iterator迭代查詢器
2.清理迭代器
3.插入空值相當于清除
取值流程
1.創(chuàng)建一個AssociationsManager管理類
2.獲取全局唯一的靜態(tài)哈希Map (AssociationsHashMap)
3.根據(jù) DisguisedPtr 找到 AssociationsHashMap 中的 iterator迭代查詢器
4.如果這個迭代查詢器不是最后一個,獲取 ObjectAssociationMap (這里有policy和value)
5.通過 ObjectAssociationMap拿到他的policy看是否需要retain

















