Memory management portal 🦋🦋🦋

Memory management analysis (a) – MANUAL memory management in the MRC era

Memory management analysis (two) – timer problem

Memory management analysis (three) – iOS program memory layout

Memory management analysis (5) — weak pointer implementation principle

Any developer who has lived through the MRC era will no doubt have used the AutoRelease method, which is used to hand objects over to AutoreleasePool and automatically release them when appropriate. To automatically free an object, you call the release method on the managed object. To understand how the AutoRelease method works, you first need to know what an Autorelease pool is.

Let’s look at a section of MRC code. Why discuss this problem under MRC? Since ARC will automatically add the autorelease code for us in place and won’t allow us to call the method manually, we’ll have to go back to MRC for the sake of studying the autorelease principle.

****************** main.m *****************
#import <Foundation/Foundation.h>
#import "CLPerson.h"

int main(int argc, const char * argv[]) {

    NSLog(@"pool--start");
    @autoreleasepool { 
        CLPerson *p = [[[CLPerson alloc] init] autorelease];
    } 
    NSLog(@"pool--end");

    return 0; } * * * * * * * * * * * * * *CLPerson.m **************
#import "CLPerson.h"

@implementation CLPerson

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [super dealloc];
}
@end****************** Result *******************2019- 0827 - 16:37:15.141523+0800 Interview16-autorelease[11602:772121] pool--start
2019- 0827 - 16:37:15.141763+0800 Interview16-autorelease[11602:772121] - [CLPerson dealloc]
2019- 0827 - 16:37:15.141775+0800 Interview16-autorelease[11602:772121] pool--end
Copy the code

To summarize what you see on the surface: The CLPerson instance object P is released at the end of the @Autoreleasepool {} brace. So what exactly does @Autoreleasepool {} do? We run the following command on the main.m file from the command line window

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

In the generated intermediate code main.cpp, the underlying implementation of the main function is found as follows

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
    return 0;
}
Copy the code

In fact, if you are familiar with the messaging mechanism, the above code can be translated into the following form

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { 
        __AtAutoreleasePool __autoreleasepool; 
        CLPerson *p = [[[CLPerson alloc] init] autorelease];
    }
    return 0;
}
Copy the code

We can see it by observation@autoreleasepool {}After compiling, the following transformation occurs

There is an __AtAutoreleasePool, which is actually a c++ structure and can be found in main. CPP as defined below

struct __AtAutoreleasePool {
    Constructor --> can be analogous to OC init method, called at creation time
  __AtAutoreleasePool()
    {
        atautoreleasepoolobj = objc_autoreleasePoolPush(a); }// Destructor --> can be analogous to OC's dealloc method, called at destruction time
  ~__AtAutoreleasePool()
    {
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    
  void * atautoreleasepoolobj;
};
Copy the code

If you are not familiar with C++ syntax, it can have functions (methods) just like OC classes. The __AtAutoreleasePool structure above already has two functions,

  • A constructor__AtAutoreleasePool() –> atautoreleasepoolobj = objc_autoreleasePoolPush();Is called when a structure is created and is used to initialize the structure
  • A destructor~__AtAutoreleasePool() –> objc_autoreleasePoolPop(atautoreleasepoolobj);Called when the structure is destroyed

Let’s go back to ourmainThe delta function, which is essentially the following formIt’s a single layer@autoreleasepool {}So if there are multiple layers@autoreleasepool {}Nested together, they can be disassembled according to the same rules

objc_autoreleasePoolPush() & objc_autoreleasePoolPop()

Let’s explore the implementation logic of these two functions. You can find their implementations in the objC4 source nsobject. mm file

*************** NSObject.mm (objc4) ******************
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push(a); }void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
Copy the code

As you can see, they call the C++ class AutoreleasePoolPage push() and pop() functions, respectively. To continue with the implementation logic of the following functions, we need to take a look at the internal structure of the AutoreleasePoolPage. It has a lot of content and a lot of functions, but we need to be clear about its member variables. These are mutable and manipulable, so we need to remove the functions and some static constants. The AutoreleasePoolPage structure can be simplified as follows

class AutoreleasePoolPage 
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}
Copy the code

