The introduction

Custom JPerson class inherits from NSObject

@interface JPerson : NSObject
{
    NSString *nickName;
}

@end
Copy the code

Let’s write the following code in the main method and look at the underlying implementation

int main(int argc, char * argv[]) {
    @autoreleasepool {
        JPerson *p = [[JPerson alloc] init];
        NSLog(@"%@",p);
    }
    return 0;
}
Copy the code

The terminal goes to the main.m file directory to view its underlying implementation through Clang

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
Copy the code

Open the generated main. CPP file

. typedef struct objc_object NSObject; struct NSObject_IMPL { Class isa; }; . typedef struct objc_object JPerson; struct JPerson_IMPL { struct NSObject_IMPL NSObject_IVARS; NSString *nickName; }; .Copy the code

You can see that underneath NSObject isa struct objc_object structure type that contains isa inside.

The underlying JPerson structure is also a struct objc_object structure containing a struct NSObject_IMPL NSObject_IVARS (ISA) and a custom nickName.

objc_object

struct objc_object { private: isa_t isa; public: // ISA() assumes this is NOT a tagged pointer object Class ISA(); // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA Class rawISA(); // getIsa() allows this to be a tagged pointer object Class getIsa(); uintptr_t isaBits() const; . };Copy the code

Isa_t As we explained in # OC’s isa exploration, the objc_object structure contains an ISA and some methods.

We know that a pointer to a class object is stored in the ISA of the instance object, and we have verified this above. Let’s look at the implementation details

inline Class objc_object::ISA() { ASSERT(! isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits; #else return (Class)(isa.bits & ISA_MASK); #endif # elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL }Copy the code

SUPPORT_INDEXED_ISA=1 represents isWatchABI, as described in # iOS Low-level Exploration – Instance object Creation

Isa.bits & ISA_MASK is used to get the pointer to the class object

Class

struct objc_class;
struct objc_object;

typedef struct objc_class *Class;
typedef struct objc_object *id;
Copy the code

The underlying Class is a structure pointer of type struct objC_class

objc_class

struct objc_class : objc_object { ...... // Class ISA; //8 bytes Class superclass; // 16 bytes cache_t cache; // formerly cache pointer and vtable // 8 bytes class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *data() const { return bits.data(); }... };Copy the code

The objC_class structure integrates ISA from ObjC_Object and defines its own superclass, cache, and bits

cache_t

typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits\

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字节
    
    //联合体占用8字节
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; //4字节
#if __LP64__
            uint16_t                   _flags; //2字节
#endif
            uint16_t                   _occupied; //2字节
        };
        explicit_atomic<preopt_cache_t *>  _originalPreoptCache; //8字节
    };
    
};
Copy the code

The cache can cache methods and improve the efficiency of method calls, which will be discussed in more detail when we examine method caches later

class_data_bits_t

struct class_data_bits_t { friend objc_class; Uintptr_t bits; // Uintptr_t bits; private: bool getBit(uintptr_t bit) const { return bits & bit; }... public: class_rw_t* data() const { return (class_rw_t *)(bits & FAST_DATA_MASK); } void setData(class_rw_t *newData) { ASSERT(! data() || (newData->flags & (RW_REALIZING | RW_FUTURE))); // Set during realization or construction only. No locking needed. // Use a store-release fence because there may be concurrent // readers of data and data's contents. uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData; atomic_thread_fence(memory_order_release); bits = newBits; } // Get the class's ro data, even in the presence of concurrent realization. // fixme this isn't really safe without a compiler barrier at least // and probably a memory barrier when realizeClass changes the data field const class_ro_t *safe_ro() { class_rw_t *maybe_rw = data(); if (maybe_rw->flags & RW_REALIZED) { // maybe_rw is rw return maybe_rw->ro(); } else { // maybe_rw is actually ro return (class_ro_t *)maybe_rw; }}};Copy the code

Class_data_bits_t stores a bit. Using bits to calculate class_rw_t, class_RO_t can be obtained

class_rw_t

struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint16_t witness; #if SUPPORT_INDEXED_ISA // abi uint16_t index; #endif explicit_atomic<uintptr_t> ro_or_rw_ext; Class firstSubclass; Class nextSiblingClass; private: using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>; const ro_or_rw_ext_t get_ro_or_rwe() const { return ro_or_rw_ext_t{ro_or_rw_ext}; }... // access class_rw_ext_t or class_ro_t const method_array_t methods() const {auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>()->methods; } else { return method_array_t{v.get<const class_ro_t *>()->baseMethods()}; } } const property_array_t properties() const { auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>()->properties; } else { return property_array_t{v.get<const class_ro_t *>()->baseProperties}; } } const protocol_array_t protocols() const { auto v = get_ro_or_rwe(); if (v.is<class_rw_ext_t *>()) { return v.get<class_rw_ext_t *>()->protocols; } else { return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols}; }}};Copy the code

Ro_or_rw_ext supports class_ro_t and class_rw_ext_t templates, which can be verified by methods(), properties(), and protocols()

class_ro_t

struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #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; . };Copy the code

Class_ro_t contains information about methods, attributes, member variables, and so on that are determined at compile time. InstanceStart and instanceSize are used when dyLD is loaded. See # Non Fragile Ivars for details

class_rw_ext_t


struct class_rw_ext_t {
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
Copy the code

Class_rw_ext_t stores Pointers to class_ro_t and dynamically added information.

For details, see # WWDC2020

Simple summary

OC objects are inherited from NSObject. The underlying implementation of NSObject is an objC_Object structure that contains only ISA. Subclasses inherit ISA from NSObject.

Isa in objc2 isa federation that stores information through bits and bitfields.

