天天看點

iOS底層原理篇(四)----拓展、分類、關聯對象

上篇博文底層原理篇(三)我們講到了類的編譯、連結、加載的過程,接下來我們去探索一下類與分類、拓展與分類、關聯對象等的内容!

1.類與分類

  • 分類的結構
    struct category_t {//仔細看一下,沒有ivar的清單哦!!!
      	const char *name; //誰的分類
      	classref_t cls;   //對應的類屬性
      	//以下是方法清單&屬性清單&協定清單
      	struct method_list_t *instanceMethods;
      	struct method_list_t *classMethods;
      	struct protocol_list_t *protocols;
      	struct property_list_t *instanceProperties;
      	struct property_list_t *_classProperties;
      	method_list_t *methodsForMeta(bool isMeta) {
      		if (isMeta) return classMethods;
      		else return instanceMethods;
      	}
      	property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
      };
               

這些屬性,方法,是怎麼和類産生關系的呢?下面我們來看看分類的加載!

iOS底層原理篇(四)----拓展、分類、關聯對象
iOS底層原理篇(四)----拓展、分類、關聯對象
iOS底層原理篇(四)----拓展、分類、關聯對象

我們要區分開類加載與分類加載的不同之處,這個地方很容易混淆!

  • 這裡需要說明一下,我們的分類為什麼不能添加成員變量,這是因為分類結構中并沒有成員變量清單的存儲屬性,分類在運作時被attach到類,是對rw的處理,在class_rw_t *rw中,并沒有const ivar_list_t *ivars成員變量清單屬性!!!這個是在class_ro_t *ro中處理的!是以我們不能在分類中添加成員變量,但是可以添加屬性!!!

2.類拓展

延展類别又稱為拓展(Extension),拓展是分類的一種特例,也叫匿名分類:寫在.m中,沒有名稱!

