OC is an object-oriented language. What is an object? Today we are going to explore the nature of objects

Know the clang

Clang is a lightweight compiler for C, C++, and Objective-C. Source code is published under the BSD protocol. Clang will support its normal lambda expressions, simplified handling of return types, and better handling of constEXPr keywords.

Clang is an Apple-led, LLVM-based C/C++/Objective-C compiler.

Clang is a C/C++/Objective-C/Objective-C++ compiler written in C++, based on LLVM and published under the LLVM BSD license. It is almost completely compatible with the GNU C language specification (although there are some incompatibables, including some differences in compiler command options) and adds additional syntactical features such as C function overloading (which modifies functions by __attribute__((overloadable)), One of its goals is to go beyond GCC. Clang official documentation

View the source code to explore the nature of the class

generatecppfile

Create a Person class

@interface Person : NSObject

@end


@implementation Person

@end
Copy the code

Clang-rewrite-objc main.m -o main.cpp to compile object file into c++ file UIKit error – fobjc – runtime = ios – 13.0.0 – isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator13.0. The SDK main.m

Xcode was installed with the xcrun command, which encapsulates clang, To better use some emulators xcrun-sdk iphonesimulator clang-arch arm64-rewrite-objc main.m -o main-arm64. CPP phone xcrun-sdk iphoneOS clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

Then we have a CPP file

View the CPP file

Search for the Person file we created

View the createdPersonclass

#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif

struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};
Copy the code

We find that Person is a structure underneath and has one element

struct NSObject_IMPL NSObject_IVARS;
Copy the code

This is a pseudo-inheritance of the structure and what is the NSObject_IMPL if you search in the CPP file

struct NSObject_IMPL {
	Class isa;
};
Copy the code

Summary: define an alias Person that points to struct objc_object;

In the structure implementation Person_IMPL, there isa member variable NSObject_IVARS from the inherited structure, isa; Our Person inherits NSObject, which at the bottom is objc_Object

Question: 🤔️ why is isa type Class?

  • InitInstanceIsa method, one of the core methods of alloc, was mentioned in the article. By looking at the source code implementation of this method, we found that when initializing isa pointer, it is initialized by isa_T type.
  • In NSObject, ISA is defined as Class. The fundamental reason is that ISA sends back Class information. In order to make developers more clear, isa needs to perform a type cast when it returns, similar to the strong cast of AS in Swift.

Explore the underlying

typedef struct objc_class *Class;


struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};


typedef struct objc_object *id;



typedef struct objc_selector *SEL;
Copy the code
  1. OCThe level ofNSObject, corresponding at the bottom levelobjc_objectStructure;
  2. A subclassisaBoth inherited fromNSObjectThat is, fromobjc_objectStructure;
  3. Objective-CIn theNSObjectIs the root class of most classes, whileobjc_objectIt can be understood thatc\c++The root class of the layer.
  4. isaThe type ofClass, is defined as pointingobjc_classThe pointer.
  5. ClassIt’s also a pointer to a structure
  6. It can be used in developmentidTo represent any object, and the root cause isidIs defined as pointingobjc_objectPointer toNSObjectThe pointer.
  7. SELMethod selector pointer, method number.

get/setmethods

Add a name attribute to the Person class above and generate a CPP file

static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }
Copy the code

As you can see from the above code, both get and set methods have two hidden arguments, self and _cmd, the method receiver and the method number. When obtaining attributes, the address of member variables is obtained by means of pointer translation, and the corresponding value is returned after conversion.

Objc_setProperty, the objc_setProperty method is automatically called when an instance variable is set. This method can be understood as the underlying adapter of set method, which realizes the unified entry of SET method through unified encapsulation.

In the Runtime source code, search objc_setProperty to find the final implementation, as shown in the following code:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { if (offset == 0) { object_setClass(self, newValue); return; } id oldValue; id *slot = (id*) ((char*)self + offset); if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return; newValue = objc_retain(newValue); Retain new value} if (! atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } // release objc_release(oldValue); }Copy the code

The essence is to find the position of the member variable by shifting the pointer, then retain the new value and release the old value.

conclusion