According to its name, the Chinese definition is automatic release pool page, there is a page concept. We know that the autofree pool is used to store objects, and the “page” indicates that the structure of the free pool should have a page size limit (memory size). How big is that? Take a look at the two functions of AutoreleasePoolPage

id * begin(a) {

        return (id *) ((uint8_t *)this+sizeof(*this));
}

id * end(a) {
        return (id *) ((uint8_t *)this+SIZE);
}
Copy the code

The begin() function returns a pointer to the memory address after its last member variable. End () contains SIZE

static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *#define PAGE_MAX_SIZE           PAGE_SIZE* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *#define PAGE_SIZE               I386_PGBYTES* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *#define I386_PGBYTES            4096            /* bytes per 80386 page */

Copy the code

As you can see,SIZEIs, in fact,4096. That is to sayend()Function, which is a pointer toAutoreleasePoolPageThe number after the address of the object4096Memory address in bytes.

With this information in mind, let’s first throw out the conclusion and then move on to deeper understanding of the source code.

Each AutoreleasePoolPage object occupies 4096 bytes, of which member variables occupy 8 bytes * 7 = 56 bytes. The remaining 4040 bytes are used to store autofree objects.

Since the memory of an AutoreleasePoolPage object is limited and many objects may be added to the auto-release pool, there may be multiple AutoreleasePoolPage objects to hold the auto-release object. All AutoreleasePoolPage objects are connected together in a bidirectionally linked list (data structure).

The meanings of the member variables of the AutoreleasePoolPage object are as follows

  • magic_t const magic;
  • id *next;Point to theAutoreleasePoolPageThe next one can be used for storageAuto-release objectMemory address of
  • pthread_t const thread;Automatically frees the thread to which the pool belongs, indicating that it cannot be associated with multiple threads.
  • AutoreleasePoolPage * const parent;Pointer to the previous page release pool
  • AutoreleasePoolPage *child;Pointer to the next page release pool
  • uint32_t const depth;
  • uint32_t hiwat;

【 first AutoreleasePoolPage: : push ();.

Next, we’ll begin study AutoreleasePoolPage: : push (); . Suppose we are now at the beginning of the first @autoreleasepool {} in the project’s main function, where the entire program will call push() for the first time:

#   define POOL_BOUNDARY nil

