In iOS development, when you’re creating an object, you often use a function called alloc, so today you’re going to explore the underlying principles of alloc, and as the saying goes, the best way to do a good job is to use a good tool, so let’s look at some of the basic things that you need to do to explore the underlying things.

The preparatory work

  1. Objc is an important tool to explore the underlying principles. We can learn about its design philosophy and the process of program operation through apple’s open source stuff. The latest one is OBJC4-824, and the one running on my device is OBJC4-779.1. The main reason is that my computer is an antique machine in the middle of 13 years, and the system version is stuck at 10.14 without upgrading. Because the upgrade is compared with the card, the highest configuration can only be OBJC4-779.1.

  2. Must have some understanding of iOS development, at least iOS actual development experience at least half a year or more it.

  3. You have a Mac with Xcode installed on it.

  4. Specific objC source configuration issues can be found in Cooci’s Github address for missing files and errors.

. Well, that’s all for now

Exploring the underlying principles of Alloc

Author: Bala Bala Reader: No picture, say a hammer Author: Received, as follows:

  1. First open the configured oneobjcThe project. When the project is opened, it looks like the figure below:

  1. So let’s start with the main point of today’s exploration, when we create an object, we always write [KGPerson alloc], what does this alloc do, I think a lot of people can answer that, but how does it work internally, how much memory does the object create? How do you calculate that? A lot of people come in here and they turn on the 10,000 whys. Well, let’s start exploring these questions together.

  2. In the compiled Objc project, we create the NSObject based class KGPerson in the KCObjcTest folder, and then introduce the header # import “kgPerson.h” into the main.m file. The contents of each file are as follows:

KGPerson. H file

KGPerson. M file

The main m file

  1. And then we havemain.mFileKGPersonObject with the following code:
KGPerson *person = [KGPerson alloc];
Copy the code
  1. And then we clickallocEnter theobjcWe’ll see that in the source codeallocWhen a method is implemented with only a return value, and the return value is another method, the system will go directly to the method that returns the value. If we turn on the disassembly mode and look at the assembly code,allocSo the first method that we call iscallAlloc
