preface

What is the nature of the object? We often hear that objects are structures in nature, so how do we verify that? NONPOINTER_ISA = NONPOINTER_ISA = NONPOINTER_ISA With that in mind, we compiled the OC code with the compiler to see if the compiled code could find any clues.

Compiler Introduction

Clang compiler

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.

The Clang use

Clang-rewrite-objc main.m -o main. CPP compiles the object file into a c++ file

If the command line project is no problem, smooth compilation success!

If it is an iOS project, an error will be reported:

The solution is to specify the SDK path (iphonesimulator14.2. SDK should be modified to the corresponding version of your machine)

Clang-rewrite-objc-fobjc-arc-fobjc-runtime = ios-14.2-isysroot / Applications/Xcode. App/Contents/Developer/Platforms/iPhoneSimulator platform/Developer/SDKs/iPhoneSimulator14.2. The SDK main2.m -o main2.cppCopy the code

It can also be compiled using Xcode’s own xcrun command:

# emulator xcrun - SDK iphoneos clang -arch arm64 -rewrite-objc main2.m -o main. CPP arm64 -rewrite-objc main.m -o main.cppCopy the code

Compare the code before and after compilation

Pre-compile code

@interface WJPerson : NSObject

@property (nonatomic.strong) NSString *sName;
@property (nonatomic.copy) NSString *cName;

@end

@implementation WJPerson

@end
Copy the code

Compiled code

// omit the above configuration code
#ifndef _REWRITER_typedef_WJPerson
#define _REWRITER_typedef_WJPerson
typedef struct objc_object WJPerson;
typedef struct {} _objc_exc_WJPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_WJPerson$_sName;
extern "C" unsigned long OBJC_IVAR_$_WJPerson$_cName;
struct WJPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_sName;
	NSString *_cName;
};

// @property (nonatomic, strong) NSString *sName;
// @property (nonatomic, copy) NSString *cName;

/* @end */


// @implementation WJPerson


static NSString * _I_WJPerson_sName(WJPerson * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_WJPerson$_sName)); }
static void _I_WJPerson_setSName_(WJPerson * self, SEL _cmd, NSString *sName) { (*(NSString((* *)char *)self + OBJC_IVAR_$_WJPerson$_sName)) = sName; }

static NSString * _I_WJPerson_cName(WJPerson * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_WJPerson$_cName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long.id.bool.bool);

static void _I_WJPerson_setCName_(WJPerson * self, SEL _cmd, NSString *cName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WJPerson, _cName), (id)cName, 0.1); }

// @end

/ / to omit...

Copy the code

The analysis process

1, the originalWJPersoninheritanceNSObjectIt becomes inheritanceobjc_object

typedef struct objc_object WJPerson;
Copy the code

2,WJPersonClass intoWJPerson_IMPLStruct, so classThe essence is the structure, the declaration ofattributeAlso help us generate automaticallyAn underlined member variable.

struct WJPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_sName;
	NSString *_cName;
};
// @property (nonatomic, strong) NSString *sName;
// @property (nonatomic, copy) NSString *cName;
Copy the code

3,WJPerson_IMPLMember variables instruct NSObject_IMPL NSObject_IVARS;We don’t declare that it’s an inherited member variable. The structure can be found in the compiled codeobjc_objectNSObject_IMPLThere’s a member variable in thereClass isa.

typedef struct objc_class *Class;

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

struct NSObject_IMPL {
	Class isa;
};
Copy the code

4,WJPerson.mI didn’t declare any methods, but the compiled code has 4 more methods, respectivelysName,cNamethesettergetterFunction implementation. Look closely and you’ll seesName,cNameDifferent implementation methods,sNameIs read and write by memory translation, andcNameIs through theobjc_setPropertyIs to achieve.sName,cNameThey’re all attributes, just different keywords,sNamestrongModified,cNameiscopyDecorated, so we can conclude that the decorated keyword changes the underlying implementation.

// @implementation WJPerson

static NSString * _I_WJPerson_sName(WJPerson * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_WJPerson$_sName)); }
static void _I_WJPerson_setSName_(WJPerson * self, SEL _cmd, NSString *sName) { (*(NSString((* *)char *)self + OBJC_IVAR_$_WJPerson$_sName)) = sName; }

static NSString * _I_WJPerson_cName(WJPerson * self, SEL _cmd) { return(* (NSString((* *)char *)self + OBJC_IVAR_$_WJPerson$_cName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long.id.bool.bool);

static void _I_WJPerson_setCName_(WJPerson * self, SEL _cmd, NSString *cName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct WJPerson, _cName), (id)cName, 0.1); }
// @end
Copy the code

NONPOINTER_ISA

Before exploring NONPOINTER_ISA, let’s look at structures, unions, and bitfields

Comparison between structure, bit domain and union