static inline void *push(a) 
    {
        id *dest;
        if (DebugPoolAllocation) {// New pages are created for each autoRelease pool in Debug mode
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {// In standard case, the autoreleaseFast() function is called
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
Copy the code

Where POOL_BOUNDARY is nil macro definition, ignoring Debug mode, we just look at normal mode, then push() will call autoreleaseFast(POOL_BOUNDARY) to get an ID *dest and return it to the upper function. Take a look at this autoreleaseFast() and see what it returns to us

static inline id *autoreleaseFast(id obj)
    {
        // Get the currently available AutoreleasePoolPage object page
        AutoreleasePoolPage *page = hotPage(a);// (1) Add obj if page is not full
        if(page && ! page->full()) {
            return page->add(obj);
        } else if (page) {// (2) autoreleaseFullPage(obj, page);
            return autoreleaseFullPage(obj, page);
        } else {// (3) If there is no page, call autoreleaseNoPage(obj);
            return autoreleaseNoPage(obj); }}Copy the code

Since this is the first push operation of the program, the page object does not exist yet, so it will follow the case (3), that is, autoreleaseNoPage(obj); , the implementation is as follows

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        
        /*--"No page" 1. Can indicate that No pool has been created (pushed) 2 You can also indicate that an empty placeholder pool has been created, but nothing has been added */
        assert(!hotPage());
        
        
        
        
        
        
        // tag --> Whether additional POOL_BOUNDARY is needed
        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            /* If there is EmptyPoolPlaceholder, change the label to true and then add an additional POOL_BOUNDARY */ based on that label
            pushExtraBoundary = true;
        }
        
        /* If the obj passed is not equal to POOL_BOUNDARY (nil) and the current pool cannot be found (lost), return nil */
        else if(obj ! = POOL_BOUNDARY && DebugMissingPools) { _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug".pthread_self(), (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        
        /* ♥️♥️♥️♥️ If POOL_BOUNDARY is passed in and not in Debug mode, setEmptyPoolPlaceholder() is called to set an EmptyPoolPlaceholder */
        else if(obj == POOL_BOUNDARY && ! DebugPoolAllocation) {return setEmptyPoolPlaceholder(a); }// Initialize the first AutoreleasePoolPage
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        // Set it to the current page (hot)
        setHotPage(page);
        
        // pushExtraBoundary determines whether to push an additional POOL_BOUNDARY
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the obj passed in to the stack via the add () function
        return page->add(obj);
    }
Copy the code

Since AutoreleasePoolPage has not been created and EmptyPoolPlaceholder has not been set, the application will hit the code marked ♥️♥️♥️♥️ in the code and call setEmptyPoolPlaceholder(); , the function is implemented as follows

#   define EMPTY_POOL_PLACEHOLDER ((id*)1)
static pthread_key_t constkey = AUTORELEASE_POOL_KEY; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *static inline id* setEmptyPoolPlaceholder(a)
    {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }
Copy the code

As you can see, we bind key to (id*)1, which is a static constant, and return this (id*)1 as a null free pool placeholder, so the first push() function of the program ends, The result is that an EMPTY_POOL_PLACEHOLDER (that is, (id*)1) is generated as a free pool placeholder.

[First call to autoRelease]

Following the above procedure, the first time we execute an autoRelease on an object after push(), we look inside the autoRelease and see what the source code looks like. Okay

- (id)autorelease {
    return ((id)self)->rootAutorelease(a);//🈯️ go down here} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *inline id 
objc_object::rootAutorelease(a)
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2(a);//🈯️ go down here
}

************************************************
__attribute__((noinline,used))
id 
objc_object::rootAutorelease2(a)
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);//🈯️ go down here} * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *static inline id autorelease(id obj)
    {
        assert(obj);
        assert(! obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);//⚠️ finally comes to this method
        assert(! dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);return obj;
    }


Copy the code

As we work our way through the layers, we see that the AutoRelease method eventually leads to the autoreleaseFast() function

static inline id *autoreleaseFast(id obj)
    {
        // Get the currently available AutoreleasePoolPage object page
        AutoreleasePoolPage *page = hotPage(a);// (1) Add obj if page is not full
        if(page && ! page->full()) {
            return page->add(obj);
        } else if (page) {// (2) autoreleaseFullPage(obj, page);
            return autoreleaseFullPage(obj, page);
        } else {// (3) If there is no page, call autoreleaseNoPage(obj);
            return autoreleaseNoPage(obj); }}Copy the code

So this time, let’s look at the first line of code hotPage(); What do I get

static inline AutoreleasePoolPage *hotPage(a) 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        // Return nil if EMPTY_POOL_PLACEHOLDER is bound to the key
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        
        if (result) result->fastcheck(a);return result;// Returns the current page object
    }
Copy the code

Because we’re going to start withkeywithEMPTY_POOL_PLACEHOLDER Bound, so this returns null, which means the current page is empty and hasn’t been created yet, so we go back toautoreleaseFastMethod, will be calledautoreleaseNoPage(obj)Function, according to our previous comment on the steps of the function, this time the program should go to the last part of the functionI mainly did the following things:

  • Initialize the first oneAutoreleasePoolPage
  • Set it to the current page (HOT)
  • The originalEMPTY_POOL_PLACEHOLDERCan makepushExtraBoundarySet totrueSo this has to be the first oneAutoreleasePoolPageLet’s push one firstPOOL_BOUNDARY
  • In the end,add(obj)Automatically release an incoming objectobjInto the stack

The add() function assigns the value of obj to the memory space pointed to by the next pointer of the current AutoreleasePoolPage, and next performs the ++ operation to move the object to the next available memory space. The following

id *add(id obj)
    {
        assert(!full());
        unprotect(a); id *ret = next;// faster than `return next-1` because of aliasing
        *next++ = obj;// assign first, then ++
        protect(a);return ret;
    }
Copy the code

Also notice the setHotPage(page) function here, implemented as follows