  • Instance variable ofisaContains a pointer toClass objectA pointer to the
  • Class objecttheisaContains a pointer toYuan class objectThe pointer.
  • Yuan class objecttheisaContains a pointer toThe root metaclass objectThe pointer.
  • The root metaclass objecttheisaContains a pointer to itself.

The underlying implementation of the Class object Class is an objc_class structure, which in addition to isa inherited from objC_Object contains:

  • superclassPointer to the parent class
  • cacheCaching information such as methods (used later when exploring method flow)
  • bitscontainsclass_rw_tInformation such as

Class_rw_t contains an important variable ro_or_rw_ext. Ro_or_rw_ext supports two templates: class_rw_ext_t and class_ro_t. You can store either class_rw_ext_t or class_ro_t, depending on the case

Class_ro_t stores information determined at compile time, such as method lists, attribute lists, protocol lists, and so on. Because OC is a dynamic language, information can be added dynamically at run time. If the Class object does not have dynamic information, ro_OR_rw_ext stores class_RO_T, and if it does, ro_OR_rw_ext stores class_rw_ext_t

Class_rw_ext_t contains Pointers to class_ro_t and other dynamically added information.

Note that class_rw_ext_t contains information copied from class_ro_t and dynamically added by the user

Code validation

NS_ASSUME_NONNULL_BEGIN

@protocol JProtocol <NSObject>

@property(nonatomic,strong) NSString *protocolProperty;
- (void)method_1; //在JPerson.m中实现了该方法
- (void)method_2; //在JPerson.m中没有实现该方法

@end

@interface JPerson : NSObject<JProtocol>
{
    NSString *name;
    NSInteger age;
}

@property (nonatomic,strong)NSString *nickName;

- (void)instanceMethod; //.m实现之
+ (void)classMethod; //.m实现之

@end

NS_ASSUME_NONNULL_END
Copy the code

We create a JPerson class that implements the JProtocol, but only some of the methods in the protocol, and create a category for JPerson, JPerson_category, that also implements only some of the methods

#import "JPerson.h" NS_ASSUME_NONNULL_BEGIN @interface JPerson (JPerson_category) @property (nonatomic,strong) NSString *categoryProperty; -(void)method_cat1; // implement this method in.m -(void)method_cat2; @end NS_ASSUME_NONNULL_END @assume_nonNULl_endCopy the code

Write the following code in the main function

JPerson *p1 = [[JPerson alloc] init]; Class pClass = object_getClass(p1); NSLog(@"%@-%@",p1,pClass); / / break pointCopy the code

P1 is the pointer to the instance object, and pClass is the pointer to the class object JPerson, which is the focus of our study in this paper

(lldb) x/5gx pClass
0x100008900: 0x00000001000088d8 0x000000010036a140
0x100008910: 0x0000000101404960 0x0001802800000003
0x100008920: 0x00000001014044b4
Copy the code

We know from the previous analysis

