IOS martial arts esoteric article summary

Writing in the front

A good App is a memory saver, so this article explores some of the tricks of memory management and RunLoop.

A possible secret Demo for this section

First, memory layout

1. 5

Next, I introduce the five regions from the lowest address in memory to the highest address:

  • Code snippet (. Text)
    • It holds program code, loaded directly into memory
  • Initialize the region (.data)
    • Store initialized global variables, static variables
    • Memory address: Generally0x1At the beginning
  • Uninitialized region (.bss)
    • bssSection holds uninitialized global variables, static variables
    • Memory address: Generally0x1At the beginning
  • The heap area (heap)
    • The heap area is stored throughallocAllocated objects,block copyAfter the object
    • The heap areaSlower speed
    • Memory address: Generally0x6At the beginning
  • The stack area (stack)
    • The stack area storesfunction,methodsAs well asA local variable
    • The stack areaThe relatively small, butRelatively fast
    • Memory address: Generally0x7At the beginning

Function Pointers exist on the stack, and function implementations exist on the heap

In addition to the five areas, there are reserved fields and kernel areas in memory

  • Kernel: In the case of a 4GB phone, 3GB is allocatedFive regions + reserved areasThe remaining 1GB is for the kernel area, which is the area of the system used for kernel processing operations
  • Reserved field: Reserved a certain area for reserved fields for storage or system processingnilEtc.

There is a question as to why the last memory address of the five extents starts at 0x00400000. The main reason for this is that 0x00000000 means nil, and you can’t directly represent a segment as nil, so you’re given a separate chunk of memory for things like nil.

The following two graphs provide a better understanding of memory distribution.

Normally when you use your App, the stack will grow down and the heap will grow up.

Let’s take a look at some of the heap and stack areas

  • forallocObject createdobj, printed separatelyobjObject address andobjThe object’sPointer to the address(Refer to the summary diagram above.)
    • objtheAddress of the objectBased on0x6At the beginning, the description is storedThe heap area
    • objThe object’sPointer to the addressBased on0x7At the beginning, the description is storedThe stack area

What is the order in which objects are accessed in the heap and stack?

  • The heap accesses objects in the order of first getting the pointer to the stack and then getting the object to which the pointer pointsisa, attribute methods, etc

  • The order in which the stack area accesses objects isAccess the object's memory space directly through registers, soFast access

2. Memory layout

Interview question 1: Is there a difference in memory between global and local variables? If so, what is the difference?

  • Is there a difference
  • Global variables are stored in the global storage area of memory (that is, the BSS +data segment) and occupy static storage units
  • Local variables are stored on the stack and are dynamically allocated only when their function is called
  • The access permissions are different

Question 2: Can you modify global variables, global static variables, local static variables, local variables in a Block?

  • You can modify global variables, global static variables, because global variables and static global variables are global, they have a lot of scope,blockAccess to
  • Local static variables can be modified, but not local variables
    • Local static variables (staticModified) and local variables, byblockCaptured from the outside, become__main_block_impl_0Member variables of this structure
    • The local variable isIn order to valueWay to pass toblockIn the constructor of theblockBecause only the value of the variable is captured, not the memory address, so inblockThe value of a local variable cannot be changed internally
    • Local static variables arePointer to theForm,blockThe value of a local static variable can be modified because it is captured as a pointer
  • ARCEnvironment, once used__blockModified and inblockIs changed in thecopyOperation,blockWill be removed from the stack areacopyTo the heap area, at this pointblockisHeap area block
  • ARCMode,BlockReferenced in theId typeWhether or not__blockEmbellishment, bothretainFor the underlying data types, no__blockModifier cannot modify the value of a variable; If you have__blockModify, also modify at the bottom level__Block_byref_a_0The structure, the internalforwardingPointer tocopyAfter the address, to achieve value modification

Interview question 3: Myth about global static variables

  • Global static variables are mutable
  • Value of a global static variableFor files only, the memory address of the global static variable in different files is not the same, that is, no matter how other files modify, this file is used with the original value/this file modified value

Second, memory management scheme

1. TaggedPointer

(1). 1 taggedPointer

Which of the following methods will crash when called separately? Why is that?

#import "ViewController.h"

@interface ViewController(a)
@property (nonatomic.strong) dispatch_queue_t queue;
@property (nonatomic.strong) NSString *nameStr;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.queue = dispatch_queue_create("com.tcj.cn", DISPATCH_QUEUE_CONCURRENT);

    [self taggedPointerDemo];
    [self testNormal];
}

- (void)taggedPointerDemo {
  
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"tcj"];  // Alloc heap iOS optimization -taggedPointer
             NSLog(@ "% @".self.nameStr); }); }} - (void)testNormal {
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@" Another dark horse born 12345"];
            NSLog(@ "% @".self.nameStr); }); }}@end
Copy the code

After running the test, testNormal crashes and the taggedPointerDemo method works fine

First of all, why did it crash? It’s actually multithreading and setter and getter operations

  • callsetterMethods willobjc_retain(newValue)+objc_release(oldValue)
  • But adding multithreading makes a difference — at some pointThread 1Apply to the old valuerelese(No Relese over), at the same time,Thread 2Also on the old valuesreleseOperations, freeing the same memory space multiple times at the same time, can causeWild pointer problem(Accessing bad addresses)

But why does testNormal crash while the taggedPointerDemo method works?

  • testNormalThe object in is__NSCFStringType stored inThe heap

  • taggedPointerDemoThe object in isNSTaggedPointerStringType stored inThe constant areaBecause the.nameStrinalloc Allocate when in heap area, due to small, so afterXcodeIn theThe optimization of iOSAnd becameNSTaggedPointerStringType stored inThe constant area

In fact, beforeobjcSource code methods have seen a similar figure —objc_retainandobjc_releaseIf the object isisTaggedPointerType is returned directly (no operation)

Address analysis of small objects

So NSString, for example, for NSString

  • The generalNSStringObject Pointers, bothString value + Pointer to the address.The two are separate
  • forTagged PointerA pointer, thePointer + value.Can be represented in small objects. SoTagged Pointer Contains both Pointers and values.

In the previous article about class loading, _read_images source there is a way of treatment for small objects, namely initializeTaggedPointerObfuscator method, we introduce below

1. 2 taggedPointer deeply

The launchiPhone 5s(iPhoneThe first useA 64 - bitArchitecture) is also presented in order to save memory and improve execution efficiencytaggedPointer

The bottom layer did the right thingobjc_debug_taggedpointer_obfuscatorThe operation of xor (two xor numbers are equivalent to encoding and decoding —iOS10.14Obfuscation operations done later)

We can do it atobjc4The source code (818.2Version)objc_debug_taggedpointer_obfuscatorTo find thetaggedPointerTo see how the underlying obfuscation is handled

Through the implementation, we can know that in the encoding and decoding part, two layers of XOR are passed, and its purpose is to get the small object itself. For example, take 1010 0001 as an example, and assume that the mask is 0101 1000

    1010 0001 
   ^0101 1000Mask (code)1111 1001
   ^0101 1000Mask (decode)1010 0001