static inline void setHotPage(AutoreleasePoolPage *page) 
    {
        if (page) page->fastcheck(a);tls_set_direct(key, (void *)page);
    }
Copy the code

It binds the newly created AutoreleasePoolPage to a key so that the hotPage() function can retrieve the current page directly from the key.

Call autoRelease again

If we continue to execute on the new objectautoreleaseThe operations will also come to the function, but because ofAutoreleasePoolPageThe object already exists if the currentpageIf not, the following function is performedThat’s just straight throughadd(obj)Function willobjObject into the stack

As mentioned earlier, an AutoreleasePoolPage object can hold a limited number of AutoreleasePoolPage objects. An AutoreleasePoolPage object is a pointer that takes up 8 bytes. An AutoreleasePoolPage object can hold 4040 bytes. AutoreleaseFast calls autoreleaseFullPage(obj, page) when a page may be full. Function, whose implementation is as follows

static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {// Get the next page object that is not full through the child pointer
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);// Set the page to the current page (hot)
        return page->add(obj);// Use the add function to store obj to the page
    }
Copy the code

The child pointer to the AutoreleasePoolPage object is used to find the next unfilled page. AutoreleasePoolPage objects are bidirectionally linked with child and parent Pointers for this purpose. Similarly, when emptying a free pool object, if the current free pool is completely empty, the parent pointer is used to find the upper free pool.

【 AutoreleasePoolPage again: : push ();.

In addition to the initial @Autoreleasepool {} layer in the main function, we may sometimes use @Autoreleasepool {} in our own code for more flexible memory management of some objects. @autoreleasepool {} must be nested inside the main function @autoreleasepool {}

int main(int argc, const char * argv[]) {
        @autoreleasepool {// This is the first layer added to the system
                @autoreleasepool {}// This is the inner nesting we might add}}Copy the code

Now let’s look at this one againAutoreleasePoolPage::push();How it will be executed. Again, the program will execute toautoreleaseFast(POOL_BOUNDARY);POOL_BOUNDARYWill be introduced toautoreleaseFastFunction, and it will also passadd()orautoreleaseFullPage()Is added to theAutoreleasePoolPageObject on the page space of. It’s just normal[obj autorelease]Same process, only this timeobj = POOL_BOUNDARY“Apparently this is for a new one@autoreleasepool{}To prepare for.

POOL_BOUNDARYWhat is it for? You’ll see in a minute.

Having analyzed the source code, I now use a legend to illustrate how @Autoreleasepool works. [Assumption] To facilitate display, each AutoreleasePoolPage can store only three release objects, as follows

When does the autoRelease object call the Release method back?

This problem needs to be made clear@autoreleasepool{}The other half of theAutoreleasePoolPage::pop(atautoreleasepoolobj);Did something. Take a lookThe core function isreleaseUntile(stop)Here,stopIn fact, it’s going to bePOOL_BOUNDARYEnter the function

void releaseUntil(id *stop) 
    {
        
        
        while (this->next ! = stop) {//🥝 If next points to POOL_BOUNDARY, break the loop 🥝
            
            //🥝 get the current page
            AutoreleasePoolPage *page = hotPage(a);//🥝🥝 If the current page is empty, use parent to get the previous AutoreleasePoolPage object as the current page
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect(a);//🥝🥝🥝 gets the top object of the current page through --next
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect(a);if(obj ! = POOL_BOUNDARY) {//🥝🥝🥝🥝 If obj is not POOL_BOUNDARY, perform [obj release]
                objc_release(obj); }}setHotPage(this);
    }
Copy the code

The core steps of pop() are shown in the comments in the function above. When the @autoreleasepool{} scope ends and the corresponding pop() function is called, the AutoreleasePoolPage is used to find the objects on the top of the stack and release them one by one until POOL_BOUNDARY is reached. This means that all objects contained within @autoRelease {} have completed the release call.

When the program reaches the end of the @Autoreleasepool {} scope at the next level, it goes back to the process above, calling the release method once for the objects it contains. You can use the example below to get a feel for it.


AutoreleasePool and RunLoop

@autoreleasepool{} calls objc_autoreleasePoolPush() at the beginning and end of the scope. And the objc_autoreleasePoolPop() function, but in iOS projects, when does @Autoreleasepool {} start and end in scope? This brings us to RunLoop, another knowledge point we studied earlier. We know that only the main thread in the program has a RunLoop unless we manually start the RunLoop for the child thread, which is enabled by default. Let’s take a look at what’s inside the main thread RunLoop.

We can just create a new iOS project, and in ViewController’s viewDidLoad method we can print the current RunLoop object.

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"% @",[NSRunLoop currentRunLoop]);
}

