【iOS】類對象的結構分析

目錄

    • 對象的分類
    • object_getClass和class方法
    • isa流程和繼承鏈分析
      • isa流程實例驗證
      • 類的繼承鏈實例驗證
    • 類的結構
      • cache_t結構
      • bits分析
      • 實例驗證
        • 屬性properties
        • 方法methods
        • 協議protocols
        • ro
        • 類方法
      • 類結構流程圖解


對象的分類

OC中的對象主要可以分為3種:實例對象(instance)、類對象(class)和元類對象(meta-class)

實例對象

通過類alloc出來的對象,每次調用alloc都會產生新的instance對象

NSObject* obj1 = [[NSObject alloc] init];
NSObject* obj2 = [[NSObject alloc] init];
NSLog(@"%p %p", obj1, obj2);
//  打印結果:0x600000180040 0x600000180050

從運行結果可看出以上是不同的兩個實例對象,分別占據著兩塊不同的內存
實例對象在內存中存儲的信息包括:isa指針、其他成員變量

類對象

#import <objc/runtime.h>
Class objectClass1 = [obj1 class];
Class objectClass2 = [obj2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(obj1);  //Runtime API
Class objectClass5 = object_getClass(obj2);  //Runtime API
//  打印結果:0x1d6fc6070 0x1d6fc6070 0x1d6fc6070 0x1d6fc6070 0x1d6fc6070

以上都是NSObject的類對象,從運行結果可看出它們都是同一個對象,即這些指針指向的是同一塊內存,每個類在內存中有且只有一個class對象
類對象在內存中存儲的信息主要包括:isa指針、superclass指針、類的屬性信息(@property)、類的對象方法信息(instance method)、類的協議信息(protocol)、類的成員變量(ivar,類型、名稱等描述信息而不是具體的值)

元類對象

看下面如何獲取元類對象(元類對象類型仍是一個類對象,底層都是struct objc_class* Class,只是包含的信息不一樣)

Class objectMetaClass = object_getClass(object_getClass(obj1));

將類對象作為參數傳入,再次調用object_getClass函數

那如果調用兩次class方法呢?

Class objectMetaClass2 = [[NSObject class] class];
NSLog(@"%p %p %d", objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));
//  打印結果:0x1d6fc6020 0x1d6fc6070 1 0

從打印結果可以看出,class不管調多少次返回的一直是類對象,不會是元類對象
每個類只有一個元類對象,元類對象在內存中存儲的信息主要包括:isa指針、superclass指針以及類方法信息

object_getClass和class方法

查看objc4源碼

object_getClass方法中傳入各種對象,通過訪問isa,返回不同的類對象:

Class object_getClass(id obj)
{if (obj) return obj->getIsa();else return Nil;
}//  傳入類名字符串,返回對象的類對象
Class objc_getClass(const char *aClassName)
{if (!aClassName) return Nil;// NO unconnected, YES class handlerreturn look_up_class(aClassName, NO, YES);
}

class方法直接返回類對象:

//+ (id)self {
//    return (id)self;
//}
//- (id)self {
//    return self;
//}+ (Class)class {return self;
}- (Class)class {return object_getClass(self);
}//+ (Class)superclass {
//    return self->getSuperclass();
//}
//- (Class)superclass {
//    return [self class]->getSuperclass();
//}

isa流程和繼承鏈分析

上面我們了解了對象的分類,認識到不同類型對象的差別,那么是什么讓這些不同類型的對象聯系起來從而構成OC對象體系的呢?

上經典老圖:

請添加圖片描述

isa指向鏈

實際上就是isa指針將它們聯系起來形成 isa指向鏈

  • 實例對象instanceisa指向類class
  • 類對象class也有isa指向的是元類meta
  • 元類meta中也有isa指向的是根元類root meta

在這里插入圖片描述
當調用對象方法時,通過實例對象的isa找到class,最后找到對象方法的實現進行調用
當調用類方法時,通過類對象的isa找到meta-class,最后找到類方法的實現進行調用

類繼承鏈

根據superclass的指向,也可總結出OC類的繼承鏈

  • 子類繼承于父類,父類繼承于根類,根類指向的是nil
  • 在元類中也存在繼承,子類的元類繼承于父類的元類,父類的元類繼承于根元類,根元類又繼承與根類

在這里插入圖片描述

當Student的實例對象要調用Person的對象方法時,會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實現進行調用

類似地,當Student的類對象要調用Person的類方法時,會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實現進行調用

isa流程實例驗證

Person類繼承于NSObject,Student類繼承于Person