After exploring the underlying source code of objc_setProperty, the following points emerge:

  • The purpose of the objc_setProperty method is to associate the upper set method with the lower set method, which is essentially an interface

  • The reason for this design is that there are many upper set methods. If you call the lower set method directly, many temporary variables will be generated. When you want to find an SEL, it will be very troublesome

  • Based on the above reasons, apple has adopted the adapter design pattern (the underlying interface adapter for the client required interfaces), provides an interface that is for the use of the upper set method, internal calls at the bottom of the set method, make its are not affected each other, so no matter how the upper and the lower is constant, change can affect the upper or lower level, It is mainly used to isolate the upper and lower interfaces

Object size

An empty object

An empty object with a size of 8 bytesisa

To use class_getInstanceSize(), import the header file #import

Add attributes

If the size of the structure is calculated, it should be 48, and the printed size is 40.

Memory optimization found, storing two fields in an 8-byte space.

Adding member variables

The amount of memory used has increased

Add methods

There is no change in memory usage

Conclusion: 1. The size of an object is dependent on its member variables and attributes, not on methods. When adding a member variable, align the member variable with the largest data type because the data type of the member variable is inconsistent. Classes that inherit from NSObject objects, with a default byte alignment of 8 bytes. 3. Although the bottom layer is a structure, its size has been optimized

Attached is a description of X/NUF

x /nuf

X is printed in 16 bytes ————— n indicates the number of memory units to be displayed ————— U indicates the length of an address unit: B indicates single byte H indicates double bytes W indicates four bytes G indicates eight bytes ————— f indicates the display mode. The value can be: In hexadecimal format shows variable x d according to decimal format shows the variables u according to decimal format display unsigned integer o the octal format variable t according to the binary format display variables in hexadecimal format a variable I instruction address format c according to the character format display variable f the floating-point format variable example: X / 4Gx is printed in hexadecimal with 4 cells, each of which is 8 bytes long and displayed in hexadecimal

The object nature

Using the tool Clang to compile the generated CPP file, we can see that the object is essentially a structure. At the OC level, NSObject is the root class of most classes, while objc_Object can be understood as the root class at the C \ C ++ level. NSObject has only one instance variable, Class ISA, and Class is essentially a pointer to objc_class.

struct objc_class : objc_object { objc_class(const objc_class&) = delete; objc_class(objc_class&&) = delete; void operator=(const objc_class&) = delete; void operator=(objc_class&&) = delete; // 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 getSuperclass() const { #if __has_feature(ptrauth_calls) # if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH if (superclass == Nil) return Nil; #if SUPERCLASS_SIGNING_TREAT_UNSIGNED_AS_NIL void *stripped = ptrauth_strip((void *)superclass, ISA_SIGNING_KEY); if ((void *)superclass == stripped) { void *resigned = ptrauth_sign_unauthenticated(stripped, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS)); if ((void *)superclass ! = resigned) return Nil; } #endif void *result = ptrauth_auth_data((void *)superclass, ISA_SIGNING_KEY, ptrauth_blend_discriminator(&superclass, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS)); return (Class)result; # else return (Class)ptrauth_strip((void *)superclass, ISA_SIGNING_KEY); # endif #else return superclass; #endif }Copy the code

To explore the isa

A domain

Let’s start with a structure

The car element is a BOOL. It takes up 4 bytes. 32 bits is a waste of space.

Use bit-field optimization

Output:

car size 4 
car1 size 1
Copy the code

BOOL front:1; 1 indicates that 1 digit is occupied. 0000 0000 indicates front, back, left, and right from back to front

A consortium

Let’s start with a structure

struct Teacher {
    char name;
    int age;
    double height;
};
Copy the code

Assign a value to a structure

1. The statement

2.nameThe assignment

3.ageThe assignment

Proves that elements in the current structure can be assigned simultaneously

Union (common body)

union Teacher1 {
    char name;
    int age;
    double height;
};
Copy the code

The contents of the union are exactly the same as those of the structure above

Assign to the union

  1. The statement



  2. nameThe assignment



    At a givennameSo after I assign,ageandheightThey all have values, but this is dirty data

  3. ageThe assignment