+ (id)alloc {
    return _objc_rootAlloc(self);
}
Copy the code
  1. The whole point of this method is to pass in two values to the next functioncheckNilAs well asallocWithZone, after the specific role to the use of the place to explain again, click_objc_rootAllocThen we jumped into the_objc_rootAlloc() {return () {return () {return ();
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
Copy the code
  1. Two special macros appear in this methodslowpath(tells the compiler there is a high probability that the condition passed in will turn out to be false) andfastpath(tells the compiler that there is a high probability that the condition passed in will turn out to be true), the others have comments in the code summary, and clickcallAllocFunction, found after entering the function implementation, changes, appearifAnd then we’re going to have a problem, where does this program go when it runs? Don’t worry, let’s take our time,__OBJC2__This keyword is used to determine the version of objc, and now we all use the new version of objc, so we’ll go to the if condition, and continue to look at the code:
Static ALWAYS_INLINE ID callAlloc(Class CLS, bool checkNil, bool allocWithZone=false) { Slowpath (checkNil &&!) cls)) return nil; /* Tells the compiler that there is a high probability that the passed condition will turn out to be true. This is to check if the passed class implements the allocWithZone method. cls->ISA()->hasCustomAWZ())) { return _objc_rootAllocWithZone(cls, nil); } #endif // No shortcuts available. if (allocWithZone) { return ((id(*)(id, SEL, struct _NSZone *))objc_msgSend)(cls, @selector(allocWithZone:), nil); } return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc)); }Copy the code
  1. After entering the function, we found that it returned a function directly, the code is as follows, and then we continue to look at_class_createInstanceFromZoneFunction implementation.
id _objc_rootAllocWithZone(Class cls, Malloc_zone_t *zone __unused) {// allocWithZone under __OBJC2__ ignores the zone parameter // The zone parameter no longer uses the class to create instance memory space return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }Copy the code

  1. _class_createInstanceFromZoneThis method is the core of alloc code, which looks like this:
static ALWAYS_INLINE id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, Int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil) {/** lock: Hold the runtime lock. */ ASSERT(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; ExtraBytes =0 size = CLS ->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else {obj = (id)calloc(1, size); } if (slowpath(! obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (! Zone && fast) {// connect CLS to obj->initInstanceIsa(CLS, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (fastpath(! hasCxxCtor)) { return obj; } construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }Copy the code

Size = CLS ->instanceSize(extraBytes); This code, this is to calculate how much memory space the current object needs, let’s look at the implementation of instanceSize.

inline size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); Size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects to be at least 16 bytes. // This is why the new class has an attribute in it but requires the same space if (size < 16) size = 16; return size; }Copy the code

Size_t size = alignedInstanceSize() + extraBytes; In this case, extraBytes are passed 0 from the start of the function call, so let’s look at the implementation of alignedInstanceSize and word_align.

// Class's rounded up to a pointer-size boundary. Uint32_t alignedInstanceSize() const {// Return word_align(unalignedInstanceSize()); } static inline size_t word_align(size_t x) { // (x + WORD_MASK) & ~WORD_MASK (x + WORD_MASK) & ~WORD_MASK (x + WORD_MASK); Return (x + WORD_MASK) & ~WORD_MASK; Return (x + WORD_MASK) >> 3 << 3; }Copy the code

Then go back to the _class_createInstanceFromZone function and call calloc to open up the memory space.

  1. After the memory space is opened, callobject_cxxConstructFromClassFunction for object creation and assignment. Let’s look at this function:
id object_cxxConstructFromClass(id obj, Class cls, int flags) { ASSERT(cls->hasCxxCtor()); // required for performance, not correctness id (*ctor)(id); Class supercls; supercls = cls->superclass; // Call superclasses' ctors first, if any. if (supercls && supercls->hasCxxCtor()) { bool ok = object_cxxConstructFromClass(obj, supercls, flags); if (slowpath(! ok)) return nil; // some superclass's ctor failed - give up } // Find this class's ctor, if any. ctor = (id(*)(id))lookupMethodInClassAndLoadCache(cls, SEL_cxx_construct); if (ctor == (id(*)(id))_objc_msgForward_impcache) return obj; // no ctor - ok // Call this class's ctor. if (PrintCxxCtors) { _objc_inform("CXX: calling C++ constructors for class %s", cls->nameForLogging()); } if (fastpath((*ctor)(obj))) return obj; // ctor called and succeeded - ok supercls = cls->superclass; // this reload avoids a spill on the stack // This class's ctor was called and failed. // Call superclasses's dtors to clean up. if (supercls) object_cxxDestructFromClass(obj, supercls); if (flags & OBJECT_CONSTRUCT_FREE_ONFAILURE) free(obj); if (flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; }Copy the code

If (supercls && supercls->hasCxxCtor()), and then recursively call the C++ constructor from the base class. If the build succeeds, the function returns the object. If the build fails, it returns nil and runs an exception, destroying all previously constructed data. And then we’re going to go back layer by layer, and we end up with KGPerson’s object.

  1. Create an empty object to get the actual allocated memory and the required memory:
16-8Copy the code

Then we add two properties to the KGPerson class, as follows:

@interface KGPerson : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end
Copy the code

Then we run the program and print access as follows:

32-24Copy the code

Conclusion: Attributes have an impact on the memory footprint of a class

  1. After analyzing the principles of memory allocation, we can explore that an object contains more information, such as:isa,objc_ivar_list,objc_method_list,objc_cache,objc_protocol_listWait, how do we determine which factors affect the size of the class? Next, analyze slowly:
  • First let’s see if ISA affects, look at the following example code:

From the output, we can see that the object created by NSObject, without adding any properties or members or methods or protocols, takes 8 bytes by default, and our ISA pointer happens to be 8 bytes, so can we say that isa takes 8 bytes? Let’s start by creating a class LGPerson that inherits from NSObject, add a protocol method, a class method and an object method to the class, and look at the print. This code looks like this:

Lgperson. h code:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol LGPerosnDelegate <NSObject>

- (void)weShouldRun;

@end

@interface LGPerosn : NSObject

+ (void)what;

- (void)weWillCan;

@end

NS_ASSUME_NONNULL_END
Copy the code

Lgperson.m:

#import "lgperosn. h" @implementation LGPerosn + (void)what{NSLog(@" what "); } - (void)weWillCan{NSLog(@" what can I do "); } @endCopy the code

Then we look at the printout, which looks like this:

We can clearly see that class methods, protocol methods, and object methods have no effect on the size of the class object, so we are left with attributes and member variables. Let’s add a member variable to LGPerson and see how it prints.

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol LGPerosnDelegate <NSObject>

- (void)weShouldRun;

@end

@interface LGPerosn : NSObject{
    NSString *_name;
    NSInteger _age;
}

+ (void)what;

- (void)weWillCan;

@end

NS_ASSUME_NONNULL_END
Copy the code

Then we look at the printout, which looks like this:

The effect of member variables is obvious from the output, and the size of the change is 8 bytes aligned. Then we continue to observe, remove the member variable, and then add the property, lgPerson.h to modify the code as follows:

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol LGPerosnDelegate <NSObject>

- (void)weShouldRun;

@end

@interface LGPerosn : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) NSInteger age;

+ (void)what;

- (void)weWillCan;

@end

NS_ASSUME_NONNULL_END
Copy the code

Then we look at the printout, which looks like this:

The effect of member variables is obvious from the output, and the size of the change is 8 bytes aligned.

Then we can conclude that the factors that affect the size of the memory space created by an object are member variables and attributes. And because isa isa property of the object itself, so isa is also affected, the default size of 8 bytes is occupied by the isa pointer.

13. This is the end of the alloc search

Conclusion:

The individual is also gradually groping forward, if there is a mistake, please timely contact correction, to prevent misleading others, god don’t bang!!