IOS & OpenGL & OpenGL ES & Metal

First, explore the idea of the underlying principle

1. Start with main

What if we want to start exploring the underbelly of iOS, but don’t know where to start?

Start with the main function!

Let’s start with the God view! To observe a rough loading process. To make preparations:

  1. Break the point directly in main

2. Next symbol breakpoint_objc_init 3. Close the first button in the debug bar on the left

Run! Take a look at the call stack information:

We can see a general flow from the bottom to the top:

ImageLoader contains our classes, categories, attributes, protocols, methods, method numbers, and so on. We will explore it later.

2, how to find the specific implementation method in the source code

2.1 Trigger thinking

So let’s look at a piece of code like this

  Person *p1 = [Person alloc];
  Person *p2 = [p1 init];
  Person *p3 = [p1 init];
  NSLog(@" %@ - %p",p1,&p1);
  NSLog(@" %@ - %p",p2,&p2);
  NSLog(@" %@ - %p",p3,&p3);
Copy the code

Print result:

<Person: 0x600003b3fc30> - 0x7ffee88cc138
<Person: 0x600003b3fc30> - 0x7ffee88cc130
<Person: 0x600003b3fc30> - 0x7ffee88cc128
Copy the code

Conclusion:

  • Printing three objects gives the same result, but it doesn’t mean they are the same object; We print three Pointers, three Pointers are different so they’re not the same object, but their Pointers point to the same piece of memory
  • That is, alloc opens up memory, but init doesn’t modify that memory

So, the idea is to see how the alloc method is implemented, but there’s no way to just command it.

Then we have to learn how to find the source code!

2.2 Source code exploration methods

