preface

This exploration began with a long-standing question I had about ARC. During the MRC era, I often wrote the following code:

- (void)dealloc { self.array = nil; self.string = nil; / /... // // Free memory of non-objC objects, such as CFRelease(...) / /... // [super dealloc]; }Copy the code

Object destructor will release other internal objects, the application of non-OBJC object memory will also be processed, and finally call super, continue to destruct the parent class object. Now, in the ARC era, only the following code remains:

- (void)dealloc{ // ... // // Free memory of non-objC objects, such as CFRelease(...) / /... / /}Copy the code

Here’s the question:

  1. Where is the release of the object instance variable (Ivars)?

  2. The call [super dealloc] is not shown. Where is the upper destructor

An explanation of the Dealloc process in the ARC documentation

The LLVM official ARC documentation provides a brief description of the DEALloc process under ARC, from which you can find some useful information:

A class may provide a method definition for an instance method named dealloc. This method will be called after the final release of the object but before it is deallocated or any of its instance variables are destroyed. The superclass’s implementation of dealloc will be called automatically when the method returns.

  • The dealloc method is called after the last release, but the instance variable (Ivars) has not been released. The parent class’s dealloc method will be called automatically when the subclass’s Dealloc method returns

The instance variables for an ARC-compiled class will be destroyed at some point after control enters the dealloc method for the root class of the class. The ordering of the destruction of instance variables is unspecified, both within a single class and between subclasses and superclasses.

  • ARC instance variables are released in the root class [NSObject dealloc], in an uncertain order (within a class, between subclasses and superclasses).

So, do not use the main tone [super dealloc] because of automatic tuning, how to implement later; ARC instance variables are destructed when the root class NSObject is destructed, which is explored below.

The destruction of NSObject

Using the Apple Runtime source code, you can see that NSObject executes dealloc by calling _objc_rootDealloc, object_Dispose, and then calling objc_destructInstance. The first steps are all conditional and simple jumps, and the final function looks like this:

void *objc_destructInstance(id obj){ if (obj) { Class isa_gen = _object_getClass(obj); class_t *isa = newcls(isa_gen); // Read all of the flags at once for performance. bool cxx = hasCxxStructors(isa); bool assoc = ! UseGC && _class_instancesHaveAssociatedObjects(isa_gen); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (! UseGC) objc_clear_deallocating(obj); } return obj; }Copy the code

I did three simple things:

  1. What does executing a thing called object_cxxDestruct do

  2. Execute _object_remove_assocations to remove the assocate object from this object (often used to add variable attributes to a category, that’s why

    ARC is not necessary to remove the reason

    (Edit: Remove is not required under ARC or MRC, thanks @Sagles for the gay love tips)

  3. Objc_clear_deallocating clears the reference count table and the weak reference table, locating all weak references to nil (which is where the weak variable can be safely empty)

So, the place where ARC automatically releases instance variables is lost inside cxxDestruct.

Explore the hidden. Cxx_destruct

The method named object_cxxDestruct found above ends up as the following call:

static void object_cxxDestructFromClass(id obj, Class cls){ void (*dtor)(id); // Call cls's dtor first, then superclasses's dtors. for ( ; cls ! = NULL; cls = _class_getSuperclass(cls)) { if (! _class_hasCxxStructors(cls)) return; dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); if (dtor ! = (void(*)(id))_objc_msgForward_internal) { if (PrintCxxCtors) { _objc_inform("CXX: calling C++ destructors for class %s", _class_getName(cls)); } (*dtor)(obj); }}}Copy the code

The code is also easy to follow, searching up the inheritance chain for the selector SEL_cxx_destruct, finding the function implementation (void (*)(id)(function pointer) and executing it. Search the selector declaration, and it turns out that there’s a method called.cxx_destruct, which starts with a dot, and I think like Unix files, it has hidden attributes

Effective Objective-C 2.0:

When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.

As you can see, the.cxx_destruct method was originally intended for C++ object destruct, and ARC has borrowed this method to insert code for automatic memory release

Find.cxx_destruct by experiment

The best way to do this is to write a test code to find the hidden method. There is nothing hidden in runtime. The simple class structure is as follows:

@interface Father : NSObject@property (nonatomic, copy) NSString *name; @end@interface Son : Father@property (nonatomic, copy) NSArray *toys; @endCopy the code

With only two simple properties, find a place to write simple test code:

// start{    // before new    Son *son = [Son new];    son.name = @"sark";    son.toys = @[@"sunny", @"xx"];    // after new}// gone
Copy the code

The main purpose is to make this object go through the Dealloc method. The new son object will be released when it passes the curly braces, so the son object is initialized on the After new line and dealloc on the gone line

Set a breakpoint at after new, run, trigger, and LLDB to print all Son method names with this extension:

The. Cxx_destruct method is found, and after several tests, it finds:

  1. This method only appears under ARC (test code)
  2. This method occurs only if the current class owns an instance variable (whether with property or not), and instance variables of the parent class do not cause subclasses to own the method
  3. It doesn’t matter if the variable is assigned, what is it assigned to

Use Watchpoint to locate the memory release time

Still at the after new breakpoint, type LLDB:

watchpoint set variable son->_name
Copy the code

Add the variable name to watchpoint and trigger when the variable is changed:

At this point, _name goes from 0x00006b98 to 0x0, nil.

Cxx_destruct method, which is freed during objc_storeStrong

Ask questions. Cxx_destruct

We know that the process of releasing object instance variables under ARC is done inside.cxx_destruct, but what happens inside this function and how does objc_storeStrong get rid of the variables? From the exploration above,.cxx_destruct is Code generated by the compiler, so it is likely to be completed at clang front-end compilation time, which reminds me of Clang’s Code Generation, Because I had been impressed by the official documentation for clang-rewrite-objc xxx.m before, Google:

Ps. IOS development exchange technology group: welcome to join, no matter you are big or small white welcome to enter, share BAT, Ali interview questions, interview experience, discuss technology, we exchange learning and growth together

.cxx_destruct site:clang.llvm.org
Copy the code

It was found that CodeGenModule module in Doxygen document of Clang was the implementation code of this part, and the digressions were deleted as follows:

/// EmitObjCIvarInitializations - Emit information for ivar initialization/// for an implementation.void CodeGenModule::EmitObjCIvarInitializations(ObjCImplementationDecl *D){ DeclContext* DC = const_cast<DeclContext*>(dyn_cast<DeclContext>(D)); assert(DC && "EmitObjCIvarInitializations - null DeclContext"); IdentifierInfo *II = &getContext().Idents.get(".cxx_destruct"); Selector cxxSelector = getContext().Selectors.getSelector(0, &II); ObjCMethodDecl *DTORMethod = ObjCMethodDecl::Create(getContext(), D->getLocation(), D->getLocation(), cxxSelector, getContext().VoidTy, 0, DC, true, false, true, ObjCMethodDecl::Required); D->addInstanceMethod(DTORMethod); CodeGenFunction(*this).GenerateObjCCtorDtorMethod(D, DTORMethod, false); }Copy the code

This function takes the selector of.cxx_destruct, creates a Method, and adds it to the Class’s list of methods. The call on the last line is the implementation that actually creates the Method. This method is located in the clang.llvm.org/doxygen/CGO… At line 1354, CXX methods are constructed and destructed. Cxx_destruct continues, and finally emitCXXDestructMethod is called:

static void emitCXXDestructMethod(CodeGenFunction &CGF, ObjCImplementationDecl *impl){ CodeGenFunction::RunCleanupsScope scope(CGF); llvm::Value *self = CGF.LoadObjCSelf(); const ObjCInterfaceDecl *iface = impl->getClassInterface(); for (const ObjCIvarDecl *ivar = iface->all_declared_ivar_begin(); ivar; ivar = ivar->getNextIvar()) { QualType type = ivar->getType(); // Check whether the ivar is a destructible type. QualType::DestructionKind dtorKind = type.isDestructedType(); if (! dtorKind) continue; CodeGenFunction::Destroyer *destroyer = 0; // Use a call to objc_storeStrong to destroy strong ivars, for the // general benefit of the tools. if (dtorKind == QualType::DK_objc_strong_lifetime) { destroyer = destroyARCStrongWithStore; // Otherwise use the default for the destruction kind. } else { destroyer = CGF.getDestroyer(dtorKind); } CleanupKind cleanupKind = CGF.getCleanupKind(dtorKind); CGF.EHStack.pushCleanup<DestroyIvar>(cleanupKind, self, ivar, destroyer, cleanupKind & EHCleanup); } assert(scope.requiresCleanups() && "nothing to do in .cxx_destruct?" ); }Copy the code

After analyzing this code and calling it, we find that it iterates through all the instance variables (Ivars) of the current object and calls objc_storeStrong. The ARC documentation for objc_storeStrong is as follows:

id objc_storeStrong(id *object, id value) { value = [value retain]; id oldValue = *object; *object = value; [oldValue release]; return value; }Copy the code

In. Cxx_destruct shaped like objc_storeStrong (& ivar, null) after the call, the instance variables will be release and set to nil note: real implementation can reference clang.llvm.org/doxygen/CGO… Line 2078

An implementation that automatically calls [Super Dealloc]

According to the above ideas, automatic call [super dealloc] also the work must be CodeGen in clang.llvm.org/doxygen/CGO… Line 492 StartObjCMethod:

if (ident->isStr("dealloc"))   EHStack.pushCleanup<FinishARCDealloc>(getARCCleanupKind());
Copy the code

When the dealloc method is called, the code is inserted, as defined by the FinishARCDealloc structure:

struct FinishARCDealloc : EHScopeStack::Cleanup { void Emit(CodeGenFunction &CGF, Flags flags) override { const ObjCMethodDecl *method = cast<ObjCMethodDecl>(CGF.CurCodeDecl); const ObjCImplDecl *impl = cast<ObjCImplDecl>(method->getDeclContext()); const ObjCInterfaceDecl *iface = impl->getClassInterface(); if (! iface->getSuperClass()) return; bool isCategory = isa<ObjCCategoryImplDecl>(impl); // Call [super dealloc] if we have a superclass. llvm::Value *self = CGF.LoadObjCSelf(); CallArgList args; CGF.CGM.getObjCRuntime().GenerateMessageSendSuper(CGF, ReturnValueSlot(), CGF.getContext().VoidTy, method->getSelector(), iface, isCategory, self, /*is class msg*/ false, args, method); }};Copy the code

The above code basically forwards the dealloc call to the parent class, which automatically calls the [super Dealloc] method.

conclusion

  • The ARC member variable is inserted by the compiler.cxx_desctructMethod automatic release
  • Under the ARC[super dealloc]Methods are also inserted automatically by the compiler
  • The so-calledThe compiler inserts codeThe process needs to be understood further and it is not clear how it works
  • The clangCodeGenIt’s also worth digging into