@interface Person : NSObject {@publicint _age;
}- (void)personInstanceMethod;
+ (void)personClassMethod;@end@interface Student : Person {@publicint _no;
}- (void)studentInstanceMethod;
+ (void)studentClassMethod;@end

打斷點,通過LLDB查看isa關聯類的地址:

//  打印出實例的地址
Person* person = [Person alloc];
NSLog(@"%@", person);
Student* student = [Student alloc];
NSLog(@"%@", student);

類對象的地址和實例對象isa所指向的地址有所出入,isa需要進行一次位運算,才能計算出類對象的真實地址
在獲取到對象的isa值后,可以通過&(按位與)一個掩碼ISA_MASK 0x007ffffffffffff8ULL來獲取到對象關聯的類地址:
在這里插入圖片描述

根據student實例的isa地址找到關聯類Student的地址0x00000001000082d8

在這里插入圖片描述

同樣地,根據Student類對象的isa找到Student元類的地址0x00000001000082b0

在這里插入圖片描述

根據Student元類對象的isa找到關聯類的地址0x00000001d6fc6020

在這里插入圖片描述

找到NSObject類對象的isa關聯類地址0x00000001d6fc6020,與Student元類對象的isa關聯類地址一致,可以驗證元類的isa指向根元類,且根元類的isa指向自己

在這里插入圖片描述

類的繼承鏈實例驗證

Class tClass = [Student class];
Class pClass = class_getSuperclass(tClass);
Class nClass = class_getSuperclass(pClass);
Class rClass = class_getSuperclass(nClass);
NSLog(@"\n tClass-%@ \n pClass-%@ \n nClass-%@ \n rClass-%@ \n", tClass, pClass, nClass, rClass);

在這里插入圖片描述
可看出類對象的繼承鏈:Student->Person->NSObject->nil

Student * student = [Student alloc];
Class tClass = object_getClass(student);
Class mtClass = object_getClass(tClass);
Class mtSuperClass = class_getSuperclass(mtClass);
NSLog(@"\n student %p 實例對象 -- %p 類 -- %p 元類 -- %p 元類父類", student, tClass, mtClass, mtSuperClass);
Person * person = [Person alloc];
Class pClass = object_getClass(person);
Class mpClass = object_getClass(pClass);
Class mpSuperClass = class_getSuperclass(mpClass);
NSLog(@"\n person %p 實例對象 -- %p 類 -- %p 元類 -- %p 元類父類", person, pClass, mpClass, mpSuperClass);
NSObject * obj = [NSObject alloc];
Class objClass = object_getClass(obj);
Class mobjClass = object_getClass(objClass);
Class mobjSuperClass = class_getSuperclass(mobjClass);
NSLog(@"\n NSObject %p 實例對象 -- %p 類 -- %p 元類 -- %p 元類父類 == %p NSObject類對象", obj, objClass, mobjClass, mobjSuperClass,
[NSObject class]);

在這里插入圖片描述

可看出元類的繼承鏈:Student Meta-class -> Person Meta-class -> NSObject Meta-class -> NSObject class -> nil

類的結構

前面我們了解到了Class的類型是struct objc_class*結構體指針類型,下面就來分析一下這個結構體的定義

struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache;             // formerly cache pointer and vtableclass_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags//  ...其他代碼,objc_class定義共計531行代碼...
};

繼承于objc_object說明:

  • 還有一個繼承過來的Class類型變量isa
  • superclass:指向父類的指針
  • cache:緩存相關
  • bits:用于獲取具體的類信息

cache_t結構

cache_t是一個結構體