@end
Copy the code

The result is a huge pile of printouts, so if you’re not familiar with the RunLoop structure, you can refer to mineThe internal structure of Runloop and how it works”, which should be more clear. We can print the result incommon mode items Section, find two heelsautoreleaseThe relevantobserver, as shown in the figure belowSpecific as follows

<CFRunLoopObserver 0x600003f3c640 [0x10a2fdae8]>
{
valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d), 
context = 
<CFArray 0x6000000353b0 [0x10a2fdae8]>
    {
    type = mutable-small, count = 1, values = (0 : <0x7f91ff802058>)
    }
}


<CFRunLoopObserver 0x600003f3c500 [0x10a2fdae8]>
{
valid = Yes, activities = 0x1, repeats = Yes, order = - 2147483647., 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e17ac9d), 
context = 
<CFArray 0x6000000353b0 [0x10a2fdae8]>
    {
    type = mutable-small, count = 1, values = (0 : <0x7f91ff802058>)}}Copy the code

We can see that the two listeners are listening to the states respectively

  • activities = 0xa0(Corresponding to decimal160)
  • activities = 0x1(Corresponding to decimal1)

How do we interpret these two states? We can find the corresponding definition in the CF framework RunLoop source code

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), * * * * * * * * * * * * a decimal1--(enter loop) kCFRunLoopBeforeTimers = (1UL << 1* * * *), decimal2
    kCFRunLoopBeforeSources = (1UL << 2* *), decimal4
    kCFRunLoopBeforeWaiting = (1UL << 5* * *), decimal32----(loop about to hibernate) kCFRunLoopAfterWaiting = (1UL << 6* * * * *), decimal64
    kCFRunLoopExit = (1UL << 7), * * * * * * * * * * * * * * a decimal128----(exit loop) kCFRunLoopAllActivities =0x0FFFFFFFU
};
Copy the code

We can see from the enumeration of RunLoop states that 160 = 128 + 32, i.e

  • activities = 0xa0= (kCFRunLoopExitkCFRunLoopBeforeWaiting)
  • activities = 0x1= (kCFRunLoopEntry)

So when these three states are monitored, they are called_wrapRunLoopWithAutoreleasePoolHandlerFunction. This function actually works as shown below

  • Listen to thekCFRunLoopEntryEvent, callobjc_autoreleasePoolPush();
  • Listen to thekCFRunLoopBeforeWaitingEvent, callobjc_autoreleasePoolPop()And then callobjc_autoreleasePoolPush();
  • Listen to thekCFRunLoopExitEvent, callobjc_autoreleasePoolPop()

Based on the above analysis, we can sum up that objc_autoreleasePoolPush() is called once except when the program starts (corresponding to kCFRunLoopEntry) and exits (corresponding to kCFRunLoopExit); In addition to objc_autoreleasePoolPop(), the program is running whenever RunLoop is about to sleep and the observer is listening to the kCFRunLoopBeforeWaiting state. Objc_autoreleasePoolPop () is called once, which clears the releasepool by calling each object in the current AutoReleasepool. Then call objc_autoreleasePoolPush() again; , which opens a new release pool and waits for the next cycle after the RunLoop wakes up.

When does the release method get called for an auto-release pool object?

During each loop of the RunLoop, objects that have called autoRelease (that is, objects added to AutoreleasePoolPage) are called release (or freed) just as the loop is about to go to sleep.

So that’s how AutoreleasePool works and how it relates to RunLoop.

Memory management portal 🦋🦋🦋

Memory management analysis (a) – MANUAL memory management in the MRC era

Memory management analysis (two) – timer problem

Memory management analysis (three) – iOS program memory layout

Memory management analysis (5) — weak pointer implementation principle