Objc_object and objc_class

First, let’s look at the relationships and differences between NSObject, Class, objc_Object, objc_class, and ID.

  • NSObject: base class in OC, inherited by most classesNSObject(NSProxyAlso base class oh ~)
  • Class:NSObjectType, inobjcSource,NSObject.mmYou can see it in the file
+ (Class)class {
    return self;
}
Copy the code
  • Objc_object:NSObjectThe structure name of the class in the underlying C++ implementation, as seen in the generated CPP file, andNSObjectIt’s the same thing, but with different names in different contexts.objc_objectInside the structure, there is only one member variableisaPointer.
typedef struct objc_object NSObject;

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
Copy the code
  • Objc_class:ClassThe structure name implemented at the bottom of C++, as seen in the generated CPP file, andClassIt’s the same thing, but with different names in different contexts.
typedef struct objc_class *Class;
Copy the code
  • The id: OC environment can point to any type and does not have an asterisk
typedef struct objc_object *id;
Copy the code
Isa and superClass exploration

Let’s first look at the memory location of the class, and see if every time we create an object, we create a new corresponding class.

void getSJPersonClass(void) {
    Class cls1 = SJPerson.class;
    Class cls2 = [SJPerson alloc].class;
    Class cls3 = object_getClass([SJPerson alloc]);
    Class cls4 = [SJPerson alloc].class;
    NSLog(@"cls1 : %p \n cls1 : %p \n cls1 : %p \n cls1 : %p \n", cls1, cls2, cls3, cls4);
}
Copy the code

Execute getSJPersonClass and print the following result:

So there’s only one copy of the same class in memory.

We print the memory of the instance variable P, and we know that the first eight bits are isa Pointers

Isa address and ISA_MASK do and operation, can get the address of the class object.

Let’s continue with the isa address and ISA_MASK of the class and see what we get.

We can see that the final result is also SJPerson, but the addresses of the two sjPersons are different, one is 0x00000001000085F8, the other is 0x00000001000085D0, that is to say, these two are not the same type. The second SJPerson is what we call a metaclass. Why metaclass? We can find out about it in machOView:

You see that there’s a metaclass which is a metaclass. So let’s go ahead and see where the metaclass isa points to.

The isa of the SJPerosn metaclass is an ONObject, but the address of the NSObject metaclass is not the same as the address of the NSObject metaclass. The isa of the SJPerosn metaclass is an ONObject, but the address of the NSObject metaclass is not the same. SJPerson inherits from NSObject, so if that means a metaclass that points directly to the parent class, let’s create SJStudent inheriting from SJPerson and try again.

We see$3withA $5Same, that is, no matter who the object inherits, custom object’s metaclassisaAll point to the root metaclass. Moving on, let’s seeNSObjectmetaclassisaWhere to point.


1 with 1 and
2 is the same, which means the root metaclassisaPoint to itself.isaWe use the following code to test the inheritance of a metaclass:

NSObject *obj = [NSObject alloc]; Class objCls = object_getClass(obj); Class objMetaCls = object_getClass(objCls); Class objRootMetaCls = object_getClass(objMetaCls); NSLog(@"NSObject Instance object: %p \n NSObject Class object: %p \n NSObject Metaclass object: %p \n NSObject Root metaclass object:  %p \n ", obj, objCls, objMetaCls, objRootMetaCls); Class pMetaCls = object_getClass([SJPerson class]); Class pMetaSuperCls = class_getSuperclass(pMetaCls); NSLog(@"SJPerson metaclass object: %p \n Parent class of SJPerson metaclass object: %p \n ", pMetaCls, pMetaSuperCls); Class sMetaCls = object_getClass([SJStudent class]); Class sMetaSuperCls = class_getSuperclass(sMetaCls); NSLog(@"SJStudent metaclass: %p \n SJStudent metaclass: %p \n ", sMetaCls, sMetaSuperCls); Class rootMetaSuperCls = class_getSuperclass(objRootMetaCls); NSLog(@" Parent of the root metaclass object: %p", rootMetaSuperCls);Copy the code

Print result:

NSObject Instance object 0x101A30050 NSObject Class object 0x10036A140 NSObject metaclass object: 0x10036A0F0 NSObject Root metaclass object: 0x10036A0F0 SJPerson metaclass object: 0x100008530 SJPerson Parent of the SJStudent metaclass object: 0x10036A0F0 SJStudent Metaclass object: 0x100008580 Parent of the SJStudent metaclass object: 0x100008530 Parent of the root metaclass object: 0x10036A140Copy the code

Based on the results, we see that the parent of the SJStudent metaclass is the SJPerson metaclass, and the parent of the SJPerson metaclass is the NSObject metaclass, and the parent of the NSObject metaclass is the NSObject class object. Isa flowchart and inheritance flowchart are as follows:

Class memory data

OC class in C++ is objc_class type, we find objc source code objc_class structure. We found three structures

struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if ! __OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE;Copy the code

Since our current environment is OBJC2, this marks OBJC2_UNAVAILABLE, which we can ignore. Objc-runtime-new. h and objc-runtime-old.h files, we don’t want to look at new directly. The structure inside is omitted after the method:

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
}

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
Copy the code

