iOS面試題-OC對象的isa指針

Objective-C中的對象,簡稱OC對象,主要可以分為3種

1. instance對象(實例對象)

  • instance對象就是通過類alloc出來的對象,每次調(diào)用alloc都會產(chǎn)生新的instance對象
  • 代碼表現(xiàn)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
  • object1, object2都是NSObject的instance對象(實例對象)
  • 它們是不同的兩個對象,分別占據(jù)著兩塊不同的內(nèi)存
  • instace對象在內(nèi)存中存儲的信息包括
    • isa指針
    • 其它成員變量

2. class對象(類對象)

  • 代碼表現(xiàn)
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1); // Runtime
Class objectClass5 = object_getClass(object2); // Runtime
  • objectClass1 ~ objectClass5都是NSObject的class對象(類對象),class方法返回的一直是class對象
  • 它們都是同一個對象.每個類在內(nèi)存中有且只有一個class對象
  • class對象在內(nèi)存中存儲的信息主要包括
    • isa指針
    • superclass指針
    • 類的屬性信息(@property),類的對象方法信息(instance method)
    • 類的協(xié)議信息(protocol),類的成員變量信息(ivar)

3. meta-class對象(元類對象)

  • 代碼表現(xiàn)
Class objectMetaClass = object_getClass([NSObject class]); // Runtime
  • objectMetaClass是NSObjectmeta-class對象
  • 每個類在內(nèi)存中有且只有一個meta-class對象
  • meta-class對象和class對象的內(nèi)存結構是一樣的,但是用途不一樣,在內(nèi)存中存儲的信息主要包括
    • isa指針
    • superclass指針
    • 類的類方法信息(class method)
  • 查看Class是否為meta-class
BOOL result = class_isMetaClass([NSObject class]); // Runtime

object_getClass的內(nèi)部實現(xiàn)

image.png
  • 因為Objective-C和swift的混編問題,一些底層實現(xiàn)有所變化
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}

Class look_up_class(const char *name, 
              bool includeUnconnected __attribute__((unused)), 
              bool includeClassHandler __attribute__((unused)))
{
    // 傳過來的類名為空的話,直接返回nil
    if (!name) return nil;

    // 定義Class結果result
    Class result;
    // 定義未實現(xiàn)過變量unrealized
    bool unrealized;
    {
        // 加鎖
        runtimeLock.lock();
        // 獲取非swift的Class
        result = getClassExceptSomeSwift(name);
        // 如果result有值,且result類的方法實現(xiàn)過了
        unrealized = result  &&  !result->isRealized();
        if (unrealized) {
            // 未實現(xiàn)的話,調(diào)用realizeClassMaybeSwiftAndUnlock實現(xiàn)一下,同時解鎖
            result = realizeClassMaybeSwiftAndUnlock(result, runtimeLock);
            // runtimeLock is now unlocked
        } else {
            // 解鎖
            runtimeLock.unlock();
        }
    }

    if (!result) {
        // Ask Swift about its un-instantiated classes.

        // We use thread-local storage to prevent infinite recursion
        // if the hook function provokes another lookup of the same name
        // (for example, if the hook calls objc_allocateClassPair)

        auto *tls = _objc_fetch_pthread_data(true);

        // Stop if this thread is already looking up this name.
        for (unsigned i = 0; i < tls->classNameLookupsUsed; i++) {
            if (0 == strcmp(name, tls->classNameLookups[i])) {
                return nil;
            }
        }

        // Save this lookup in tls.
        if (tls->classNameLookupsUsed == tls->classNameLookupsAllocated) {
            tls->classNameLookupsAllocated =
                (tls->classNameLookupsAllocated * 2 ?: 1);
            size_t size = tls->classNameLookupsAllocated *
                sizeof(tls->classNameLookups[0]);
            tls->classNameLookups = (const char **)
                realloc(tls->classNameLookups, size);
        }
        tls->classNameLookups[tls->classNameLookupsUsed++] = name;

        // Call the hook.
        Class swiftcls = nil;
        if (GetClassHook.get()(name, &swiftcls)) {
            ASSERT(swiftcls->isRealized());
            result = swiftcls;
        }

        // Erase the name from tls.
        unsigned slot = --tls->classNameLookupsUsed;
        ASSERT(slot >= 0  &&  slot < tls->classNameLookupsAllocated);
        ASSERT(name == tls->classNameLookups[slot]);
        tls->classNameLookups[slot] = nil;
    }

    return result;
}