Copy the code

So in the outside world, to get the real address of the small object, we can also use a similar methodtaggedPointerDecode. We can decode the source code to the outside, willNSStringThe obfuscated part is decoded as shown below

Observe the decoded small object address, where62saidbASCIICode, and then toNSNumberFor example, it can also be seen that1That’s our actual value

Here, we verify that the small object pointer addresses do store values. What is the meaning of 0xA and 0xb in the small object address high?

//NSString
0xa000000000000621

//NSNumber
0xb000000000000012
0xb000000000000025
Copy the code

Need to go to the source code to view_objc_isTaggedPointerSource code, mainly throughRetain the value of the highest bit(that is, a 64-bit value) to determine whether it equals_OBJC_TAG_MASK(2 ^ 63) to determine if it is a small object

Therefore, 0xA and 0xB are mainly used to determine whether they are taggedpointer small objects, that is, to determine whether the 64th bit is 1 (the address of the pointer is both the address of the pointer and the value).

  • 0xaConverted toBinary is 1 010(64 to 1.The three after the 63 ~ 61saidTagType type2Said),NSStringtype
  • 0xbConvert to binary1, 011(64 to 1.The three after the 63 ~ 61saidTagType type3Said),NSNumberType, one thing to notice here is ifNSNumberThe value is- 1, the value in its address is usedcomplementThe said

This is where you can go through_objc_makeTaggedPointerMethod parameterstagtypeobjc_tag_index_tEnter its enumeration, where2saidNSString.3saidNSNumber

Similarly, we can define aNSDateObject to verify ittagTypeWhether it is6. By printing the result, its address high is0xe, is converted to binary1, 110And eliminate the1, 64And the rest of thethreeExactly convert to decimal6Conforms to the enumerated values above

So let’s look at memory management for NSString

We can test the memory management of AN NSString in two ways, with NSString initialization

  • throughWithString + @ ""Mode initialization
  • throughWithFormatMode initialization

It can be concluded from the above that there are three main types of memory management for NSString

  • __NSCFConstantString:String constant, it is a kind ofCompile timeConstants,RetainCount value is very big, to operate it,No reference count changes are caused.Stored in the string constants area
  • __NSCFString: it is inThe runtimeTo create theNsstrings subclassAfter, createThe reference count is increased by oneAnd stored in theThe heap
  • NSTaggedPointerString: label pointer, is apple inA 64 - bitUnder the environment ofNSString,NSNumberAnd so onTo optimize theFor.NSStringobjects
    • whenThe string is a combination of digits and letters and contains 9 characters or less, automatically becomesNSTaggedPointerStringType stored inThe constant area
    • When you haveChinese or other special symbols, will directly become__NSCFStringType stored inThe heap area

1. 3 taggedPointer summary

  • Tagged PointerSmall object type (used for storageNSNumber,NSDate,Small nsstrings), small object Pointers are no longer simple addresses, butAddress + value, i.e.,The real valueSo, actuallyIt's no longer an objectIt’s just aAn ordinary variable in object skinSo you can read it directly.Advantage is to occupy small space, save memory
  • Tagged PointerSmall objects,Retain and release will not be enteredInstead, it goes straight back, meaningNo ARC management is required, so you canDirectly released and recovered by the system itself
  • Tagged PointerThe memory andNot stored in the heapIn, but inThe constant areaIn, you don’t need tomallocandfree, so you canDirectly read, instead of reading data stored in the heap,The efficiency ofOn the fastThree timesThe left and right sides.Efficiency of creationIt’s faster than the heap100 timesOr so
  • taggedPointerMemory management solutions, thanRegular memory management, much faster
  • Tagged PointertheA 64 - bitAddress,The first four digits represent the type.The last four bits are mainly used for system processing.The middle 56 bits are used to store values
  • Memory optimization suggestion: YesNSStringSay, when a stringsmaller, you are advised to directly pass@ ""Initialize as stored inThe constant area, you canRead directlyIs it.WithFormatInitialization modeMore quickly

(2). Nonpointer_isa

nonpointer_isaAs mentioned in the previous section, this is a way for Apple to optimize memory:isaIs a8 bytes (64 bits)Pointer to, used onlyIsa toIt’s a little wasteful, soisaSome other data is mixed in to save memory

(3) SideTable.

When the reference count is stored to a value, it is not stored in extra_RC of Nonpointer_isa’s bitfield, but in the SideTables hash table

③.1 Why are there multiple hash tables in memory? What is the maximum number?

  • If the hash table has only one table, it means that all global objects are stored in the same table, and any operation on any object will be unlocked (lock is to lock the read and write of the entire table). When unlocking, because all the data is in one table, it means that the data is not secure

  • Having a table for every object would cost performance, so you can’t have an infinite number of tables

  • The hash table is of type SideTable and is defined as follows

  • By looking at thesidetable_unlockMethods to locateSideTablesAnd its interior is throughSideTablesMapgetMethod to obtainSideTablesMapIs through theStripedMap<SideTable>The definition of the

To enterStripedMapAs you can see from this, there are the most hash tables in the real machine at the same timeYou can only have eight

③.2 Why hash tables are used instead of arrays and linked lists?

  • An array of: The characteristics lie inEasy to query (that is, access by subscript),It is troublesome to add and delete(Similar to what we talked about beforemethodList.Add and delete memcopy and memmove, very troublesome), so the properties of arrays areFast reading and inconvenient storage
  • The list: The characteristics lie inEasy to add and delete, slow query(needThe query is traversed from the beginning node), so the properties of linked lists areFast storage, slow reading
  • The nature of hash tablesisA hash tableThe hash tableBrings together the best of arrays and linked lists.Add, delete, change and check are more convenientFor example, zip hash tables (discussed in the previous lock article)tlsThe storage structure ofZip formIs the most commonly used, as shown below

Third, ARC&MRC

ARC and MRC are often asked in interviews, but they are central to memory management

① MRC(Manual memory management)

  • inMRCIn the era, the system determines whether to destroy an object by reference count, with the following rules
    • The object isWhen creatingThe reference counts are1
    • When objectReferenced by another pointer, you need to manually invoke the[objc retain]To make the object reference count+ 1
    • When pointer variables are no longer using objects, they need to be called manually[objc release]toRelease objectTo make the object reference count- 1
    • When an objectThe reference count is 0, the system willThe destructionThis object
  • So, inMRCIn mode, the following must be observed:Who created.Who released.Who reference.Who manage

② ARC(Automatic Memory Management)

  • ARCPattern is inWWDC2011andiOS5The introduction ofAutomatic management mechanism, automatic reference counting. Is a feature of the compiler. The rules are the same as the MRC, except that
    • ARCIn theProhibit manuallycallretain/release/retainCount/dealloc
    • The compilerIt will be inserted in the right placereleaseandautorelease
    • ARCThe new addedweak,strongThe keyword
  • ARCisLLVMandRuntimeResult of coordination