That is, there are isa Pointers, superclasses, cache, and bits in the class structure. Isa isa superclass, cache isa superclass, and bits are bits. Before we do that, let’s verify the superclass:

SJPersonIs the parent classNSObjectNo problem. To studybitsFirst we need to knowbitsIn the memory storage location,isaIt’s 8 bytes,superclassThere’s nothing to say about 8 bytes, socacheHow many bytes? Let’s click on itcache_tThe structure of the:

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}
Copy the code

Take a look at the explicit_atomic implementation

template <typename T> struct explicit_atomic : public std::atomic<T> { explicit explicit_atomic(T initial) noexcept : std::atomic<T>(std::move(initial)) {} operator T() const = delete; T load(std::memory_order order) const noexcept { return std::atomic<T>::load(order); } void store(T desired, std::memory_order order) noexcept { std::atomic<T>::store(desired, order); } // Convert a normal pointer to an atomic pointer. This is a // somewhat dodgy thing to do, but if the atomic type is lock // free and the same size as the non-atomic type, we know the // representations are the same, and the compiler generates good // code. static explicit_atomic<T> *from_pointer(T *ptr) { static_assert(sizeof(explicit_atomic<T> *) == sizeof(T *), "Size of atomic must match size of original"); explicit_atomic<T> *atomic = (explicit_atomic<T> *)ptr; ASSERT(atomic->is_lock_free()); return atomic; }};Copy the code

T generic. The size of this variable depends on the size of the generic T. Uintptr_t pointer takes up 8 bytes, the second variable shares the body, mask_t: typedef uint32_t; Uint16_t uses 2 bytes, uint16_t structure uses 8 bytes, _originalPreoptCache is an *, which means the pointer type is also 8 bytes, so cache_t is 16 bytes in total. Bits Starts from 32 bytes. To get data from bits, you can find the data method in the source code.

class_rw_t *data() const {
        return bits.data();
    }
Copy the code

The only thing in there that can guess the meaning of words isfirstSubclass, the first subclass, butSJPersonThere’s a subclassSJStudentAh, why is the data in herenil? If the OC class is not used, it will not be loaded.

SJStudent *s = [SJStudent alloc];
SJPerson *p = [SJPerson alloc];
Copy the code

That’s when we seefirstSubclassThere’s something in there. There’s nothing wrong. But THEN I thought, what ifSJPersonThere are other subclasses, and what is this value going to be? Create a newSJHandsomeManInherited fromSJPerson.

The result is different, which means that the value of firstSubclass is the last class of the subclass initialized.

Attributes of a class

Ok, so now that we’re done, what if we want to look at a property in a class? A: Find a member variable or method from the class_rw_t structure.

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 *>(&ro_or_rw_ext)->properties; } else { return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties}; }}Copy the code

Member variable not found, method does appear to have one, so we continue debugging.

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) { }
};
Copy the code
template <typename Element, typename List, template<typename> class Ptr> class list_array_tt { struct array_t { uint32_t count; Ptr<List> lists[0]; static size_t byteSize(uint32_t count) { return sizeof(array_t) + count*sizeof(lists[0]); } size_t byteSize() { return byteSize(count); }}; }Copy the code
struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
Copy the code
template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
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(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }

    size_t byteSize() const {
        return byteSize(entsize(), count);
    }
    
    static size_t byteSize(uint32_t entsize, uint32_t count) {
        return sizeof(entsize_list_tt) + count*entsize;
    }

    struct iterator;
}
Copy the code

Property_array_t = property_array_t It’s an array, and every element in that array is property_list_t, which is also an array, because entsize_list_tt has an iterator inside it, The element inside the property_list_t array is property_t. General structure: property_array_t = [property_list_t[property_t, property_t], property_list_t[property_t]].

Look at theproperty_tThe structure of the:

struct property_t {
    const char *name;
    const char *attributes;
};
Copy the code
Methods of a class

After we look at properties, let’s look at methods. Add two methods in SJPerson, one instance method and one class method.

@interface SJPerson : NSObject

@property (nonatomic, copy) NSString *name;

- (void)sayNB;
+ (void)say666;

@end
Copy the code

Class_rw_t, class_rw_t, class_rw_t, class_rw_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 *>(&ro_or_rw_ext)->methods; } else { return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()}; }}Copy the code

Method_array_t has the same structure as property_array_t, which is a two-dimensional array, except that the innermost object is method_t.

When we see the same operation, why can’t we print the data at last? Is it our wrong operation? That’s the pointproperty_tThere are two members in there, so when you print out the two members, let’s seemethod_tThe structure does not have such a member, but it does have a structure:

struct big {
    SEL name;
    const char *types;
    MethodListIMP imp;
};
Copy the code

The member can see all the contents of the method, shall we print the big? Test it out:

It’s all perfectly printed out, but looking at it myself, there are only three methods,say666It’s not printed. Why is that? Object methods exist in the class structure, so what if the class methods are inside the metaclass? Let’s verify:

