iOS底層-18:類的拓展 & 關(guān)聯(lián)對象 底層探索

類別和類拓展

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)。
添加一個LRPersoncategory,在分類里添加一個屬性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_namesetter方法

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_namegetter 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

  • Resultfalse

    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


打印TheBucketNumBuckets為4

12.調(diào)用incrementNumEntries

13.返回InsertIntoBucket
打印TheBucket


-getFirst()后,打印TheBucket,沒有變化

  • getSecond()后,打印TheBucket,沒有變化

14.返回try_emplace


打印TheBucket,沒有變化

15.makeIteratormake_pair組裝后返回_object_set_associative_reference

  • 返回值為ture,設(shè)置isa的關(guān)聯(lián)對象位

  • refs賦值

  • 打印refs_result

  • 打印refs

16.進入try_emplace查找插入key對應(yīng)的值ObjcAssociation
流程與前面大致相同


此時TheBucketObjcAssociation類型

17.LookupBucketFor返回false,回到try_emplace

18.開始插入InsertIntoBucket

  • 執(zhí)行完InsertIntoBucketImpl,返回值為false
    打印TheBucket

    key、policy、value的值已經(jīng)設(shè)置進了TheBucket

20.返回try_emplace,組裝TheBucket

  1. 返回_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 (這里有policyvalue)
5.通過 ObjectAssociationMap拿到他的policy看是否需要retain

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容