說說isa

  • instance對象的isa指向class
    • 當調(diào)用對象方法時,通過instanceisa找到class,最后找到對象方法的實現(xiàn)進行調(diào)用
  • classisa指向meta-class
    • 當調(diào)用類方法時,通過classisa找到meta-class,最后找到類方法的實現(xiàn)進行調(diào)用
  • meta-classisa指向基類meta-class

說說superclass

  • classsuperclass指向meta-class

    • 如果沒有父類,superclass指針為nil
  • meta-classsuperclass指向基類meta-class

    • 基類的meta-classsuperclass指向基類的class
  • 例子1(實例對象調(diào)用父類的對象方法),Student的instance實例調(diào)用Person的對象方法流程


    image.png
  • 例子2(類對象調(diào)用父類的類方法),Student的class對象調(diào)用Person類的類方法流程


    image.png

經(jīng)典的isa和superclass圖解

image.png
  • intance調(diào)用對象方法的軌跡

    • isa找到class,方法不存在,就通過superclass找父類,一直找,直到所有的父類找完都沒有這個對象方法的實現(xiàn)時,再經(jīng)過runtime的動態(tài)方法解析和消息轉(zhuǎn)發(fā),如果都沒有,就會報unrecognized selector sent to instance 0xxxxxxxxx
  • class調(diào)用類方法的軌跡

    • isa找meta-class,方法不存在,就通過superclass找父類
    • 這種有一種特殊情況??,找到meta-class的都沒有找到時,因為meta-classsuperclass指向基類meta-class的,所以會調(diào)用基類meta-class的類方法.這里不用奇怪明明調(diào)用的是類方法,最后卻調(diào)用了基類的對象方法.
      • 示例代碼如下
      #import <Foundation/Foundation.h>
      #import <objc/objc.h>
      
      @interface XYPerson : NSObject
      
      + (void)test;
      
      @end
      
      @implementation XYPerson
      
      @end
      
      @interface NSObject (Test)
      
      + (void)test;
      
      @end
      
      @implementation NSObject (Test)
      
      - (void)test
      {
          NSLog(@"-[NSObject test] - %p", self);
      }
      
      @end
      
      int main(int argc, const char * argv[]) {
          @autoreleasepool {
              // [XYPerson class] - 0x1000041e0
              NSLog(@"[XYPerson class] - %p", [XYPerson class]);
              // [NSObject class] - 0x7fff90dd4118
              NSLog(@"[NSObject class] - %p", [NSObject class]);
      
              // -[NSObject test] - 0x1000041e0
              [XYPerson test];
      //        objc_msgSend([XYPerson class], @selector(test));
      
              // -[NSObject test] - 0x7fff90dd4118
              [NSObject test];
      //        objc_msgSend([NSObject class], @selector(test));
          }
          return 0;
      }
      

說說isa和superclass細節(jié)

  • 代碼示意
// MJPerson類對象的地址:0x00000001000014c8
// MJPerson實例對象的isa:0x001d8001000014c9(為什么地址有差別,需要要與上一個 ISA_MASK 才是真實的值)
// isa & ISA_MASK:0x00000001000014c8

MJPerson *person = [[MJPerson alloc] init];

Class personClass = [MJPerson class];

Class personMetaClass = object_getClass(personClass);
  • 從64bit開始,isa需要進行一次位運算,才能計算出真實的isa地址值

    • isa & ISA_MASK
    • ISA_MASK的值


      image.png
  • superclass不需要做此操作,直接就是對應的地址值

isa和superclass-class和meta-class的結構

  • 代碼結構
/// Represents an instance of a 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 vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() const {
        return bits.data();
    }
    ...
}

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    union {
        const uint8_t * ivarLayout;
        Class nonMetaclass;
    };

    explicit_atomic<const char *> name;
    // With ptrauth, this is signed if it points to a small list, but
    // may be unsigned if it points to a big list.
    void *baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};
  • 圖片結構


    image.png

模擬class內(nèi)部的結構

#import <Foundation/Foundation.h>

#ifndef MJClassInfo_h
#define MJClassInfo_h

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif

#if __LP64__
typedef uint32_t mask_t;
#else
typedef uint16_t mask_t;
#endif
typedef uintptr_t cache_key_t;

struct bucket_t {
    cache_key_t _key;
    IMP _imp;
};

struct cache_t {
    bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};

struct entsize_list_tt {
    uint32_t entsizeAndFlags;
    uint32_t count;
};