(3) alloc

OC object principle – (alloc & init & new)

(4) retain

Retain calls objc_retain underneath

  • objc_retainFirst check whether it isisTaggedPointer, is directly returned without processing, not in the callobj->retain()

  • objc_object::retainthroughfastpathHigh probability callrootRetain(), with a small probability provided externally through a message sending callSEL_retain

  • rootRetaincallrootRetain(false, false)

  • RootRetain’s internal implementation is actually a do-while loop:

    • First check whether it isnonpointer_isa(Low probability event) If not, direct operationSideTablesThe reference count table in the hash table where the hash table is mergedNot just one, but many
      • Find the corresponding hash table to proceed+=SIDE_TABLE_RC_ONE, includingSIDE_TABLE_RC_ONEMove two places left to find the reference counter table
    • Check whether the vm is being released. If the vm is being released, execute the commanddeallocprocess
    • calladdcFunction performsextra_rc+1, i.e.,Reference count +1Operation and gives a status identifier for a reference countcarryUsed to indicateWhether extra_RC is full
      • rightisaIn theBit 45 (RC_ONE is 45 in ARM64) extra_RCOperation processing
    • ifcarryState representation ofextra_rcThe reference count of is full, and the hash table needs to be manipulated, i.eTake the full half and store it in extra_RC.The other half has rc_half of the hash tableThe reason for doing this is that if all the data is stored in the hash table, every operation on the hash table needs to be unlocked, which is time-consuming and consumes high performanceEqually dividedThe purpose of the operation is to improve performance
      • Why is use preferred hereisaReference counting is stored becauseReference counts are stored in the bits of isa

Retain summary:

  • retainAt the bottom, it first determines whether or notNonpointer isaIf theIf no, the hash table is directly manipulated to perform the +1 operation
  • ifIs Nonpointer isa, you also need toDetermine if the release is in progressIf theThe dealloc process is executed, release the weak reference table and reference count table, and finallyfreeFree object memory
  • ifIt's not releasing, theNonpointer ISA does a regular reference count of +1.One thing to note here is thatExtra_rc has only 8 bits to store reference count values on the real machinewhenStorage is full, you need toUse hash tables for storageThe need toSplit the full extra_RC in half.Half (2^7) is stored in the hash table.The other half is still stored in extra_RCUsed of conventionalThe +1 or -1 operation of the reference count, and then back again

(5) release

Release is similar to retain in that objc_release is called underneath

  • objc_releaseFirst check whether it isisTaggedPointer, is directly returned without processing, not in the callobj->release()
  • objc_object::releasethroughfastpathHigh probability callrootRelease(), with a small probability provided externally through a message sending callSEL_release
  • rootReleasecallrootRelease(true, false)
  • rootReleaseThere’s also an internal implementationdo-whilecycle
    • First check whether it isnonpointer_isa(Low probability event)Is not theWords,Apply -1 directly to the reference count in the hash table
    • If it isNonpointer isa,Apply -1 to the reference count value in EXTRA_RCAnd,Stores the EXTRA_RC state at this time into carry
    • If the state at this pointCarray 0, then gounderflowprocess
      • judgeWhether half the reference count is stored in the hash table
      • ifis,Retrieves half of the stored reference count from the hash table,1 the operationBut that isStore it in extra_RC
      • If at this timeExtra_rc no value.The hash table is also empty.Then the destruction is carried out directly, i.e.,deallocOperation belonging toAutomatic trigger

Therefore, to sum up, the underlying process of Release is shown in the figure below

6 retainCount

With all this reference counting, what does retainCount have to do with reference counting? Here’s a question:

What is the reference count of the object created by alloc?

The code above prints 1, but I don’t see anything related to retainCount in the alloc flow. What’s going on here? Let’s take a look at the underlying implementation of retainCount

  • Enter theretainCount -> _objc_rootRetainCount -> rootRetainCountSource code, its implementation as follows

Here we can use the source breakpoint debugging to see the currentextra_rc, the result is as follows:

When you hit the break point at line 953Extra_rc 0And then at line 954, let’s seeextra_rcWhat is the value of.The value at this point is1.

isa_t bits = __c11_atomic_load((_Atomic uintptr_t *)&isa.bits, __ATOMIC_RELAXED)
Copy the code

The above code adds +1 to the EXTRA_rc in bits.

The object created by alloc actually has a reference count of 0. Its reference count prints as 1 because the underlying rootRetainCount method has a reference count of +1 by default, but only reads the reference count, not writes. To prevent objects created by alloc from being freed (a reference count of 0 would be freed), the underlying program defaults to +1 at compile time.

  • allocObject createdThere is no retain and release
  • allocObject creatingThe reference count is 0, can be inCompile time, the programThe default add 1, so the reference count is read as 1

All landowners autorealese

Covered in the section on automatic release pools.

Today dealloc

The dealloc destructor is mentioned in both retain and release’s underlying implementations. Let’s look at the underlying implementation of dealloc

  • deallocIt’s called at the bottom_objc_rootDealloc

  • _objc_rootDealloccallrootDealloc

  • In the rootDealloc method

    • Determine whetherisTaggedPointerIf it is, go straight back. If it is not, continue down
    • judgeIsa identifies aIs in theNo Weak reference exists,associations,C++ destructor,Extra hash table, if there is callobject_dispose.Otherwise direct free

  • object_disposeIn the
    • First empty processing
    • Then callobjc_destructInstance(Core part)
    • Finally,Free Free object

  • objc_destructInstance
    • judgeWhether there are c++ destructors and associated objectsIf yes, call it separatelyobject_cxxDestruct,_object_remove_assocationsFor processing
    • And then callclearDeallocating

  • clearDeallocatingIn the
    • Determine whether or notnonpointer, is calledsidetable_clearDeallocatingClear hash table
    • judgeWhether or notThere areWeak references and the extra reference count table has_SIDETABLE_rc.If so, call clearDeallocating_slow to process the weak reference table and reference count table

To sum up,dealloc’s process can be summarized as follows:

  • 1: whether to call directly based on the current object statefree()The release of
  • 2: ExistsC++Removes the associated property of this object
  • 3: sets the weak reference pointer to the object tonil
  • 4: Erases the reference count for this object from the weak reference table

And finally, here’s onedeallocThe flow chart

So so far, the initial alloc -> retain -> release -> dealloc is all concatenated.

Weak references

(1). The principle of the weak

The author of the previousIOS Martial Arts Secrets ⑩: OC underlying topic analysisWe’ve already talked about it in.

② circular references in. NSTimer

Circular references are notoriously common with NSTimer, so let’s analyze and fix them

Let’s say I haveBoth A and BInterface,B interfaceThere is the following timer code in.

The problem with this code is that A pop from B to A does not trigger the dealloc function of B. The main reason is that the interface B is not released, that is, the dealloc method is not executed, so the Timer cannot stop and release