struct cache_t {
private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8字節union {struct {explicit_atomic<mask_t>    _maybeMask; // uint32_t 4字節
#if __LP64__uint16_t                   _flags;     // 2字節
#endifuint16_t                   _occupied;  // 2字節};explicit_atomic<preopt_cache_t *> _originalPreoptCache;  // 8字節};};
//  此段為部分代碼,cache_t定義總共有290行

分析整個cache_t的結構,發現cache_t的內存總共為16字節,后面會對其底層進行學習

bits分析

objc_class里有一段源碼是data操作

class_rw_t *data() const {return bits.data();
}
void setData(class_rw_t *newData) {bits.setData(newData);
}

dataclass_rw_t類型,下面是其部分源碼:

在這里插入圖片描述

ro:成員變量、methods:方法、properties:屬性、protocols協議
我們在類中定義的方法、屬性等就是通過調取class_rw_t結構體中的方法獲取的

實例驗證

下面通過實例來驗證一下類的結構是否如上面一致
創建Person類繼承于NSObject,定義一些屬性、方法以及協議:

@protocol PersonDelegate<NSObject>- (void)personDelegateMethod;
// 讓Person類遵守并實現此協議方法
@end@interface Person : NSObject<PersonDelegate> {NSString* hobby;
}@property (nonatomic, strong)NSString* name;
@property (nonatomic, assign)NSInteger age;- (void)sayHello;
+ (void)sayWorld;@end

LLDB調試輸出

請添加圖片描述

第一個地址0x0000000100008470是類的第一個成員isa,第二個地址0x00000001d6fc6070是類的第二個成員superclass
isasuperclass都是結構體指針類型,占用8字節,cache結構體占用16字節,XYPerson的地址加上8 + 8 + 16 = 32就可以得到bits的地址

請添加圖片描述

相加并強轉為class_data_bits_t *類型得到bits的地址0x0000000100008270,再調用data()方法就得到類型為class_rw_t的地址

屬性properties

調用class_rw_tproperties()方法,得到property_array_t類型的數組,繼承于list_array_tt,找到list下的ptr

請添加圖片描述

class property_array_t :public list_array_tt<property_t, property_list_t, RawPtr>
{typedef list_array_tt<property_t, property_list_t, RawPtr> Super;public:property_array_t() : Super() { }property_array_t(property_list_t *l) : Super(l) { }
};

ptrproperty_list_t類型,繼承于entsize_list_tt

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

entsize_list_tt部分源碼:

struct entsize_list_tt {uint32_t entsizeAndFlags;uint32_t count;  //  數量uint32_t entsize() const {return entsizeAndFlags & ~FlagMask;}uint32_t flags() const {return entsizeAndFlags & FlagMask;}Element& getOrEnd(uint32_t i) const {ASSERT(i <= count);return *PointerModifier::modify(*(List *)this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));}Element& get(uint32_t i) const {  //  獲取元素方法ASSERT(i < count);return getOrEnd(i);}//  ...其他代碼...
};

通過調用get()方法,獲取元素,下面的結果就是Person類的nameageproperties()里,而實例變量hobby不在這里

請添加圖片描述

方法methods

調用class_rw_tmethods()方法,得到method_array_t類型的數組,繼承于list_array_tt,同樣找到list下的ptr

請添加圖片描述

這里看到ptrmethod_list_t類型,同樣繼承于entsize_list_tt,其中有count為6,調用get()方法查看輸出

請添加圖片描述

這里的元素為method_t類型,method_t為結構體類型,其中的一個成員變量為big的結構體,里面是方法名稱等信息:

struct method_t {method_t(const method_t &other) = delete;// The representation of a "big" method. This is the traditional// representation of three pointers storing the selector, types// and implementation.struct big {SEL name;const char *types;MethodListIMP imp;};
//  ...其他代碼
};

調用big方法查看輸出

請添加圖片描述

這6個方法分別是:

  • 實例方法:sayHello
  • 屬性nameageset/get方法
  • C++析構函數:.cxx_destruct

且都是實例方法,并沒有類方法sayWorld

協議protocols

調用class_rw_tprotocols()方法,得到protocol_array_t類型的數組,繼承于list_array_tt,同樣找到list下的ptr

請添加圖片描述

這里protocol_list_t并沒有繼承于entsize_list_tt

struct protocol_list_t {// count is pointer-sized by accident.uintptr_t count;protocol_ref_t list[0]; // variable-sizesize_t byteSize() const {return sizeof(*this) + count*sizeof(list[0]);}protocol_list_t *duplicate() const {return (protocol_list_t *)memdup(this, this->byteSize());}typedef protocol_ref_t* iterator;typedef const protocol_ref_t* const_iterator;const_iterator begin() const {return list;}iterator begin() {return list;}const_iterator end() const {return list + count;}iterator end() {return list + count;}
};

看到protocol_list_t的定義,我們知道count值為1,說明是有值,但是其成員是protocol_ref_tuintptr_t類型,那怎么輸出查看這個count中的1到底是什么呢

在這里插入圖片描述

查看protocol_ref_t的定義,通過注釋信息,我們可以看到protocol_ref_t未映射到protocol_t類型,那我們就找protocol_t的定義

在這里插入圖片描述
這里看到protocol_t中有mangledName以及instanceMethods等,只要得到protocol_t就可以輸出我們想要的名稱方法等信息,怎么才能從protocol_ref_t映射到protocol_t呢,全局找一下吧

在這里插入圖片描述

這里我們看到,protocol_ref_t是可以強轉protocol_t的,那我們就試試:

請添加圖片描述

強轉成功,調用demangledName方法,我們就得到了LGPersonDelegate,那我們再找一下協議方法

請添加圖片描述

按照method查看輸出的步驟,成功找到協議方法personDelegateMethod

ro

調用class_rw_tro方法,得到class_ro_t的結構體

請添加圖片描述
請添加圖片描述

查看ivars,也是繼承于entsize_list_ttivar_list_t類型的結構體,調用get方法查看:

請添加圖片描述

這6個實例變量分別是自定義hobby以及系統自動幫我們自動生成的帶有_的實例變量

類方法

methods中的方法全部都存在類中,都是實例方法,那么類方法應該去在元類中找

請添加圖片描述

通過類的isa指針找到元類,再根據上面的步驟找到并輸出這個元類的methods

這里我們不由地想,OC的底層是C/C++實現的,不存在對象方法和類方法的區分,有的都是函數實現,在OC的設計中,一個類可以new出無數個對象,因此把方法存在類中,而不是動態創建的對象中,是合理的。
因為OC的對象方法和類方法的定義是-+的區分,那么方法名稱就會有重名的存在,因此才會引入元類的概念,元類的存在就是解決類方法重名的問題

類結構流程圖解

類的結構流程圖解析:

請添加圖片描述

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/46229.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/46229.shtml
英文地址,請注明出處:http://en.pswp.cn/web/46229.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【React】JSX基礎

一、簡介 JSX是JavaScript XML的縮寫&#xff0c;它是一種在JavaScript代碼中編寫類似HTML模板的結構的方法。JSX是React框架中構建用戶界面&#xff08;UI&#xff09;的核心方式之一。 1.什么是JSX JSX允許開發者使用類似HTML的聲明式模板來構建組件。它結合了HTML的直觀性…

TDesign組件庫日常應用的一些注意事項

【前言】Element&#xff08;餓了么開源組件庫&#xff09;在國內使用的普及率和覆蓋率高于TDesign-vue&#xff08;騰訊開源組件庫&#xff09;&#xff0c;這也導致日常開發遇到組件使用上的疑惑時&#xff0c;網上幾乎搜索不到其文章解決方案&#xff0c;只能深挖官方文檔或…

2024.7.17 ABAP面試題目總結

2024.7.17 用的SAP什么平臺&#xff0c;S4/HANA嗎&#xff0c;有用過ECC嗎 S4/HANA&#xff0c;沒用過ECC 會不會CDS VIEW 不會 會不會FIORI 不會 銀企直連里面的邏輯了解不 不了解&#xff0c;做過&#xff0c;但是只能算很簡單的修改 SAP做增強&#xff0c;如何創建…

網絡安全-網絡安全及其防護措施7

31.防病毒和惡意軟件保護 防病毒和惡意軟件防護的定義和作用 防病毒和惡意軟件防護是一種保護計算機和網絡免受病毒、木馬、間諜軟件等惡意軟件侵害的安全措施。通過防護措施&#xff0c;可以檢測、阻止和清除惡意軟件&#xff0c;確保系統和數據的安全。其主要作用包括&…

C++右值引用和移動語義

目錄 概念&#xff1a; 左值引用和右值引用 概念&#xff1a; 注意&#xff1a; 左值引用的意義 作函數參數 函數引用返回 右值引用的意義 誕生背景 移動構造 移動賦值 其他應用 萬能引用和完美轉發 默認的移動構造和移動賦值 概念&#xff1a; 左值&#xff1a;顧…

List數據的幾種數據輸出方式

一、問題引入 在Java中&#xff0c;查詢List集合是一項常見的任務&#xff0c;它可以通過多種方式實現&#xff0c;以滿足不同的需求。下面&#xff0c;List數據的幾種數據輸出方式。 二、實例 /*** 查詢所有用戶信息* return*/ List<User> getAllUser(); <select…

Git【撤銷遠程提交記錄】

在實際開發中&#xff0c;你是否遇到過錯誤的提交了代碼&#xff0c;想要刪掉本次提交記錄的情況&#xff0c;你可以按照如下方法實現。 1、使用 git revert 如果你想要保留歷史記錄&#xff0c;并且對遠程倉庫其他使用者的影響最小&#xff0c;你可以使用 git revert 命令。這…

conda 使用

首先要安裝Miniconda的環境 下面是一下命令關于如何使用conda 查看當前環境列表&#xff1a; conda env list 創建環境不指定路徑&#xff1a; conda create --name p38 python3.8 創建新環境并指定路徑&#xff1a; conda create --prefix /data/p38 python3.8激活新環境&a…

VulnHub:CK00

靶場搭建 靶機下載地址&#xff1a;CK: 00 ~ VulnHub 下載后&#xff0c;在vmware中打開靶機。 修改網絡配置為NAT 處理器修改為2 啟動靶機 靶機ip掃描不到的解決辦法 靶機開機時一直按shift或者esc直到進入GRUB界面。 按e進入編輯模式&#xff0c;找到ro&#xff0c;修…

Kubernetes網絡性能測試-calico插件環境

Kubernetes 網絡性能測試-calico插件環境 本次主要針對calico網絡插件k8s集群的網絡性能進行摸底及測試方法探索實踐。 1. 測試準備 1.1 測試環境 測試環境為VMware Workstation虛擬機搭建的一套K8S環境&#xff0c;版本為1.28.2&#xff0c;網絡插件使用calico&#xff0c…

6Python的Pandas:數據讀取與輸出

Pandas是一個強大的Python數據分析庫&#xff0c;提供了讀取和輸出數據的多種功能。以下是一些常見的數據讀取與輸出方法&#xff1a; 1. 讀取CSV 讀取數據 從CSV文件讀取數據 import pandas as pd# 讀取CSV文件 df pd.read_csv(file_path.csv) print(df.head())從Excel文…

Flutter 狀態管理調研總結

一, 候選狀態管理組件簡介 0. flutter_hooks 一個 React 鉤子在 Flutter 上的實現&#xff1a;Making Sense of React Hooks 鉤子是一種用來管理 Widget 生命周期的新對象&#xff0c;以減少重復代碼、增加組件間復用性&#xff0c;允許將視圖邏輯提取到通用的用例中并重用&…

思路|如何利用oneNote釣魚?

本文僅用于技術研究學習&#xff0c;請遵守相關法律&#xff0c;禁止使用本文所提及的相關技術開展非法攻擊行為&#xff0c;由于傳播、利用本文所提供的信息而造成任何不良后果及損失&#xff0c;與本賬號及作者無關。 本文來源無問社區&#xff0c;更多實戰內容&#xff0c;…

[python]pycharm設置清華源

國內鏡像源有以下幾個&#xff0c;因為都是國內的&#xff0c;基本速度差不了太多。 清華&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http://mirrors.aliyun.com/pypi/simple/ 中國科技大學 https://pypi.mirrors.ustc.edu.cn/simple/ 豆瓣&…

針對【module_or_function】的單元測試,全面覆蓋可能的【edge_cases】

針對【module_or_function】的單元測試&#xff0c;全面覆蓋可能的【edge_cases】 編寫單元測試是為了驗證代碼模塊或函數的正確性和魯棒性。對于module_or_function&#xff0c;首先需要確定這個模塊或函數的具體功能和預期輸入范圍。一個好的單元測試應該包括以下幾個步驟&a…

高并發服務器-使用多線程(Multi-Thread)實現【C語言】

在上期的socket套接字的使用詳解中&#xff08;socket套接字的使用詳解&#xff09;最后實現的TCP服務器只能處理一個客戶端的請求發送&#xff0c;當有其他客戶端請求連接時會被阻塞。為了能同時處理多個客戶端的連接請求&#xff0c;本期使用多線程的方式來解決。 程序流程 …

爬蟲案例(讀書網)(下)

上篇鏈接&#xff1a; CSDN-讀書網https://mp.csdn.net/mp_blog/creation/editor/139306808 可以看見基本的全部信息&#xff1a;如(author、bookname、link.....) 寫下代碼如下&#xff1a; import requests from bs4 import BeautifulSoup from lxml import etreeheaders{…

scottplot5 中 使用signalXY圖,如何更新數據?

&#x1f3c6;本文收錄于《CSDN問答解答》專欄&#xff0c;主要記錄項目實戰過程中的Bug之前因后果及提供真實有效的解決方案&#xff0c;希望能夠助你一臂之力&#xff0c;幫你早日登頂實現財富自由&#x1f680;&#xff1b;同時&#xff0c;歡迎大家關注&&收藏&…

總部下達任務時,如何保證員工的執行力?

執行力是個體基于戰略目標&#xff0c;有效整合利用資源&#xff0c;保質保量完成預定目標的操作能力&#xff0c;員工執行力的高低是企業完成效益、成果轉化的關鍵。執行力包含完成任務的意愿、完成能力、完成程度三個維度&#xff0c;其中意愿是基礎和出發點&#xff0c;能力…

物聯網與通信技術

查了很多資料&#xff0c;也夾雜著一些自己的見解。此篇文章僅探討三個問題&#xff1a;物聯網與通信技術的關系&#xff1b;5G為物聯網帶來了什么&#xff0c;物聯網真的需要5G嗎&#xff1b;物聯網發展的現實問題。 1、物聯網與通信技術的關系 最近幾年&#xff0c;物聯網的…