1. How did you solve the difficult problems at work?
Refactoring should be a common problem that everyone will encounter and even be included in KPI assessment. Firstly, the process should be sorted out, such as the purpose of reconstruction, the development of reconstruction workflow, reconstruction process and verification results.
- The purpose of refactoring: The reason for refactoring is simply because the code is unstructured, logical, and unable to expand in the face of new requirements. So refactoring, layering code, making it scalable, and setting up features that fit into the actual business of the company.
- Refactoring workflow: Grooming
designRefactoring. It is important to comb through this part, because you are going to move the original code, so it is best to comb through the existing code, where it was called, where it was called. It is best to perform functional combing as well. Finally, the results of combing are documented. Documentation is something I prefer, because on the one hand, other colleagues can read the document without interrupting your work constantly. On the one hand, you can review yourself later. Don’t overestimate your memory of code. After a month or two, you won’t be able to remember all the code you wrote. - Reconstruction process: the code is stratified, the components are removed, and the dispatching center mode is introduced as the general control to facilitate the subsequent module reconstruction. And so on. Redundancy measures should be implemented to ensure that services can be switched back dynamically.
- Verification results: The development of self-testing and testing personnel completed.
2. Implementation principle of Block
2.1 Block storage domain: there are three types of stack blocks, heap blocks and global blocks.
- For global blocks: Block syntax where global variables are declared,Block syntax when automatic variables are not captured in expressions. A block that uses no external variables or only global or static variables is _NSConcreteGlobalBlock, and lives from creation to end of the application.
- For stack blocks: normally declare local Block local variables. Blocks that use only external local variables, member attribute variables, and no strong pointer references are stackblocks. The StackBlock lifecycle is controlled by the system, and once returned, it is destroyed by the system.
- For heap blocks: call copy to copy stack blocks to heap blocks. Blocks with strong pointer references or copy-modified member attribute references are copied to the heap as mallocblocks. Without strong pointer references, they are destroyed and the lifecycle is controlled by the programmer
- Blocks are automatically copied from stack to heap:
- In most cases in ARC, Block stack variables are automatically judged by the compiler to generate code that copies blocks from the stack to the heap. Because it is copied to the heap, it can be resident in memory, and therefore still has a lifetime.
- Block as a function return value; Block as a method parameter of the GCD API; Block as a system method name containing usingBlock method parameter; Block is strongly referenced, such as when a Block is assigned to __strong or id;
- When passing a Block to a method or function argument, it is best to manually copy the Block from the stack to the heap without the compiler being able to judge, and without mindlessly using copy.
2.3 How does Block capture external variables
- When an external local variable is used in a Block, it is automatically captured and becomes a member variable of the Block structure so that it can be accessed within the Block.
#import <Foundation/Foundation.h> int global_i = 1; static int static_global_j = 2; int main(int argc, const char * argv[]) { static int static_k = 3; int val = 4; void (^myBlock)(void) = ^{ global_i ++; static_global_j ++; static_k ++; //var ++; NSLog(@" global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val); }; global_i ++; static_global_j ++; static_k ++; val ++; NSLog(@" global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val); myBlock(); return 0; }Copy the code
In the external Block, global_i = 2,static_global_j = 3,static_k = 4,static_k = 5,val = 4Copy the code
-
The value of global_i and static_global_j increases, and they are captured by the Block. This makes sense because they are global and have a wide scope, so the Block captures them and does ++ in the Block. When the Block is done, Their values can still be preserved.
-
Both global and static global variables are directly accessible within their scope. A static variable becomes a member variable, but what is passed in from the constructor is a memory address, which is then accessed via the address. A local variable becomes a member variable, its value is passed in directly from the constructor and assigned to the member variable, which is then accessed through the member variable.
-
The __main_block_IMPL_0 structure can be seen from clang’s Block conversion source code. It can be observed that when Block syntax is executed, the values of automatic variables used by Block syntax expressions are stored in the Block’s structure instance, that is, in the Block itself. The Block captures external variables only for values that are needed inside the Block closure. It does not capture values that are not needed.
2.2 Implementation principle of __bolck
- The __block variable is also converted to a structure __Block_byref_i_0, which has five member variables. The first is an isa pointer, the second isa __forwarding pointer to its own type, the third isa flag, the fourth is its size, and the fifth is the variable value with the same name as the variable name.
- Once a Block assignment triggers copy, __block is copied onto the heap, which is also __NSMallocBlock. ARC environments also have __nsstackBlocks, in which case __block is on the stack.
- In the ARC environment, blocks capture external object variables, is a copy, the address is different. Only variables with a __block modifier are captured and held inside the Block. For non-object variables, the value of an automatic variable is copied into a Block, and the value of an automatic variable without a __block can only be accessed from inside and cannot be changed. Automatic and static variables with __block are direct address access. So inside a Block you can change the value of a variable directly.
3. How does Autorealespool work?
- Code wrapped in Autoreleasepool is compiled to become
void * atautoreleasepoolobj = objc_autoreleasePoolPush(); Objc_autoreleasePoolPop (atAutoReleasepoolobj); void *objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); }Copy the code
- The push and POP functions are actually push and pop calls to AutoreleasePoolPage
class AutoreleasePoolPage { magic_t const magic; // Verify the integrity of the current AutoreleasePoolPage id *next; Pthread_t const thread; pthread_t const thread; pthread_t const thread; pthread_t const thread; // Current thread AutoreleasePoolPage * const parent; // AutoreleasePoolPage *child; // Uint32_t const depth; uint32_t hiwat; };Copy the code
Each node is an AutoreleasePoolPage that is offset every time an object is added to the next pointer.
- POOL_BOUNDARY also known as POOL_SENTINEL POOL_BOUNDARY is just another name for nil when objc_autoreleasePoolPush is called for each autorelease pool initialization, Both push a POOL_BOUNDARY to the top of the auto-release pool stack and return the POOL_BOUNDARY sentinel object. It can be understood as a token.
- The push process makes a series of decisions, such as whether the current page is full, traversing the bidirectional list, and putting the object on the stack of AutoreleasePoolPage.
- Use pageForPointer to retrieve the current token (sentry object) from AutoreleasePoolPage; Call the releaseUntil method to release objects in the stack until stop, and call the child’s kill method. The kill process also passes a series of judgments, such as whether the current page is empty, to release the current and all children.
Heap, stack, queue
4.1 the heap
- A heap is a sorted tree data structure, with each node having a value. The data structure of a heap is usually a binary tree. So a heap in a data structure can often be thought of as an array object of a tree. And the heap needs to satisfy the following two properties:
- The value of a node in the heap is always no greater than or less than its parent;
- The heap is always a complete binary tree.
- There are two types of heap, maximum heap and minimum heap. The heap with the largest root node is called the maximum heap or the large root heap, and the heap with the smallest root node is called the minimum heap or the small root heap. In a minimum heap of placed elements, the elements in the parent node must be smaller than the elements in the children, but there is no rule on the size of the left and right nodes.
- The heap is often used to implement priority queues, and access to the heap is arbitrary.
4.2 the stack
- A stack is a linear table that limits insert and delete operations to the end of the table. We call the end that allows insertion and deletion the top of the stack, the other end the bottom of the stack, and the stack that does not contain any data elements the empty stack
- Stack is a data structure with lifO, also known as lifO linear table. Application of stack – recursion
4.3 the queue
- A queue is a linear table that allows only an insert at one end and a delete at the other. The end that allows insertion is called the tail, and the end that allows deletion is called the head.
- Queue is a fifO data structure, also known as fifO linear table.
4.4 How to simulate a queue with two stacks
- InStack, outStack; inStack, outStack; When joining a team, push to inStack; If the outStack is empty, all inStack elements are ejected one by one and pushed to the outStack, which ejects the top element of the stack. If outStack is not empty, outStack pops the top element.
#import "YZQueue.h" #import "YZStack.h" @interface YZQueue () @property (nonatomic, strong) YZStack *inputStack; @property (nonatomic, strong) YZStack *outStack; @end@implementation YZQueue - (NSInteger)size {return self.inputStack.size + self.outstack.size; } // whether the queue isEmpty - (BOOL)isEmpty {return self.inputstack.isempty && self.outstack.isempty; } // enterQueue - (void)enterQueue:(id)value {[self.inputStack push:value]; } // queue - (id)deQueue {if (self.outstack.isempty) {while (! self.inputStack.isEmpty) { [self.outStack push:self.inputStack.pop]; } } return self.outStack.pop; } // Get the header element of the object - (id)front {if (self.outstack.isempty) {while (! self.inputStack.isEmpty) { [self.outStack push:self.inputStack.pop]; } } return self.outStack.top; } #pragma mark - (YZStack *)inputStack {if (! _inputStack) { _inputStack = [[YZStack alloc] init]; } return _inputStack; } - (YZStack *)outStack { if (! _outStack) { _outStack = [[YZStack alloc] init]; } return _outStack; } @endCopy the code
5. The difference between dynamic and static libraries
- Library: a collection of resource files and code compilations
- Static library: static library is at compile time, the complete copy to the executable file, is used for many times there are many redundant copies; . A and b. The framework.
- Dynamic library: the program is dynamically loaded into memory by the system at runtime, rather than copied, for the program to call. The system only loads once, multiple programs share, saving memory. As a result, the compilation is smaller, and because dynamic libraries are referenced only when needed, they are faster. .dylib and framework.
- Why can the Framework be a dynamic library or a static library? The. Framework of the system is a dynamic library, which we build ourselves. The framework is a static library.
- Static and dynamic names distinguish how the compiled code is linked to the target program.
6. Structure memory alignment
Memory usage of basic data types (all 64 bits, 32 bits meaningless)
struct LGStruct1 {
double a;
char b;
int c;
short d;
}struct1;
struct LGStruct2 {
double a;
int b;
char c;
short d;
}struct2;
struct LGStruct3 {
double a;
int b;
char c;
short d;
struct LGStruct1 e;
}struct3;
NSLog(@"%lu-%lu-%lu",sizeof(struct1),sizeof(struct2),sizeof(struct3));
Copy the code
The output is
- Data member alignment rules: The data member of a struct (or union), the first data member is placed at offset 0, and the starting position of each data member is from the size of the member or the size of the member’s children (as long as the member has children, such as arrays). Structure, etc.) (for example, if int is 4 bytes, it is stored from the address that is a multiple of 4.
- Struct as members: If a structure has some struct members, the struct members are stored from an integer multiple of the size of the largest element in the structure. (struct A contains struct B, char, int, double, etc.)
- Wrap up: the total sizeof the structure, the result of sizeof, must be an integer multiple of the largest member within the structure.
Case analysis structure LGStruct1, through the memory alignment rule calculation process is as follows:
- Variable A: **double ** takes 8 bytes, starting at position 0, then 0-7 stores a
- The variable b: **char ** takes 1 byte, starting at position 8, where 8 is a multiple of 1 and 8 stores B
- The variable c: int takes 4 bytes, starting at position 9, but at this point 9 is not a multiple of 4, so we need to look further and find the nearest position 12 that divisible 4, then 12-15 stores C
- Variable D: **short ** takes 2 bytes, starting at position 16, where 16 is a multiple of 2, then 16-17 stores D
- End: LGStruct1 requires 18 bytes of memory, and the maximum number of bytes in LGStruct1 is 8 bytes. The memory size of 18 bytes is not an integer multiple of the maximum number of bytes in LGStruct1, so it must be replenished up to the final size of 24 bytes