The structure of the body

struct Car1 {
    BOOL front; 
    BOOL back;
    BOOL left;
    BOOL right;
};
struct Car1 car1;
NSLog(@"%ld".sizeof(car1));
Copy the code

The memory size of car1 is 4 bytes. If you don’t know how to calculate the size of the structure, you can see the internal memory alignment of the structure I wrote earlier.

A domain

/ / the mutex
struct Car2 {
    BOOL front: 1; // Use one bit
    BOOL back : 1;
    BOOL left : 1;
    BOOL right: 1;
};
struct Car2 car2;
NSLog(@"%ld".sizeof(car2));
Copy the code

The BOOL type is either 1 or 0, so one bit will do. Car2 uses four bits, which is only half a byte, and the smallest unit is one byte, so the memory size of car2 output above is 1 byte. Three bytes less than the structure.

A consortium

/ / structure
struct Person1 {
    char        *name;
    int         age;
    double      height ;
};

/ / a consortium
union Person2 {
    char        *name;
    int         age;
    double      height ;
};
Copy the code
struct Person1  person1;
person1.name    = "person1";
person1.age     = 18;
person1.height  = 180;

union Person2   person2;
person2.name = "person2";
person2.age  = 18;
person2.height  = 180;
NSLog(@"%ld-%ld".sizeof(person1),sizeof(person2));
// Print: 24-8
Copy the code

In each step, the attribute of Person1 is successfully assigned and the previously assigned attribute remains unchanged.

(lldb) p person1
(Person1) $0 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p person1
(Person1) $1 = (name = "person1", age = 0, height = 0)
(lldb) p person1
(Person1) $2 = (name = "person1", age = 18, height = 0)
(lldb) p person1
(Person1) $3 = (name = "person1", age = 18, height = 180)
(lldb) 
Copy the code

In each step, the person2 attribute is assigned successfully, but the other attributes are either dirty or empty, and only the currently assigned attribute is a normal value.

(lldb) p person2
(Person2) $4 = (name = 0x0000000000000000, age = 0, height = 0)
(lldb) p person2
(Person2) $5 = (name = "person2", age = 15952, height = 2.1220036723004548 e-314)
(lldb) p person2
(Person2) $6 = (name = "", age = 18, height = 2.1219957998584539 e-314)
(lldb) p person2
(Person2) $7 = (name = "", age = 0, height = 180)
(lldb) 
Copy the code

So we can come to the conclusion that,

  • Unions are mutually exclusive and share a memory address that affects other member variables
  • The structure is co-existing, and the member variables are each occupying their own memory, with complementary effects
  • A domain (bit field), C allows a structure to specify the memory size of its members in bits, using bitfields (bit field) can store data with fewer bits,Apple uses bitfields and unions to optimize memory for classes

NONPOINTER_ISA

ISA is divided into pure ISA and NONPOINTER_ISA, which contains some information about the class in addition to a pure pointer.

In the previous iOS source exploration alloc, we will eventually bind the structure pointer from the heap to the current class via obj->initIsa(CLS).

inline void 
objc_object::initIsa(Class cls)
{
    initIsa(cls, false.false);
}
Copy the code
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT boolhasCxxDtor) { ASSERT(! isTaggedPointer()); isa_t newisa(0);
    // Omit some code
 }


Copy the code
/ / share
union isa_t {
   // constructor
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    / / variable
    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_BITFIELDinarm64andx86_64Definitions under the architecture

# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
/ / simulator
#   if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#     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<<7)
#   else
/ / real machine
#     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)
#   endif

# elif __x86_64__
//macOS
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   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          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t unused            : 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
Copy the code
  • Nonpointer: indicates whether pointer optimization is enabled for isa Pointers. 0: indicates pure ISA Pointers. 1: indicates not only the address of the class object, but also the class information and reference count of the object

  • Has_assoc: flag bit of the associated object. 0 does not exist and 1 exists

  • Has_cxx_dtor: does the object have a destructor for C++ or Objc? If it has a destructor, the destructor logic needs to be done. If not, the object can be freed faster

  • Shiftcls: Stores the value of the class pointer. With pointer optimization turned on, 33 bits are used to store class Pointers in the ARM64 architecture

  • -magic: Used by the debugger to determine whether the current object is a real object or has no space to initialize

  • Weakly_referenced: A weak variable that records whether an object is pointed to or used to point to an ARC. Objects without weak references can be released faster

  • Unused: Indicates whether it has not been used

  • Has_sidetable_rc: hash table that needs to be borrowed to store carry when object reference technique is greater than 10

  • Extra_rc: When representing the reference count of this object, the reference count is actually subtracted by 1. For example, if the object’s reference count is 10, the extra_rc is 9. If the reference count is greater than 10, the following has_sideTABLE_rc is used