Here are three ways: note that the real machine debugging break point is arm64, the emulator is looking for x86

  1. Direct the breakpoint:CTRL + (left) inSkip to Assembly and findlibobjc.A.dylibDynamic library source code

  1. Lower sign breakpoint: breakpoint first (avoid skipping to the method of the same name, it is safer to first exact breakpoint), then sign breakpoint. That’s what I mentioned above_objc_initSwitch toallocCan) findSymbolic BreakpointOption, type the breakpoint in Symbol, and run to jump to assemblylibobjc.A.dylib`+[NSObject alloc]:

  1. assemblyAlways Show Disassembly is checked in the Debug WorkFlow section of Xcode. In the Debug WorkFlow section of Xcode, Always Show Disassembly is checkedobjc_allocinside

In all three ways, we can figure out, if you want to look at the source of Alloc, if you want to find the libobJC dynamic library, you can go to the official website.

Objc4-756.2 – the source code

Direct download does not work, go to debug: objC4-756.2 source code compiler debugging

The latest version of 779.1 is optimized for callalloc method. It will be updated later when there is time. If you are interested, please explore by yourself.

The latest is ObjC4-779.1, download: Apple open Source -10.15

Objc4-779.1 source code compilation and debugging

2. Start exploring alloc

As we know, alloc is used to create objects, allocate memory space (allocate memory space to make it have corresponding Pointers). The object then has the corresponding pointer address, which means that it owns the memory space.

We can use a card breakpoint, card into alloc, and then LLDB debugging, read register read. Alloc has the ability to allocate memory by seeing if x0 returns a pointer address. (There is a stipulation that x0 is the passer of the first argument and, when returned, is where the returned value is stored.)

Have interest, can oneself debug try, the result is affirmative of course! Alloc is the ability to allocate memory for an object.

Here we are in libobJC-756.2 configured source code to explore, rather than debugging assembly in the project much more fun ~

1. Preparation

In libObjc source, create a new Target custom project file to run. Execute the following code (note that this is a dynamic library, running on the MAC, not into the iOS sandbox, not running on iOS)

Person *p = [Person alloc];
Copy the code

So we can command all the methods that are associated with breakpoints, and then we can look at what’s going on in alloc, and see how it’s going to run.

2. Alloc execution process in the source code

2.1 alloc

So first the command goes to the alloc method

+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code

2.2 _objc_rootAlloc

The second step comes to the _objc_rootAlloc method

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code

2.3 callAlloc

Step 3 comes to the callAlloc method, but!! There’s a fork in the road! Don’t worry, let’s take our time, it will go to the class_createInstance method (objc has two versions: objc and objc2). We’re using the latest objC2.)

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate // shortcutting optimizations. static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { /* #define fastpath(x) (__builtin_expect(bool(x), #define slowpath(x) (__builtin_expect(bool(x), 0)) Fastpath (x) indicates high likelihood of doing something after if slowpath(x) indicates high likelihood of doing something after else, which means no if here is a null */ if (slowPath (checkNil &&! cls)) return nil; #if __OBJC2__ if (fastpath(! CLS ->ISA()->hasCustomAWZ())) {No alloc/allocWithZone implementation. Go straight to the allocator. I didn't implement alloc, I didn't implement allocWithZone and I'm going to get here, so I'm going to go ahead and open up memory. // Fixme Store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summary Fix classes with no metaclass, that is, classes that do not inherit from NSObject, to determine if the current class can quickly open up memory, note that this is never called, If (fastPath (CLS ->canAllocFast())) {// No canAllocFast, raw isa, etc. Go straight to the metal. bool dtor = cls->hasCxxDtor(); id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(! obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { // Has ctor or raw isa or something. Use the slower path. id obj = class_createInstance(cls, 0); if (slowpath(! obj)) return callBadAllocHandler(cls); return obj; } } #endif // No shortcuts available. if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; }Copy the code

2.4 class_createInstance

Step 4 goes to the class_createInstance method. Continue!

id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

Copy the code

2.5 _class_createInstanceFromZone

Step 5 goes to the _class_createInstanceFromZone method. Read on here to find that Calloc can’t click in! Since this part of the code is in the malloc source code, we will talk about it later.

CLS ->instanceSize class size obj = (id)calloc(1, size); Calloc opens up this big space, and this space is obj obj->initInstanceIsa and you associate obj with class by ISA and then you return obj, and that completes the process

static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, Bool cxxConstruct = true, size_t *outAllocatedSize = nil) {if (! cls) return nil; // Assert whether CLS implements assert(CLS ->isRealized()); Bool hasCxxCtor = CLS ->hasCxxCtor(); Bool hasCxxDtor = CLS ->hasCxxDtor(); Bool fast = CLS ->canAllocNonpointer(); Size_t size = CLS ->instanceSize(extraBytes); // outAllocatedSize = nil, skip if (outAllocatedSize) *outAllocatedSize = size; id obj; // Where zone is passed nil, fast is passed true, so if (! Zone && fast) {/* Calloc opens up the obj space in malloc, and obj is the return value of this method through initInstanceIsa method, By associating obj with class (the first argument we passed in) via ISA, we have successfully created space for our object. How about size in calloc? How much memory is opened up? Obj = (id)calloc(1, size); // Return nil if (! obj) return nil; // pass CLS and C++ destructor to initInstanceIsa, instantiate isa obj->initInstanceIsa(CLS, hasCxxDtor); } else {// If the zone is not empty, after testing analysis, in general, the call to alloc will not come here, Only allocWithZone // or copyWithZone will come to the following logic if (zone) {// open up memory according to the given zone and size obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else {obj = (id)calloc(1, size); } // Return nil if (! obj) return nil; // Use raw pointer ISA on the assumption that they might be // doing something weird with the zone or RR obj->initIsa(cls); If (cxxConstruct && hasCxxCtor) {obj = _objc_constructOrFree(obj, CLS);} // if C++ initializes constructors and destructors, make optimizations to speed up the process. } return obj; }Copy the code

2.6 AlloC process Summary

We execute alloc method to find the flow according to a custom class. If it is in other cases, different flow direction may occur. So let’s summarize the general process of initialization:

2.7 allocWithZone

We click through to see the implementation method step by step (that is, 2.1~2.5), but when we are more careful and run debugging at the point where all the alloc related functions are interrupted, we will find that the objc_alloc method is entered first

// Calls [cls alloc].
id
objc_alloc(Class cls)
{
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
}
Copy the code

Then in the callAlloc method, since the arguments checkNil = true and allocWithZone = false are passed in, we go straight to the return [CLS alloc] method, and we go through 2.1 to 2.5.

I also went to look up some materials, some god pointed out that macho generation will bind a symbol, which will bind the number of SEL_alloc to objc_alloc, this part is not really open source. But the objc_alloc will only go once and will eventually go through the normal flow of alloc. And find out where to test the conjecture:

/*********************************************************************** * fixupMessageRef * Repairs an old vtable dispatch call site. * vtable dispatch itself is not supported. **********************************************************************/ static void fixupMessageRef(message_ref_t *msg) { msg->sel = sel_registerName((const char *)msg->sel); If (MSG - > imp = = & objc_msgSend_fixup) {/ / * * * look here!!!!!! If (MSG -> SEL_alloc) {MSG ->imp = (imp)&objc_alloc; } else if (msg->sel == SEL_allocWithZone) { msg->imp = (IMP)&objc_allocWithZone; } else if (msg->sel == SEL_retain) { msg->imp = (IMP)&objc_retain; } else if (msg->sel == SEL_release) { msg->imp = (IMP)&objc_release; } else if (msg->sel == SEL_autorelease) { msg->imp = (IMP)&objc_autorelease; } else { msg->imp = &objc_msgSend_fixedup; } } else if (msg->imp == &objc_msgSendSuper2_fixup) { msg->imp = &objc_msgSendSuper2_fixedup; } else if (msg->imp == &objc_msgSend_stret_fixup) { msg->imp = &objc_msgSend_stret_fixedup; } else if (msg->imp == &objc_msgSendSuper2_stret_fixup) { msg->imp = &objc_msgSendSuper2_stret_fixedup; } #if defined(__i386__) || defined(__x86_64__) else if (msg->imp == &objc_msgSend_fpret_fixup) { msg->imp = &objc_msgSend_fpret_fixedup; } #endif #if defined(__x86_64__) else if (msg->imp == &objc_msgSend_fp2ret_fixup) { msg->imp = &objc_msgSend_fp2ret_fixedup; } #endif }Copy the code

FixupMessageRef is actually called from _read_images, it’s read file after startup, it’s basically broken, it goes in there to fix it, it binds, and then it calls objc_alloc. (Expect apple to open source it later for further research)

AllocWithZone is essentially the same as alloc, but in the early days of OC, programmers needed to use things like allocWithZone to optimize the memory structure of objects. We’ll write that alloc and allocWithZone are exactly the same at the bottom.

Start exploring init

Now that we’ve done alloc, let’s look at init. And you can see that init is just lazy and it just returns self. You’re essentially handing over most of the work to Alloc.

The only function of init is to normalize the code and leave it to subclasses to rewrite it.

+ (id)init {
    return (id)self;
}

- (id)init {
    return _objc_rootInit(self);
}

Copy the code
id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}
Copy the code

4. Start exploring new

If you go to the source code, you’ll see that it’s even lazier than init, just returning the alloc+init method.

+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}
Copy the code

5. Memory Size and byte alignment

When we first explored alloc, CLS ->instanceSize got the size of the object. How did we calculate this size? How much memory does our object need to store its properties? It’s like, if you’re building your own house, how many rooms are you going to have based on who’s going to live there, how many people are going to live there?

Step by step:

1. Start with thissizeMethods of

CallAlloc = size_t size = CLS ->instanceSize(extrabize);Copy the code

2. Then comeinstanceSizeThe minimum size is 16 bytes

size_t instanceSize(size_t extraBytes) { size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects to be at least 16 bytes. // Minimum return 16 bytes if (size < 16) size = 16; return size; }Copy the code

3. Then comealignedInstanceSizeMethod to align with our instance object

Uint32_t alignedInstanceSize() { Return word_align(unalignedInstanceSize()); }Copy the code

4. Then comeword_alignMethod, which is a byte alignment algorithm: representationMemory is aligned in multiples of 8 bytes(There are calculation comments in the code)

Data is stored in bytes and transferred in bits. A bit represents a 0 or a 1 in binary. Every eight bits make up a byte.

Pointer: In 64-bit, 8 bytes (64 bits = 8 bytes). In 32 bits, it is 4 bytes (32 bits = 4 bytes).

# define WORD_MASK 7UL static uint32_t word_align(uint32_t x) { So x=8 (x + WORD_MASK) = 15 binary: 0000 1111 WORD_MASK = 7 binary: 0000 0111 ~WORD_MASK = ~7 binary: 1111 1000 (x + WORD_MASK) &~ WORD_MASK = 15&~ 7 Here ~7 is to complete 8 bytes, and +7 is to round up to a multiple of 8. WORD_MASK = 19 binary: 0001 0011 0001 0011&1111 1000 = 0001 0000 So: this algorithm, is to get a multiple of 8 results! That is: memory aligned in multiples of 8 bytes!! */ return (x + WORD_MASK) & ~WORD_MASK; }Copy the code

5. And finallyunalignedInstanceSize()Method, this method is mainly to get some information in the data section of the instance object, that is, to calculate the size of the information required, and then byte alignment, return the final size

	// May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() {
        assert(isRealized());
        return data()->ro->instanceSize;
    }
Copy the code

A small extension

Why align with 8 bytes?

For example: I have 5 attributes, 3, 5, 2, 4, 6 bytes, stored in memory. However, when we read it, it’s hard to say, how do I know how many bytes are in your first property? Or how many bytes do I have to take to get your first property? Even if I know a few bytes, I look up three bytes the first time and read them out. Then look up five more bytes and read it out. Also very time-consuming operation ~

As a result, the CPU has a “space for time” way, 8-byte storage, 8-byte read, 8-byte read. This improves readability a lot!

Six, summarized

1. The underlying alloC process is: alloc -> _objc_rootAlloc -> callAlloc -> class_createInstance -> _class_createInstanceFromZone Calculate the memory size instanceSize, create space calloc, isa Point space to initInstanceIsa.

Init -> _objc_rootInit() returns self. What it really means is that it’s up to subclasses to customize overrides.

3, new underlying analysis: the underlying is to return the [callAlloc() init] method directly. CallAlloc is the same thing as alloc, which is what you do with alloc + init.

4. Memory allocation: (64-bit) The attributes of objects in memory are aligned in multiples of 8 bytes, with a minimum of 16 bytes (presumably to prevent some out-of-bounds operations, if less than 16 bytes, some space will be reserved)


As we explore the underlying alloc process today, we still have some questions, such as: what is the true principle of memory alignment? An implementation in Calloc? How does ISA relate objects and classes? These questions will be explored in the next chapter