We’ve seen that beforereleaseIn reference toCount to zeroCalled whendeallocMessage sent, no trigger at this timedeallocThe function must beCircular references occurWhere does the circular reference appear? Is actuallyNSTimertheApis are strongly held, until Timer invalidated.

Is this timetimerholdself.selfAlso heldtimer, forming theA circular reference

So is it possible to look likeblockHow about using weak references to solve circular references? The answer is no!

The holding relationship between them is as follows:

Weak typeof(self) weakSelf = self can solve circular reference; How does using weak reference table management without reference counting work here?

I have two more questions here, right?

  • weakSelfReference counting is performed+ 1 operation?
  • weakSelfselfIs the pointer to the same address, to the same piece of memory?

With questions, we areweakSelfBefore and after printingselfReference count ofRun after discovery before and afterselfThe reference count of the8Is the.WeakSelf does not perform +1 operation on memory

To continue printingweakSelfselfObjects and their pointer addresses:

It can be seen from the print result that both weakSelf and self point to TCJTimerViewController objects, but the Pointers of weakSelf and self are not the same — they are not the same thing. It just points to the same TCJTimerViewController object.

throughblockMethods of underlying principles_Block_object_assignTo be seen,blockThe object is capturedPointer to the address blockholdingweakSelfPointer address of;timerholdingThe object to which a weakSelf pointer pointsI’m holding it indirectlyself, so there are still circular references that cannot be released.

③ Solve NSTimer circular reference

We need to break this layer of strong-hold -self

③.1 Idea 1: Destroy timer in other methods during pop

  • sincedeallocCan’t make it. It’s justdeallocResolve this layer of strong references before calling the function
  • Can be found inviewWillDisappear,viewDidDisappearIn the processingNSTimer, but this processing effect is not good, because jump to the next page timer will also stop working, inconsistent with the service
  • usedidMoveToParentViewControllerThis is a good way to deal with strong references. This method is used forWhen a viewController is added or removed from a viewController, which must be called. The purpose is to telliOSThe child controller has been added or deleted.
  • inB interfaceThe rewritedidMoveToParentViewControllermethods

③.2 Idea 2: The mediator mode, which does not use self, relies on other objects

  • Use other global variables at this timetimerHolds global variables,selfAlso holds global variables as long as the pagepop.selfBecause it’s not being held and it can walk normallydeallocIn thedeallocTo deal withtimer
  • The chain of possession is thetarunloop->timer->target->timer,self->target,self->timer
#ifdef DEBUG
#define CJNSLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#defineCJNSLog(format, ...) ;
#endif

#import "TCJTimerViewController.h"
#import <objc/runtime.h>

static int num = 0;

@interface TCJTimerViewController(a)
@property (nonatomic.strong) NSTimer *timer;
@property (nonatomic.strong) id  target;
@end

@implementation TCJTimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
     self.target = [[NSObject alloc] init];
     class_addMethod([NSObject class].@selector(fireHome), (IMP)fireHomeObjc, "v@:");
     self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];
}

void fireHomeObjc(id obj){
    CJNSLog(@"%s -- %@",__func__,obj);
}

- (void)fireHome{
    num++;
    CJNSLog(@"hello word - %d",num);
}

- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
    CJNSLog(@"%s",__func__);
}
Copy the code

③.3 Idea 3: Customize the wrapper timer

  • Similar to plan 2, but more convenient to use
  • If incoming respondertargetCan respond to incoming response eventsselector, useruntimeDynamically add methods and start timers
  • fireWapperIf you have inwrapper.target, letwrapper.targetCall (external responder)wrapper.aSelector(External response event)
  • fireWapperIn nowrapper.target, which means the responder is released (unable to respond), and the timer can rest (stop and release).
  • The holding chains are respectivelyrunloop->timer->TCJTimerWrapper,vc->TCJTimerWrapper-->vc
//***********. H file ***********
@interface TCJTimerWapper : NSObject

- (instancetype)cj_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)cj_invalidate;

@end

***********. M file ***********
#import "TCJTimerWapper.h"
#import <objc/message.h>

@interface TCJTimerWapper(a)

@property(nonatomic.weak) id target;
@property(nonatomic.assign) SEL aSelector;
@property(nonatomic.strong) NSTimer *timer;

@end

@implementation TCJTimerWapper

- (instancetype)cj_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self= = [super init]) {
        / / to vc
        self.target = aTarget;
        // The timer method passed in
        self.aSelector = aSelector;
        
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method = class_getInstanceMethod([self.target class], aSelector);
            const char *type = method_getTypeEncoding(method);
            // Add methods to timerWapper
            class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
            
            // Start a timer. Target is self, that is, listen on yourself
            self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:selfselector:aSelector userInfo:userInfo repeats:yesOrNo]; }}return self;
}

// Keep running runloop
void fireHomeWapper(TCJTimerWapper *wapper){
    // Check whether target exists
    if (wapper.target) {
        // If it exists, it needs to be known to the VC, that is, to send the selector message to the target passed in, and the timer parameter is also passed in, so the VC can know the 'fireHome' method, this way the timer method can execute the reason
        //objc_msgSend Sends messages and executes the timer method
        void (*cj_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
         cj_msgSend((__bridge void *)(wapper.target), wapper.aSelector,wapper.timer);
    }else{
        // If target does not exist and has already been released, the current timerWrapper is released
        [wapper.timer invalidate];
        wapper.timer = nil; }}// The timer is released by the vc dealloc method
- (void)cj_invalidate{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc
{
    NSLog(@"%s",__func__);
}

@end

Copy the code
#ifdef DEBUG
#define CJNSLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#defineCJNSLog(format, ...) ;
#endif

#import "TCJTimerViewController.h"
#import "TCJTimerWapper.h"

static int num = 0;

@interface TCJTimerViewController(a)
@property (nonatomic.strong) NSTimer *timer;
@property (nonatomic.strong) TCJTimerWapper *timerWapper;
@end

@implementation TCJTimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
     self.timerWapper = [[TCJTimerWapper alloc] cj_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];
}

- (void)fireHome{
    num++;
    CJNSLog(@"hello word - %d",num);
}

- (void)dealloc{
    [self.timerWapper cj_invalidate];
    CJNSLog(@"%s",__func__);
}
Copy the code

This method seems to be cumbersome with many steps. Moreover, for timerWapper, method needs to be continuously added and a series of processing needs to be carried out.

NSProxy, a subclass of NSProxy virtual base class, has the same status as NSObject and is mainly used for message forwarding

  • useNSProxybreakNSTimerthevcBut the strong hold still exists. You need to manually disable the timer
  • The holding chains are respectivelyrunloop->timer->TCJProxy->timer,vc->TCJProxy-->vc
/ / * * * * * * * * * * * * TCJProxy. H file * * * * * * * * * * * *
@interface TCJProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end

/ / * * * * * * * * * * * * TCJProxy m file * * * * * * * * * * * *
@interface TCJProxy(a)
@property (nonatomic.weak) id object;
@end

@implementation TCJProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    TCJProxy *proxy = [TCJProxy alloc];
    proxy.object = object;
    returnproxy; } - (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}