  • 0x00000001000088d8forisa
  • 0x000000010036a140Is a pointer to the parent classsuperclass
  • 0x0000000101404960 0x0001802800000003forcache
  • 0x00000001014044b4forbits.0x100008920forbitsThe pointer,bitsThe type ofclass_data_bits_t, which contains methodsdata()Access toclass_rw_t
(lldb) p (class_data_bits_t *)0x100008920 (class_data_bits_t *) $1 = 0x0000000100008920 (lldb) p *$1.data() (class_rw_t)  $2 = { flags = 2148007936 witness = 1 ro_or_rw_ext = { std::__1::atomic<unsigned long> = { Value = 4295001368 } } firstSubclass = nil nextSiblingClass = NSUUID } Fix-it applied, fixed expression was: *$1->data() (lldb) p/x 4295001368 (long) $3 = 0x0000000100008518Copy the code

Ro_or_rw_ext contains ro_OR_rw_ext, and ro_OR_rw_ext supports class_RO_T and class_rw_ext_t templates. So $3 is a pointer to class_ro_t

(lldb) p (class_ro_t *)$3
(class_ro_t *) $4 = 0x0000000100008518
(lldb) p *$4
(class_ro_t) $5 = {
    flags = 0
    instanceStart = 8
    instanceSize = 32
    reserved = 0
    = {
        ivarLayout = 0x0000000000000000
        nonMetaclass = nil
    }
    name = {
        std::__1::atomic<const char *> = "JPerson" {
            Value = 0x0000000100003c29 "JPerson"
        }
    }
    baseMethodList = 0x0000000100008048
    baseProtocols = 0x0000000100008500
    ivars = 0x0000000100008560
    weakIvarLayout = 0x0000000000000000
    baseProperties = 0x00000001000080b0
    _swiftMetadataInitializer_NEVER_USE = {}
}
Copy the code

Class_rw_t also has methods(), properties(), and protocols() methods

(lldb) p $2.methods()
(const method_array_t) $6 = {
    list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
    = {
        list = {
            ptr = 0x0000000100008048
        }
        arrayAndFlag = 4295000136
      }
   }
}
(lldb) p $2.properties()
(const property_array_t) $7 = {
list_array_tt<property_t, property_list_t, RawPtr> = {
    = {
        list = {
            ptr = 0x00000001000080b0
        }
        arrayAndFlag = 4295000240
    }
  }
}
(lldb) p $2.protocols()
(const protocol_array_t) $8 = {
list_array_tt<unsigned long, protocol_list_t, RawPtr> = {
    = {
    list = {
        ptr = 0x0000000100008500
    }
    arrayAndFlag = 4295001344
    }
  }
}
Copy the code

We can see that the pointer to methods() in class_rw_t is the same as the pointer to baseMethodList in class_ro_t, as well as the attribute class list and protocol list. Let’s take the method list as an example

(lldb) p $6.list.ptr (method_list_t *const) $9 = 0x0000000100008048 (lldb) p *$9 (method_list_t) $10 = { entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 4) } (lldb) p $10.get(0).big() (method_t::big) $11 = { name = "method_cat1" types = 0x0000000100003ea8 "v16@0:8"  imp = 0x00000001000038f0 (KCObjcBuild`-[JPerson(JPerson_category) method_cat1]) } (lldb) p $10.get(1).big() (method_t::big) $12 = { name = "method_1" types = 0x0000000100003ea8 "v16@0:8" imp = 0x0000000100003900 (KCObjcBuild`-[JPerson method_1]) } (lldb) p $10.get(2).big() (method_t::big) $13 = { name = "nickName" types = 0x0000000100003ec3 "@16@0:8" imp = 0x0000000100003910 (KCObjcBuild`-[JPerson nickName]) } (lldb) p $10.get(3).big() (method_t::big) $14 = { name = "setNickName:" types = 0x0000000100003f72 "v24@0:8@16" imp = 0x0000000100003930 (KCObjcBuild '-[JPerson setNickName:])} (LLDB) p $10.get(4).big(Copy the code

We see method_1 in the protocol JProtocol, but we don’t see method_2. We see method_cat1 in the JPerson_category but not method_cat2. This indicates that unimplemented methods do not exist in the method list.

We see that the instanceMethod instanceMethod does not see the classMethod because the classMethod is stored in the metaclass

Big () is because the information in method_t is wrapped so it can’t be accessed directly

struct method_t { ...... // 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; }; . public: big &big() const { ASSERT(! isSmall()); return *(struct big *)this; }... };Copy the code

Here we simply verify where the method is stored, leaving a lot of questions to verify:

  • agreementHow are provided properties stored? Are member variables generated?
  • categoryHow are provided properties stored? Are member variables generated?

We will verify this further in a later article.

Refer to the article

# Class structure analysis

# iOS Low-level exploration – instance object creation

# WWDC2020

# Non Fragile ivars