While we’ve explored NSObject’s alloc method and memory alignment rules, we’ll focus on the nature of OC objects in this article.

Find the definition of an object

Referring to the nature of the object, we first think of is to see the source code, that how to locate the content of the object definition in the source code. If you don’t know what to do, let’s define an object and initialize it, and use clang to convert OC code to c++ code for clues. We define a class JSPerson and then initialize the object:

@interface JSPerson : NSObject

@property (nonatomic, strong) NSString *firstName;

@property (nonatomic, strong) NSString *lastName;
@end
   
@implementation JSPerson
@end
   
#import "JSPerson.h"
int main(int argc, const char * argv[]) {
  @autoreleasepool {
      JSPerson *person = [[JSPerson alloc] init];
      person.firstName  = @"Jason";
      person.lastName    = @"Test";
      NSLog(@"%@",person.firstName);
      NSLog(@"%@",person.firstName);
      }
   return 0;
 }
Copy the code

M to convert the main.m file into c++ code: clang-rewrite-objc main.m -o main. CPP We searched for JSPerson in the main.cpp file and found the following code:

typedef struct objc_object JSPerson;
/// omit code
struct JSPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_firstName;
	NSString *_lastName;
};
Copy the code

The JSPerson_IMPL structure contains two attributes we defined _firstName and _lastName, indicating that the nature of the object is an objC-object structure

typedef struct objc_object JSPerson; Struct JSPerson_IMPL {struct NSObject_IMPL NSObject_IVARS; NSString *_firstName; NSString *_lastName; };Copy the code

The JSPerson_IMPL structure contains two attributes we defined _firstName and _lastName, indicating that the nature of the object is an objC-object structure

typedef struct objc_object JSPerson; Struct JSPerson_IMPL {struct NSObject_IMPL NSObject_IVARS; NSString *_firstName; NSString *_lastName; };Copy the code

JSPerson_IMPL contains two attributes (firstname and lastname), indicating that the object is of type objC-object. The first attribute of JSPerson_IMPL is NSObject_IVARS. It is not a property we defined. We searched for its type NSObject_IMPL and found:

struct NSObject_IMPL {
	Class isa;
};
Copy the code

NSObject_IVARS is the ISA pointer. Let’s start exploring ISA next. Before exploring isa Pointers, let’s look at a conceptual bitfield

A domain

Let’s define two structures JSCar1 and JSCar2, and print the size of the two structures in the main method

struct JSCar1 {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};
struct JSCar2 {
    BOOL front: 1;
    BOOL back : 1;
    BOOL left : 1;
    BOOL right: 1;
};
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct JSCar1 car1;
        struct JSCar2 car2;
        NSLog(@"%ld-%ld",sizeof(car1),sizeof(car2));
    }
Copy the code

Found the result of the print is

4- 1
Copy the code

Why structure properties of the number and type are the same, while the structure is not the same as the size of the memory, this is the role of a domain, behind the structure properties and: 1 said this attribute only takes up a (note that this is not a byte, a byte is eight bits), so the car2 four attributes only to take four, But memory allocation is at least one byte (8 bits), so car2’s memory size is 1 byte. Here’s an example:

You can see that we can save memory by using bitfields (4B->1B).

A consortium

In addition to using bitfields in structures, we can also achieve savings by using unions:

union JSCar3 {
    BOOL front;
    BOOL back;
    BOOL left;
    BOOL right;
};
struct JSCar1 car1;
struct JSCar2 car2;
union JSCar3 car3;
NSLog(@"%ld-%ld-%ld",sizeof(car1),sizeof(car2),sizeof(car3));
Copy the code

We define a union JSCar3 and print its size:

4- 1- 1
Copy the code

We can see that car3 union has a memory size of 1, so it’s easy to guess that car2 and CAR3 have the same memory size, so is the memory structure the same? With this question in mind we continue to explore and print the corresponding structure using LLDB at the arrow position in the figure:

Here is the print:

(lldb) p car2
(JSCar2) $0 = (front = NO, back = NO, left = NO, right = NO)
(lldb) p car2
(JSCar2) $1 = (front = 255, back = NO, left = NO, right = NO)
(lldb) p car2
(JSCar2) $2 = (front = 255, back = 255, left = NO, right = NO)
(lldb) p car3
(JSCar3) $3 = (front = NO, back = NO, left = NO, right = NO)
(lldb) p car3
(JSCar3) $4 = (front = YES, back = YES, left = YES, right = YES)
(lldb) p car3
(JSCar3) $5 = (front = YES, back = YES, left = YES, right = YES)
Copy the code

We found from the result of print, car2 an attribute of the assignment will not affect other properties, and car3 an attribute’s value has changed, and values of other attributes will also change, car2 and car3 memory storage structure is different, variable is the coexistence of structure members, a consortium member variables are mutually exclusive, general group and domain.

The memory rules for unions are as follows

  • Multiple members can be defined in a union, and the size of the union is determined by the largest member size.
  • Members of a consortium share a single memory and can only use one member at a time.
  • Assigning a value to one member overwrites the values of other members.

isaPointer to the

With this knowledge in mind, we move on to the ISA pointer. Let’s open objc source and find the definition of isa pointer:

union isa_t {
    // constructor
    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(a) {
        return extra_rc == 0 && has_sidetable_rc == 0;
    }
    void setDeallocating(a) {
        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 isa union, and it has a bitfield member ISA_BITFIELD.

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

These bits represent the following:

  • Nonpointer: indicates whether to enable pointer optimization for isa Pointers. 0: indicates pure ISA Pointers. 1: indicates that isa** contains not only the address of the class object but also the reference count of the object
  • Has_assoc: flag bit of the associated object. 0 does not exist and 1 exists
  • Has_cxx_dtor: does this object have a destructor ** of C++ or Objc? If it has a destructor, it needs to do the destructor logic. If it does not, it can release the object 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.
  • Deallocating: Indicates whether the object is freeing memory
  • Has_sidetable_rc: When the object reference technique is greater than 10, this variable is borrowed to store carry
  • 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.

On the x86 platform, the isa pointer 4-48 stores the address of the class. To verify our interpretation, we initialize the JSPerson instance in the main method:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JSPerson *p = [JSPerson alloc];/ / break point
        NSLog(@"% @",p);
    }
    return 0;
}
Copy the code

We break the point at the comment position, using the LLDB command, and print the address of p:

(lldb) x/4gx p
0x1005186b0: 0x001d800100008309 0x0000000000000000
0x1005186c0: 0x63756f54534e5b2d 0x746e6f4372614268
(lldb) p/x 0x001d800100008309 >> 3 // Isa pointer address moved 3 bits to the right
(long) $2 = 0x0003b00020001061
(lldb) p/x 0x0003b00020001061 << 20// Move 20 bits to the left.
(long) $3 = 0x0002000106100000
(lldb) p/x 0x0002000106100000 >> 17// Move 17 to the right
(long) $4 = 0x0000000100008308 // The bitwise operation shiftcls
(lldb) p/x JSPerson.class
(Class) $5 = 0x0000000100008308 JSPerson Print the address of the JSPerson class directly
Copy the code

Using the bit operations above, we can determine that Shiftcls stores the address of the JSPerson class.

In our actual development, the isa of the object we define is basically nonpointer. The benefits are self-evident, which can increase the utilization of memory and reduce memory waste. So much for exploring the nature of objects, which we will continue to explore in subsequent articles.