We seesay666That is, the class methods are stored in the metaclass. That’s why iOS has metaclasses, because OC has instance methods and class methods, so called minus methods and plus methods, but underneath OC, it doesn’t have that meaning, that means all the methods are converted to functions of the same name. If there’s no metaclasses, let’s write two methods, okay+(void)say666and-(void)say666These two methods are stored in the class, so when we execute the method, according to the function name, we can not determine whether to execute the instance method or the class method, so we design the metaclass to separate the two methods.

Class member variables

Class_ro_t Evolution In the WWDC video, we can see the class_RO_T evolution optimization process in detail, and also know that member variables exist in class_RO_T. Add a member variable hobby to SJPerson.

@interface SJPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *name;
- (void)sayNB;
+ (void)say666;

@end
Copy the code

How do I find class_ro_t, also in the source code

const class_ro_t *ro() const {
    auto v = get_ro_or_rwe();
    if (slowpath(v.is<class_rw_ext_t *>())) {
        return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
    }
    return v.get<const class_ro_t *>(&ro_or_rw_ext);
}
Copy the code

Debugging verification:

TypeEncoding

The types of symbols printed in the above method are TypeEncoding in iOS. See # Type Encodings for the meaning of each symbol. The first letter: the return value type. First number: how much memory is occupied by all the parameters of this method. Second letter: The type of the first argument. Second number: the first parameter starts at the memory number. Third letter: The type of the second argument. Third number: the second parameter starts at the memory number. . And so on.

Summary: Classes are essentially structures containing ISA, superclass, cache, and bits. Isa uses bitfields to store class-specific information, superclasses to store superclasses, cache to store method caches, and bits to store attribute lists, instance variable lists, method lists, and protocol lists.

__has_feature (ptrauth_calls) is introduced

  • __has_feature: This function determines whether the compiler supports a feature

  • Ptrauth_calls: pointer authentication for arm64E architecture; Devices using Apple A12 or later A series processors (such as iPhone XS, iPhone XS Max, and iPhone XR or newer devices) support the ARM64E architecture

  • Preparing Your App to Work with Pointer Authentication

  • The following uses real iPhone 12 and iPhone 8 respectively for verification

Verification Method 1: Use the ISA to verify storage data
  • becausearm64If and else branches store different data structuresweakly_referencedValue for validation
  • The test code
LGPerson *p = [LGPerson alloc]; __weak typeof(p) weakP = p; NSLog(@"p:%@", p); Copy the codeCopy the code
  • In __weak Typeof (p) weakP = p; The value of Weakly_referenced is 0 before execution and will change to 1 after execution

  • To test the iPhone 8

    • Perform before

    • After the execution:

  • To test the iPhone 12

    • Perform before

    • After performing

  • Verification results: From the verification results of the above two devices, it can be seen that __weak Typeof (P) weakP = P is executed; Before and after, iPhone 8 changes bit 43 (from right to left) and iPhone 12 changes bit 3 (from right to left), which correspond to the ELSE and if branches of the ARM64 architecture

Verification method two: through breakpoints and assembly verification
  • in[LGPerson alloc]Set a breakpoint at
  • After execution at the breakpoint, add a symbolic breakpoint_objc_rootAllocWithZoneAnd continue running
  • iPhone 8

  • iPhone 12

  • Verification results: iPhone 8The use of the0xffffffff8.iPhone 12The use of the0x7ffffffffffff8In theobjcSource,isa.hIt is found that the two values correspond to respectivelyarm64In the architectureelseThe branchISA_MASKValues andifThe branchISA_MASKvalue

conclusion
  • The __has_feature(ptrauth_calls) is used to determine whether the compiler supports pointer authentication

  • Devices above the iPhone X series (arm64E architecture, devices using Apple A12 or later A series processors) support pointer authentication

  • For arm64 architecture

    • IPhone X series and above (included) devices use the following structure:
    # define ISA_MASK 0x007ffffffffffff8ULL # define ISA_MAGIC_MASK 0x0000000000000001ULL # define ISA_MAGIC_VALUE 0x0000000000000001ULL # define ISA_HAS_CXX_DTOR_BIT 0 # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t weakly_referenced : 1; \ uintptr_t shiftcls_and_sig : 52; \ uintptr_t has_sidetable_rc : 1; \ Uintptr_t EXTRA_RC: 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7Copy the code
    • The following structure is used for devices below iPhone X (not included) :
    #     define ISA_MASK        0x0000000ffffffff8ULL
    #     define ISA_MAGIC_MASK  0x000003f000000001ULL
    #     define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #     define ISA_HAS_CXX_DTOR_BIT 1
    #     define ISA_BITFIELD                                                      \
            uintptr_t nonpointer        : 1;                                       \
            uintptr_t has_assoc         : 1;                                       \
            uintptr_t has_cxx_dtor      : 1;                                       \
            uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
            uintptr_t magic             : 6;                                       \
            uintptr_t weakly_referenced : 1;                                       \
            uintptr_t unused            : 1;                                       \
            uintptr_t has_sidetable_rc  : 1;                                       \
            uintptr_t extra_rc          : 19
    #     define RC_ONE   (1ULL<<45)
    #     define RC_HALF  (1ULL<<18)
    Copy the code