    At a givenageSo after I assign,nameThe union is mutually exclusive

Isa isa consortium

When we look at the source code, when we alloc an object, we bind isa to the class, we use initIsa(), and initIsa() has an object isa_t, which isa union

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    uintptr_t bits;

private:
    // Accessing the class requires custom ptrauth operations, so
    // force clients to go through setClass/getClass by making this
    // private.
    Class cls;

public:
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };

    bool isDeallocating() {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating() {
        extra_rc = 0;
        has_sidetable_rc = 0;
    }
#endif

    void setClass(Class cls, objc_object *obj);
    Class getClass(bool authenticated);
    Class getDecodedClass(bool authenticated);
};
Copy the code

Isa_t isa union with two attributes Class CLS and Uintptr_t bits. These two attributes are mutually exclusive and the union occupies 8 bytes of memory space.

Class CLS, non-Nonpointer ISA, does not optimize Pointers to point directly to classes,

typedef struct objc_class *Class;
uintptr_t bits;
Copy the code

Nonpointer ISA, which uses the structural bit domain, provides different bit domain setting rules for arm64 and x86 architectures.

#if SUPPORT_PACKED_ISA // ios Real environment #if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL # 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 deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 19 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL # define ISA_BITFIELD \ uintptr_t nonpointer : 1; \ uintptr_t has_assoc : 1; \ uintptr_t has_cxx_dtor : 1; \ uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \ uintptr_t magic : 6; \ uintptr_t weakly_referenced : 1; \ uintptr_t deallocating : 1; \ uintptr_t has_sidetable_rc : 1; \ uintptr_t extra_rc : 8 # define RC_ONE (1ULL<<56) # define RC_HALF (1ULL<<7) # else # error unknown architecture for packed isa # endif // SUPPORT_PACKED_ISA #endifCopy the code

For the two different platforms, the isa storage situation is shown in the figure

Isa meaning

  • nonpointer: 1 bit, indicating whether it is correctisaPointer on pointer optimization,
    • Zero: pureisaA pointer,
    • 1: Not only class object address,isaIncluded in theThe class information,Object reference countAnd so on.
  • has_assoc: 1 bit, associated object flag bit,
    • 0 no,
    • 1.
  • has_cxx_dtor: 1 bit, whether the object hasC++orObjcIf there is a destructor, it needs to do the destructor logic; If not, the object can be released more quickly.
  • shiftcls: Stores the value of the class pointer. Turn on pointer optimization in case ofarm64In the architecture33Bits are used to store class Pointers inx86In the architecture44Bits are used to store class Pointers.
  • magic: 6 bits, used by the debugger to determine whether the current object is a real object or has no space to initialize.
  • weakly_referenced: 1 bit, indicating whether the object is or has been pointed to oneARCObjects without weak references can be freed faster.
  • deallocating: 1 bit, indicating whether the object is freeing memory.
  • has_sidetable_rc: 1 bit when the object reference count is greater than10, the variable needs to be borrowed to store carry.
  • extra_rc: represents the referential count value of the object, actually the referential count value minus1For example, if the object’s reference count is10, so extra_rc is9. If the reference count is greater than10, you need to use the followinghas_sidetable_rc.

To explore the isa

Alloc –> _objc_rootAlloc –> callAlloc –> _objc_rootAllocWithZone –> _class_createInstanceFromZone method path, Find initInstanceIsa and enter its principle implementation into the source implementation of the initIsa method, mainly to initialize the ISA pointer

The logic of this method is divided into two parts

  • throughclsInitialize theisa
  • throughbitsInitialize theisa

Isa’s association with classes

The principle of CLS association with ISA is that the shiftCls bit field in isa pointer stores the class information, where initInstanceIsa associates the Calloc pointer with the current CLS class

This is verified by the isa pointer address and the value & of ISA_MSAK

Create a Command Line Tool project for macOS

Here we seeIsa ` ` 64 bitsStorage conditions

Here,isaHow to get toPerson.classThis address is in the source code

Isa mask

persontheIsa & maskAnd what you get is 1, 2, 3Person.classWe start from the topisaYou can see that in the structurex86_64environmentshiftclsAccount for44From back to front, so it’s in60 [17]

Verify by bit operation