/ / * * * * * * * * * * * * TCJTimerViewController m file * * * * * * * * * * * *
#ifdef DEBUG
#define CJNSLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#defineCJNSLog(format, ...) ;
#endif

#import "TCJTimerViewController.h"
#import "TCJProxy.h"

static int num = 0;

@interface TCJTimerViewController(a)
@property (nonatomic.strong) NSTimer *timer;
@property (nonatomic.strong) TCJProxy *proxy;
@end

@implementation TCJTimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.proxy = [TCJProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
}

- (void)fireHome{
    num++;
    CJNSLog(@"hello word - %d",num);
}

- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
    CJNSLog(@"%s",__func__);
}

Copy the code

The first idea is relatively simple, the second idea is reasonable use of intermediaries but very drag, the third idea is suitable for loading force, the fourth idea is more suitable for large projects (timer with more) detailed code

AutoReleasePool Automatic release pool

Automatic release tankisOCOne of theAutomatic memory reclamation mechanismIn theMRCYou canUse AutoReleasePool to delay the release of memoryIn theARCYou canUse AutoReleasePool to add objects to the nearest automatic release pool.No immediate release, will wait untilRunloop dormancyorOut of autoreleasepool scope {}beforeBe releasedThe mechanism can be illustrated in the following figure

  1. From the applicationStart until loading is complete.The main threadThe correspondingrunloopWill beA dormant state.Waiting for user interactiontoWake up the runloop
  2. The user’sEvery interactionwillStart runloop onceforTo deal withOwnership of the userClick on the,Touch eventsEtc.
  3. runloopinListening to thetoInteraction eventsLater, you will getCreate an automatic release poolAnd will allDelay the release oftheObject added to the automatic release pool
  4. In aBefore the complete runloop endsAnd try toAutomatic release tankIn theAll objects send a Release messageAnd thenDestroy the automatic release pool

① Clang analyzes the autoreleasepool structure

To see the underlying structure of @Autoreleasepool, print a main. CPP file to the blank main.m file using the clang command

  • clangCommand as follows:xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
  • intoC++The following code

  • We know from the figure above@autoreleasepoolBe converted into__AtAutoreleasePool __autoreleasepool, this is aThe structure of the body.__AtAutoreleasePoolThe structure is defined as follows:

From the above picture, we can know the following:

  • __AtAutoreleasePoolIs aThe structure of the body, there areConstructor + destructorThe structureDefined objectinAfter the scope endsWill,Automatically invoke the destructor
  • Among them{}isscope, the advantage isThe structure is clear.readable, you canCreate destroy in time

The timing of the constructor and destructor calls involved can be verified by the following example

From the results of the run, we can see that the constructor is automatically called when TCJTest creates an object, and the destructor is automatically called after the {} scope is left.

② Assemble and analyze the autoreleasepool structure

inmainAdd a breakpoint to the code section, run the program, and enable assembly debugging:

Through debugging results, it is found that the results are the same as those of our CLANG analysis.

③ ObjC source code analysis autoreleasepool

inObjc sourceThere’s a passage in thatAutoreleasePoolAnnotations.