還有一種寫法:直接建立一個File type為Extension的OC檔案!!也是我們的類拓展!

  • 下面是兩種實作的舉例:
    //1.在.m中
      @interface WMPerson () //沒有名字,我們的分類在這裡的括号裡面是有名稱的
      @property (nonatomic, copy) NSString *mName;//拓展的屬性 
      - (void)extM_method;//拓展的方法(.m實作)
      @end
      
      //2.建立一個File type為Extension的OC檔案WMPerson+WMExtension.h
      //其實和上面的一樣,隻是單獨成為一個檔案
      @interface WMPerson () 
      @property (nonatomic, copy) NSString *ext_name;
      @property (nonatomic, copy) NSString *ext_subject;
      - (void)extH_method;//在類的.m實作
      @end
               
  • 我們在read_images中看到分類的代碼處理邏輯,但是我們沒有看到拓展在哪裡處理的?
  • 那麼我們該怎麼去分析這個過程呢?
  • 我們知道,在read_images中,類加載最初始:
    //Discover classes. 讀取所有的類資訊  在這之後才會根據load方法初始化
      for (EACH_HEADER) {
      	classref_t *classlist = _getObjc2ClassList(hi, &count);
      
      	if (! mustReadClasses(hi)) {
          	// Image is sufficiently optimized that we need not call readClass()
          	continue;
      	}
    
      	bool headerIsBundle = hi->isBundle();
      	bool headerIsPreoptimized = hi->isPreoptimized();
    
      	for (i = 0; i < count; i++) {
          	Class cls = (Class)classlist[i];
          	Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
          	const class_ro_t *ro = (const class_ro_t *)cls->data();
          	const char *cname = ro->name;
          	const char *oname = "WMPerson";
          	if (cname && (strcmp(cname, oname) == 0)) {
              	printf("_getObjc2ClassList 類名 :%s  - %p\n",cname,cls);
          	}
          	if (newCls != cls  &&  newCls) {
              	// Class was moved but not deleted. Currently this occurs 
              	// only when the new class resolved a future class.
              	// Non-lazily realize the class below.
              	resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses, 
                          (resolvedFutureClassCount+1) * sizeof(Class));
              	resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
          	}
      	}
      }
               
    • 我們斷點斷在printf("_getObjc2ClassList 類名 :%s - %p\n",cname,cls);列印處,如果我們的這個類加載了,我們可以去看看他在編譯階段的ro都存了什麼!!!
      //列印結果:
        (lldb) p ro
        (const class_ro_t *) $0 = 0x0000000100002320
        (lldb) p *$0
        (const class_ro_t) $1 = {
        	flags = 388
        	instanceStart = 8
        	instanceSize = 40
        	reserved = 0
        	ivarLayout = 0x0000000100001eda "\x04"
        	name = 0x0000000100001ed1 "WMPerson"
        	baseMethodList = 0x0000000100002140
        	baseProtocols = 0x0000000000000000
        	ivars = 0x0000000100002250
        	weakIvarLayout = 0x0000000000000000
        	baseProperties = 0x00000001000022d8
        	_swiftMetadataInitializer_NEVER_USE = {}
        }
        (lldb) p $1.baseMethodList
        (method_list_t *const) $2 = 0x0000000100002140
        (lldb) p *$2
        (method_list_t) $3 = {
        	entsize_list_tt<method_t, method_list_t, 3> = {
        	entsizeAndFlags = 24
        	count = 11
        	first = {
        		name = "extM_method"
        		types = 0x0000000100001f84 "[email protected]:8"
        		imp = 0x0000000100001950 (objc-debug`-[WMPerson extM_method] at WMPerson.m:24)
        		}
        	}
        }
        (lldb) p $3.get(1)
        (method_t) $4 = {
        	name = "extH_method"
        	types = 0x0000000100001f84 "[email protected]:8"
        	imp = 0x0000000100001980 (objc-debug`-[WMPerson extH_method] at WMPerson.m:28)
        }
        (lldb) p $3.get(2)
        (method_t) $5 = {
        	name = ".cxx_destruct"
        	types = 0x0000000100001f84 "[email protected]:8"
        	imp = 0x0000000100001b70 (objc-debug`-[WMPerson .cxx_destruct] at WMPerson.m:18)
        }
        (lldb) p $3.get(3)
        (method_t) $6 = {
        	name = "name"
        	types = 0x0000000100001f8c "@[email protected]:8"
        	imp = 0x00000001000019b0 (objc-debug`-[WMPerson name] at WMPerson.h:13)
        }
        (lldb) p $3.get(4)
        (method_t) $7 = {
        	name = "setName:"
        	types = 0x0000000100001f94 "[email protected]:[email protected]"
        	imp = 0x00000001000019e0 (objc-debug`-[WMPerson setName:] at WMPerson.h:13)
        }
        (lldb) p $3.get(5)
        (method_t) $8 = {
        	name = "mName"
        	types = 0x0000000100001f8c "@[email protected]:8"
        	imp = 0x0000000100001a20 (objc-debug`-[WMPerson mName] at WMPerson.m:12)
        }
        (lldb) p $3.get(6)
        (method_t) $9 = {
        	name = "setMName:"
        	types = 0x0000000100001f94 "[email protected]:[email protected]"
        	imp = 0x0000000100001a50 (objc-debug`-[WMPerson setMName:] at WMPerson.m:12)
        }
        (lldb) p $3.get(7)
        (method_t) $10 = {
        	name = "ext_name"
        	types = 0x0000000100001f8c "@[email protected]:8"
        	imp = 0x0000000100001a90 (objc-debug`-[WMPerson ext_name] at WMPerson+WMExtension.h:15)
        }
        (lldb) p $3.get(8)
        (method_t) $11 = {
        	name = "setExt_name:"
        	types = 0x0000000100001f94 "[email protected]:[email protected]"
        	imp = 0x0000000100001ac0 (objc-debug`-[WMPerson setExt_name:] at WMPerson+WMExtension.h:15)
        }
        (lldb) p $3.get(9)
        (method_t) $12 = {
        	name = "ext_subject"
        	types = 0x0000000100001f8c "@[email protected]:8"
        	imp = 0x0000000100001b00 (objc-debug`-[WMPerson ext_subject] at WMPerson+WMExtension.h:16)
        }
        (lldb) p $3.get(10)
        (method_t) $13 = {
        	name = "setExt_subject:"
        	types = 0x0000000100001f94 "[email protected]:[email protected]"
        	imp = 0x0000000100001b30 (objc-debug`-[WMPerson setExt_subject:] at WMPerson+WMExtension.h:16)
        }
                 
    • 下标0和1分别是extM_method/extH_method兩個方法,以及還有下面的一些setter/getter!
    • 總結:這裡我們看到,在加載這些類的時候,從ro中就能取出這些資料,說明是在編譯期就處理好的了!!!

3.runtime關聯對象

我們使用分類,很多時候會用到關聯對象!

例如:我們在分類中,添加一個property屬性,我們上面知道,分類的操作是對類中rw的操作,屬性存儲在property_list_t數組中!但是,在rw中并沒有屬性的setter/getter!是以我們要在分類的.m中對分類進行屬性的關聯操作!

//在.h中添加cate_name屬性!
#import "WMPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface WMPerson (WMCategory)
@property (nonatomic, copy) NSString *cate_name;
@end
NS_ASSUME_NONNULL_END

//.m中對屬性的setter/getter添加
#import "WMPerson+WMCategory.h"
#import <objc/runtime.h>
@implementation WMPerson (WMCategory)
-(void)setCate_name:(NSString *)cate_name {
	//關聯屬性
	objc_setAssociatedObject(self, @"name",cate_name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)cate_name{
	return objc_getAssociatedObject(self, @"name");
}
@end
           
  • 下面我們來看這個關聯對象的底層原了解析:
    • 1.objc_setAssociatedObject()
      iOS底層原理篇(四)----拓展、分類、關聯對象
      • 1.上面如果不夠清楚,我們來畫個圖:
        iOS底層原理篇(四)----拓展、分類、關聯對象
      • 2.總結:上圖中,總表中包含了ObjectAssociationMap0、ObjectAssociationMap1等這樣的表,這些表的下标是根據對應的類的位址經過處理得到的下标!根據下标object,疊代器周遊擷取object對應的ObjectAssociationMap小表,再根據key,疊代器周遊ObjectAssociationMap小表,擷取到key對應的ObjcAssociation對象!這樣我們就找到了關聯對象!!!
    • 2.objc_getAssociatedObject()
      iOS底層原理篇(四)----拓展、分類、關聯對象

      總結:取值的查找過程和存值基本一樣!

      關聯對象的釋放是在調用dealloc過程中進行判斷bool assoc = obj->hasAssociatedObjects();如果assoc為YES,則說明有關聯對象,就會進行釋放:if (assoc) _object_remove_assocations(obj);

以上就是分類,拓展以及關聯對象的一些底層知識,謝謝觀賞!!!

繼續閱讀