struct method_t {
    SEL name;
    const char *types;
    IMP imp;
};

struct method_list_t : entsize_list_tt {
    method_t first;
};

struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};

struct ivar_list_t : entsize_list_tt {
    ivar_t first;
};

struct property_t {
    const char *name;
    const char *attributes;
};

struct property_list_t : entsize_list_tt {
    property_t first;
};

struct chained_property_list {
    chained_property_list *next;
    uint32_t count;
    property_t list[0];
};

typedef uintptr_t protocol_ref_t;
struct protocol_list_t {
    uintptr_t count;
    protocol_ref_t list[0];
};

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;  // instance對象占用的內(nèi)存空間
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;  // 類名
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;  // 成員變量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
};

struct class_rw_t {
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_list_t * methods;    // 方法列表
    property_list_t *properties;    // 屬性列表
    const protocol_list_t * protocols;  // 協(xié)議列表
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
};

#define FAST_DATA_MASK          0x00007ffffffffff8UL
struct class_data_bits_t {
    uintptr_t bits;
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
};

/* OC對象 */
struct mj_objc_object {
    void *isa;
};

/* 類對象 */
struct mj_objc_class : mj_objc_object {
    Class superclass;
    cache_t cache;
    class_data_bits_t bits;
public:
    class_rw_t* data() {
        return bits.data();
    }
    
    mj_objc_class* metaClass() {
        return (mj_objc_class *)((long long)isa & ISA_MASK);
    }
};

#endif /* MJClassInfo_h */

// objective-c++
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "MJClassInfo.h"

// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
@public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation MJPerson

- (void)test
{
    
}

- (void)personInstanceMethod
{
    
}
+ (void)personClassMethod
{
    
}
- (id)copyWithZone:(NSZone *)zone
{
    return nil;
}
@end

// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
    int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation MJStudent
- (void)test
{
    
}
- (void)studentInstanceMethod
{
    
}
+ (void)studentClassMethod
{
    
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
    return nil;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJStudent *stu = [[MJStudent alloc] init];
        stu->_weight = 10;
        
        mj_objc_class *studentClass = (__bridge mj_objc_class *)([MJStudent class]);
        mj_objc_class *personClass = (__bridge mj_objc_class *)([MJPerson class]);
        
        class_rw_t *studentClassData = studentClass->data();
        class_rw_t *personClassData = personClass->data();
        
        class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
        class_rw_t *personMetaClassData = personClass->metaClass()->data();

        NSLog(@"1111");
    }
    return 0;
}

面試題

  1. 對象的isa指針指向哪里?
    • instance對象的isa指向class對象
    • class對象的isa指向meta-class對象
    • meta-class對象的isa指向基類meta-class對象
  2. OC的類信息存放在哪里?
    • 對象方法,屬性,成員變量,協(xié)議信息,存放在class對象中
    • 類方法,存放在meta-class對象中
    • 成員變量的具體值,存放在instance對象
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末癞蚕,一起剝皮案震驚了整個濱河市妖混,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖招盲,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诈嘿,死亡現(xiàn)場離奇詭異,居然都是意外死亡糠雨,警方通過查閱死者的電腦和手機才睹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甘邀,“玉大人琅攘,你說我怎么就攤上這事∷尚埃” “怎么了坞琴?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長逗抑。 經(jīng)常有香客問我剧辐,道長,這世上最難降的妖魔是什么锋八? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任浙于,我火速辦了婚禮,結果婚禮上挟纱,老公的妹妹穿的比我還像新娘羞酗。我一直安慰自己,他們只是感情好紊服,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布檀轨。 她就那樣靜靜地躺著,像睡著了一般欺嗤。 火紅的嫁衣襯著肌膚如雪参萄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天煎饼,我揣著相機與錄音讹挎,去河邊找鬼。 笑死吆玖,一個胖子當著我的面吹牛筒溃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沾乘,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼怜奖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翅阵?” 一聲冷哼從身側(cè)響起歪玲,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤迁央,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后滥崩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岖圈,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年夭委,在試婚紗的時候發(fā)現(xiàn)自己被綠了幅狮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡株灸,死狀恐怖崇摄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慌烧,我是刑警寧澤逐抑,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站屹蚊,受9級特大地震影響厕氨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汹粤,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一命斧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘱兼,春花似錦国葬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至踢涌,卻和暖如春通孽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睁壁。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工背苦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人潘明。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓糠惫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钉疫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容