Several points can be drawn from this:

  • 1.Automatic release tankIt’s a story aboutThe stack structure of Pointers
  • 2. Among themPointer to theisPoint to the freed objectorPool_boundary sentry(Now often referred to asThe border)
  • 3.Automatic release tankIs aThe structure of the page(virtual memory mentioned), and thisA page is a bidirectional linked list(said to haveThe parent nodeandChild nodes, mentioned in the class, i.e. the inheritance chain of the class.
  • 4.Automatic release tankandThreads are relevant

From the above description of the automatic release pool, we know several directions of our research:

  • 1.When is the automatic release pool created?
  • 2.How are objects added to the auto-release pool?
  • 3.Which objects will be added to the automatic release pool?

With these questions in mind, we set out to explore the underlying principles of automatic release pools

(3). 1 AutoreleasePoolPage analysis

From the initialclangorAssembly analysisWe get itAutomatically release the pool from its underlying callisobjc_autoreleasePoolPushandobjc_autoreleasePoolPopThe source code for these two methods is as followsFrom the source we can find, are calledAutoreleasePoolPagethepushandpopImplementation, which is defined below, from which you can see that the auto-release pool is both a page and an object, andAutoreleasePoolPageIs inherited fromAutoreleasePoolPageData.The following conclusions can be made from the above:

  • 1.Automatic release tankIs apageAnd at the same timeAn objecttheThe page size is 4096 bytes
  • 2. From its definition,AutoreleasePoolPageIs inherited fromAutoreleasePoolPageData, and the attributes of this class also come from the parent class, as followsAutoreleasePoolPageDataThe definition of

It can be found:

  • One of theAutoreleasePoolPageObject, so there is the followingAutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPageIt can be explained hereAuto release pool except is a page, or aBidirectional linked list structure
  • AutoreleasePoolPageDataStructure of theThe memory size is 56 bytes
    • attributemagicIs of typeMagic_t structure, the memory size occupied by ism[4]Its memory (i.e4 * 4 = 16 bytes)
    • attributeNext (pointer),Thread (object),Parent object,ChildAre accounted for8 bytes(i.e.4 * 8 = 32 bytes)
    • attributedepth,hiwatA type ofuint32_t, the actual type isunsigned intType, all accounted for4 bytes(i.e.2 * 4 = 8 bytes)

The structure of an empty AutoreleasePoolPage is as follows:

Objc_autoreleasePoolPush source code analysis

Enter the source code implementation of push:There is the following logic:

  • Judge firstWhether or notThere arepool
  • If not, passautoreleaseNewPageMethod to create
  • If yes, passautoreleaseFastPush the sentinel object
AutoreleaseNewPage Creates a new page

In the firstautoreleaseNewPageThe implementation of creating a new page

Using the code implementation above (more on autoreleaseFullPage later), we can conclude the following

  • 1. Obtain the current operation page.
  • 2. If the current operation page exists, the system passesautoreleaseFullPageMethod to push objects
  • 3. If the current operation page does not exist, the operation page passesautoreleaseNoPageMethod to create a page
    • inautoreleaseNoPageThe automatic release pool for the current thread is passedAutoreleasePoolPagecreate
    • AutoreleasePoolPagetheA constructorisThis is done by implementing the initialization method of the parent class AutoreleasePoolPageData.
AutoreleasePoolPage constructor

The above saidAutomatic release pool for the current threadIs through theAutoreleasePoolPage createAnd seeAutoreleasePoolPageConstruction method:

The meanings of parameters passed to AutoreleasePoolPageData are:

  • begin()saidPosition of the stack(The next oneThe pushaddress of the object to release). Debugging can be done through source codebeginAnd find that its concrete implementation is equal toHeader address +56, in which the56isMemory size of AutoreleasePoolPageData structure.
    • Due to theARCIn mode, it cannot be invoked manuallyautorelease, so willDemoSwitch to theMRCMode (Build Settings -> Objectice -c Automatic Reference Counting Set this parameter to NO)

Why do I add 56 to this pointer address? Where did this 56 come from? Is an inherent property in AutoreleasePoolPageAnalysis:AutoreleasePoolPageDataIn thePointers and objectsAre accounted for8 bytes.uintAccount for4 bytes, onlymagic_tUnknown (it is not a pointer, so it depends on the type);Magic_t is a pointer, the storage area of static variables is in the global segment, somagic_tTake up4 * 4 = 16Byte, i.e.,AutoreleasePoolPageDataThe memory size of the structure is56 bytes.

  • objc_thread_self()Is saidThe current thread, and the current thread isObtained through TLS
  • newParentRepresents the parent node
  • The next two parameters areDepth through the parent node,Maximum number of stacksCalculation of thedepthAs well ashiwat
View automatic free pool memory structure

And then we use_objc_autoreleasePoolPrintFunction to print information about the auto release pool (remember to switch toMRCMode debugging, which we have already switched)By running the results as follows, we foundreleaseSix, but weThere are actually only five objects to push, in which thePOOL indicates the sentinel object, i.e.,The borderIts purpose is toTo prevent cross-border, we looked at the printed address again and foundThe first address of a page(PAGE) andThe sentry object(POOL) differ0x38, todecimalIs precisely56Is the.AutoreleasePoolPage Memory size.

Is it possible to add objects to AutoreleasePool indefinitely? The answer is no!

Cycle timesiThe upper limit of505, its memory structure is as followsThe first page is full.504 were storedtoReleased object.The second page only stores one

In theCycle number IIs page 2 also storing 504 objects?By running discovery, the first page is stored504, the second page is stored505, page 3 storage2.

Through the above tests, we can draw the following conclusions:

  • The first page can be stored504 objects, and onlyThe first page has sentinel objectsWhen one page is full a new page is opened
  • At the beginning of page two,A maximum of 505 objects can be stored
  • The size of a page is equal to505 times 8 is 4040

That’s what we saw beforeAutoreleasePoolPageIn theSIZEAs I said, the size of a page is4096 bytes, and in its constructorObject to pushFrom theStart with address +56 bytesSo it can be in one pageThe actual storage can be 4096-56 = 4040 bytesIs converted to the object4040/8 is equal to 505A maximum of one page can be stored505 objectsOn the first pageThe sentry object(The POOL_BOUNDARY sentinel object can only hold 504 objects on the first page and 505 objects on each subsequent page due to the automatic release pool being pushed to the top of the stack during initialization). Its structure is illustrated below

From the above conclusion, I have a question: How many sentinel objects are there in an auto-release pool?

  • inAn automatic release poolIn theThere is only one sentinel objectAnd,The sentinel is on the first page
  • The first page can be stored at most504Object, the second page at the beginning of the maximum storage505a

③.2 Sentinel object — POOL_BOUNDARY

The sentinel object is essentially nil, and its purpose is mainly when objc_autoreleasePoolPop is called:

  • Locate the sentinel object based on the sentinel object address passed inpage
  • In the currentpage, will be inserted later than the sentinel objectAutorelese objectSend them all oncereleaseMessage and moveNext pointerGet to the right place
  • From the newly added object all the wayforwardClearing can span several forwardpageUntil the sentinel object is locatedpage

③.3 Press the autoreleaseFast object

AutoreleaseFast:There are mainly the following steps:

  • 1. Obtain the current operation page and determine whether the page exists and is full
  • 2. If the pageThere are.And underbyaddmethodsPressure stack object
  • 3. If the pageThere are.And full ofbyautoreleaseFullPagemethodsArrange a new page
  • 4. If the pageThere is nobyautoreleaseNoPagemethodsCreate a new page
AutoreleaseFullPage method

The source code is:This method is mainly used to determine whether the current page is full, if the current page is full, passdo-whileLoop to find the page corresponding to the child node, ifThere is noitOpen a new AutoreleasePoolPage and set it to HotPageAnd thenPressure stack objectFrom the above.AutoreleasePoolPageAs can be seen in the initialization method, mainly throughManipulating the Child objectThat will beThe child of the current page points to the new page, it can be concluded thatpageIs through theTwo-way linked list join.

The add method

View source code:This method is basicallyAdd release objectThe underlying is that the implementation is throughNext pointerStore the release object and willNext pointer incrementSaid,The next location to release the object storeIt can be seen from herepageIs through theStack storage

③.4 Underlying analysis of Autorelease

In demo, we use the autorelease method, in MRC mode, to push the object to the automatic release pool, the following analysis of its underlying implementation:

  • Check the source code for the AutoRelease method
  • Go to the autoRelease implementation of the object

From this, you can see that both the pushguard object and the normal object come to the autoreleaseFast method, but with different identifiers.

③.5 objc_autoreleasePoolPop source code analysis & stack

Objc_autoreleasePoolPop source code analysis

inobjc_autoreleasePoolPopThe method has an argument in itclangWhen analyzing, it is found that the parameter passed isSentinel object returned after push, i.e.,ctxt, its purpose isAvoid stack clutter.Prevents other objects from being pushed off the stackInside is the callAutoreleasePoolPagethepopMethod, we look at the pop source:Pop source code implementation, mainly by the following steps:

  • 1. Handle empty pages and obtain pages based on the token
  • 2. Fault tolerant processing
  • Through 3.popPagePage out of the stack
Stack – popPage

PopPage:Go to popPage source code, which is passed inallowDebugforfalsebyreleaseUntilPush current pageAll objects up to the stop position, that is, to objects on the stackNews releaseUntil theAn incoming sentry object is encountered.

ReleaseUntil method

Look at the source we can know:

  • releaseUntilThe realization of, mainly throughTo iterate over, determines whether the object is equal tostop, its purpose isRelease all objects before stop
  • First of all by gettingPage next releases the object(that is, the last object of the page), and tonextfordiminishing.Gets the last object
  • judgeIs it a sentinel objectIf not, it is automatically calledobjc_releaseThe release of
Kill method

We know from kill implementation, mainlyDestroy current page.Assign the current page to the parent page.And sets the child object pointer of the parent node page to nil

6 (3).conclusion

  • 1. The essence of autoReleasepool is a structure object. An autoreleasepool object is a page, which is a stack structure storage

  • 2. At the bottom of the stack is an empty placeholder of 56 bytes, for a total size of 4096 bytes

  • 3. Only the first page has sentinel objects, storing a maximum of 504 objects. From the second page, a maximum of 505 objects can be stored

  • 4. When autoreleasepool adds the object to be released, the underlying call is objc_autoreleasePoolPush.

    • When there is nopool, i.e.,Only empty placeholders(stored in TLS), the page is created,Push the sentinel object
    • In the pagePush ordinary objectsMainly throughNext pointer incrementthe
    • whenPage full of, you need to set the pagechildThe object ofA new page
    • objc_autoreleasePushThe overall underlying flow chart is as follows
  • 5. The internal implementation of autoreleasepool when it calls the destructor release is to call the objc_autoreleasePoolPop method (pop operation)

    • In the pagePush ordinary objectsMainly throughNext pointer decrementthe
    • whenPage is emptyIs required to assign the pageparentObject is the current page
    • The flow chart for removing objc_autoreleasePoolPop from the stack is as follows

④ Ask questions

④.1 When will temporary variables be released?

  • 1. If thenormalDown, generallyIt is released immediately when it is out of scope
  • 2. If temporary variables are addedAutomatic release tank.Will delay releaseIn which theRunloop sleep or autoreleasepool release after scope

4.2 Principle of Automatic releasePool The principle of AutoreleasePool

  • 1.Automatic release tankthenatureIs aAutoreleasePoolPage structure object, it is aThe stack structure stores pages, each oneAutoreleasePoolPageAre allJoin as a two-way linked list
  • 2.Automatic release tanktheThe pressure of stackandOut of the stackMainly throughThe constructor of a structureandThe destructorCalling the underlyingobjc_autoreleasePoolPushandobjc_autoreleasePoolPop, is actually calledAutoreleasePoolPagethepushandpopTwo methods
  • Every time 3.Call pushIn fact, iscreateA newAutoreleasePoolPageAnd theAutoreleasePoolPageThe specific operation ofInsert a POOL_BOUNDARYAnd return insertPOOL_BOUNDARY Specifies the memory address.pushInternal callsautoreleaseFastMethods: There are three main cases
    • whenThe page isAnd,discontent, the callThe add methodwillObject to the next pointer of the page.And next increment
    • whenThe page isAnd,Is full, the callautoreleaseFullPageInitialize oneA new pageAnd then callThe add methodwillObject added to the page stack
    • whenPage does not exist, the callautoreleaseNoPageTo create ahotPageAnd then callThe add methodwillObject added to the page stack
  • 4. When you performPop operationwhenPassing in a valueThis value is zeroThe return value of the push operation, i.e.,POOL_BOUNDARY Specifies the memory address token. SopopThe internal implementation isFind the page where the sentinel object is located based on the token, and then useobjc_releaseThe release oftokenBefore the object, and putThe next pointer is in the correct position

④.3 Can the automatic release pool be nested?

  • 1. Can be nested, its purpose is to be able toControl the application's peak memoryMake it not too high
  • 2. Nesting is possible becauseThe auto-release pool is a stack nodeThrough theBidirectional linked list form joinAnd isOne to one with threads
  • 3. Automatically release the poolMultilayer nestedIt just doesn’t stopPush sentinel objectIn thepop“Will release the inside first, before releasing the outside

④.4 Which objects can be added to AutoreleasePool? Alloc creation ok?

  • 1. InMRCuseNew, alloc, copyKeyword generated object andretainThe object ofManual release is requiredWill not be added to the automatic release pool
  • In 2.MRCThe Settings forautoreleaseThe object ofManual release is not requiredWill,Go directly to the automatic release pool
  • All 3.autoreleaseObject, inOnce it's out of scope, will beAutomatically adds to the recently created auto release poolIn the
  • In 4.ARCThe only needFocus on reference CountingBecause the creation is done inThe main thread,The system automatically creates an AutoreleasePool for the main thread, soThe created objects are automatically placed into the automatic release pool

④.5 When is AutoreleasePool released?

  • 1.AppAfter startup, Apple in the main threadRunLoopTwo of them are registered inObserver, its callback is_wrapRunLoopWithAutoreleasePoolHandler()
  • 2. The firstObserverThe monitored event isEntry(about to enter Loop), which is called within its callback_objc_autoreleasePoolPush()Create an automatic release pool. Its order is -2147483647,Highest priorityTo ensure that the release pool is created before any other callbacks
  • 3. The secondObserverTwo events were monitored:BeforeWaiting(ready to go to sleep)_objc_autoreleasePoolPop()and_objc_autoreleasePoolPush()Release old pools and create new pools;Exit(that is, will exit the Loop)_objc_autoreleasePoolPop()To release the automatic release pool. thisObservertheorderIs 2147483647.Lowest priorityTo ensure that its release pool occurs after all other callbacks

4.6 Relationship between Threads and AutoreleasePool

Each thread has an autofree pool stack structure associated with it. New pools are pushed to the top of the stack when they are created, pools are removed when they are destroyed, free objects are pushed to the top of the stack for the current thread, and the associated autofree pool is automatically released when the thread stops.

④.7 Relationship between RunLoop and AutoreleasePool

  • 1. The main program RunLoop automatically creates an autoreleasePool before each event loop
  • 2. At the end of the event loop, drain is executed to release the objects

Six, NSRunLoop

(1) introduce RunLoop

RunLoop is an implementation of the event receiving and distribution mechanism. It is part of the thread-specific infrastructure. A RunLoop is an event processing loop that continuously dispatches work and processes input events.

A RunLoop is essentially a do-while loop where you rest when you have nothing to do and work when you come to work. This is different from the normal while loop, which causes the CPU to wait in a busy state, meaning that it consumes CPU all the time, whereas RunLoop does not. RunLoop is an idle wait, meaning that RunLoop has hibernation.

The role of the RunLoop

  • hold-in-programRun continuously
  • To deal withApp(touch,The timer,performSelector)
  • savecpuResources, provider performance,Do as you must, and rest as you must

RunLoop source code download address, where to find the latest version to download

② The relation between RunLoop and thread

(2). 1 get RunLoop

In general, in daily development, forRunLoopThere are two main ways to obtain

(2). 2 CFRunLoopGetMain source code

(2). 3 _CFRunLoopGet0 source code

And as you can see from this,RunloopThere are only two. One isThe main threadOne of them isOther threadsOf.Runloop and threadisOne to one correspondence.

③ Create RunLoop

Through the top_CFRunLoopGet0You can knowRunloopIs through the__CFRunLoopCreateCreate (System creation, developers cannot create their ownLet’s check__CFRunLoopCreateSource:We found that__CFRunLoopCreateIs mainly torunloopProperty assignment operation. Let’s move onCFRunLoopRefThe source of

The following conclusions can be drawn:

  • 1. By definition, actuallyRunLoopIt’s also an object. Yes__CFRunLoopStructure of thePointer to thetype
  • 2.A RunLoop depends on multiple modes, means aRunLoop needs to handle multiple transactions, i.e.,A Mode corresponds to multiple itemsAnd theAn itemContainstimer,source,observer, can be illustrated by the following figure

(3). 1 Mode type

There are five modes mentioned in apple documentation, while only NSDefaultRunLoopMode and NSRunLoopCommonModes are publicly exposed in iOS. NSRunLoopCommonModes are actually a set of modes. The default include NSDefaultRunLoopMode and NSEventTrackingRunLoopMode.

  • NSDefaultRunLoopMode:The defaultMode, normally run under this model (including the main thread)
  • NSEventTrackingRunLoopMode(cocoa) :Tracking mode, use thismodeGo to theTracking events from user interactionsUITrackingRunLoopMode(iOS)
  • NSModalPanelRunLoopMode: handlingmodal panelsThe event
  • NSConnectionReplyMode: handlingNSConnectionObject related events,System internal use, users will hardly use it
  • NSRunLoopCommonModes: This is onePseudo mode, which is a groupA collection of runloop modes, adding input sources to the mode means thatCommon ModesAll modes contained in the. inCocoaIn the application, by defaultCommon Modescontainsdefault modes,modal modes,event Tracking modes. You can useCFRunLoopAddCommonModeMethod will beCommon ModesTo add a custommodes.

③.2 Source & Timer & Observer

  • SourceIt means you can wake upRunLoopSome events, such as when the user clicks on the screen, will create oneRunLoop, mainly divided intoSource0andSource1
    • Source0saidNon-system event, that is, user-defined events
    • Source1saidSystem events, the mainResponsible for the underlying communications.Have the ability to wake up
  • TimerIs commonly usedNSTimerTimers
  • ObserverIt is mainly used forListen for state changes in the RunLoopAnd makeA certain response, there are mainly the following states

④ Test and verification

④.1 Verification: RunLoop and mode are one-to-many

We talked about that aboveRunLoopandmodeisMore than a pair ofBelow we run the code to prove the actual operation. Let’s go throughLLDB commandTo obtainmainRunloop,currentRunloopthecurrentMode Running results show thatrunloopAt run timemodeOnly one.

So let’s getmainRunLoopAll the modelsThis can be verified from the print aboverunloopandCFRunloopModewithMore than a pair ofThe relationship between.

④.2 Verification: Mode and Item are also one-to-many

We continue at the breakpoint and look at the stack information through bt, where we can see that the item type of the timer is as follows (intercept)RunLoopView in source codeItemThe types are as follows:

  • Block application:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • Call timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • Response source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • Source1 response:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD main queue:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • The observer source:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

In general, when initializing Timer, the Timer will be added to Runloop through addTimer:forMode: method. Therefore, the relevant method of addTimer, namely CFRunLoopAddTimer method, will be found in the source code, and its source code is as follows

  • 1. Its implementation mainly determines whether it iskCFRunLoopCommonModesAnd then findrunloopthemodeMatch processing
  • Of 2.KCFRunLoopCommonModes is not a mode, it isAn abstract pseudo-pattern, thandefaultModeMore flexible
  • Through 3.CFSetAddValue(rl->_commonModeItems, rlt); As you can see,runloopwithmodeisMore than a pair ofAt the same time, it can be obtainedmodewithitemIs alsoA one-to-many.

(5) RunLoop perform

As we all know,RunLoopThe execution of therunMethod, which, as you can see from the stack information below, executes below__CFRunLoopRunmethodsEnter the__CFRunLoopRunSource:through__CFRunLoopRunSource code known, for different objects, there are different processing

  • If there is an observer, __CFRunLoopDoObservers is called
  • If there are blocks, __CFRunLoopDoBlocks is called
  • If there is a timer, __CFRunLoopDoTimers is called
  • If it is source0, __CFRunLoopDoSources0 is called
  • If it is source1, __CFRunLoopDoSource1 is called

_ _CFRunLoopDoTimers

Check out the __CFRunLoopDoTimers source codeMainly throughforLoop, singletimerLet’s go ahead and do that__CFRunLoopDoTimerSource:Through the source code: the main logic istimerAfter the execution is complete, the call is actively invoked__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__The delta function is exactly the sametimerConsistency in stack calls.

Summary of Timer Execution

  • 1. User-definedtimerTo set upModeAnd add itRunLoopIn the
  • In 2.RunLooptherunMethod is called when it executes__CFRunLoopDoTimersTo perform alltimer
  • In 3.__CFRunLoopDoTimersMethod, will passThe for loop executes a single timerThe operation of the
  • In 4.__CFRunLoopDoTimerMethod,timerAfter the command is executed, the corresponding command is executedtimerThe callback function

Above, is aimed attimerThe execution analysis forobserver,block,source0,source1, its execution principle andtimerIt’s similar. I won’t repeat it hereApple Official DocumentationforRunLoopHandles diagrams for different sources

⑥ Basic principle of RunLoop

As you can see from the stack information above,runThe implementation path at the bottom level isCFRunLoopRun -> CFRunLoopRun -> __CFRunLoopRunEnter theCFRunLoopRunSource code, which is passed in parameters1.0 e10The scientific count is equal to1* e^10Used to indicatetimeoutCFRunLoopRunSpecific CFRunLoopRunSpecific

  • First of all, according to themodeNameFind the correspondingmode, and then divided into three main cases:
    • If it isentry, the noticeobserverBe about to enterrunloop
    • If it isexitbyobserverBe about to quitrunloop
    • If it’s anything elseIntermediate state, mainly throughRunloop handles a variety of sources

__CFRunLoopRun is called. This step is handled differently depending on the source of the event. When the RunLoop is asleep, the RunLoop can be woken up by the corresponding event.

So, to sum up,RunLoopThe execution process is as follows

⑦ Ask questions

⑦.1 Currently has a child thread, which has a timer. Can the timer execute and print continuously?

No, because the runloop of the child thread does not start by default. Runloop run needs to be started manually.

⑦.2 Relation between RunLoop and thread

1. Each thread has a RunLoop corresponding to it, so the RunLoop is one-to-one with the thread, and its binding is stored through a global Dictionary, where the thread is the key and the RunLoop is valu.2. The RunLoop in a thread is mainly used to manage the thread. When the RunLoop is enabled, the thread will sleep after executing the task. When an event triggers the wake up, the thread will start working again, that is, work when there is work, and rest when there is no work. The main thread RunLoop is enabled by default and will continue running after the program is started. Runloops for other threads are disabled by default and can be manually enabled if necessary

⑦.3 Differences between NSRunLoop and CFRunLoopRef

  • 1.NSRunLoopIs based onCFRunLoopRefobject-orientedAPI, it isunsafethe
  • 2.CFRunLoopRefIs based onCLanguage is,thread-safe

⑦.4 What is the mode function of Runloop?

Mode is primarily used to specify the priority of events in RunLoop

7. 5 to + scheduledTimerWithTimeInterval: way to trigger the timer, when sliding on the page list, the timer will suspend the callback, why? How to solve it?

  • 1.timerThe reason it stops is because of slidingscrollViewWhen, the main threadRunLoopfromNSDefaultRunLoopModeSwitch to theUITrackingRunLoopModeAnd thetimerIs added to theNSDefaultRunLoopMode. sotimerDo not perform
  • 2.timerIn theNSRunLoopCommonModesIn the execution.

Write in the back

Study harmoniously without being impatient. I’m still me, a different color of fireworks.