I am very happy to write my own understanding and summary of Objective-C, not only because I have been a heavy Objective-C developer for many years, but also because it is a programming language with unique ideas, creativity, beautiful syntax, and historical status. If there is any expectation for this article, I hope to clarify some questions like "Why is it so?" Objective-C was invented in the 1980s. The authors of Objective-C, Brad Cox and Tom Love, were inspired by SmallTalk after they came into contact with it. On the other hand, they were optimistic about the huge influence and broad prospects of C language, so they chose to introduce the object-oriented and message dispatching concepts of SmallTalk language on the basis of C language. The initial version was implemented as an extension of C language. A preprocessing module supporting Objective-C was written in the C compiler. The preprocessing would first convert the Objective-C syntax code into C code, and then continue the compilation process of C code. In 1988, NeXT, which targeted enterprises as its customers, purchased the license to use Objective-C, and then expanded the famous open source compiler GCC to support Objective-C, and developed basic libraries such as AppKit and FoundationKit. Objective-C became the "standard" application development language on the NeXTSTEP system (workstation). In 1996, Apple acquired NeXT, and the NeXTSTEP/OPENSTEP system became the research and development basis of Apple's new generation operating system OS X. In 2005, Apple introduced Chris Lattner and his LLVM technical team. For the first time, new features and compilation optimization of Objective-C received top priority support from high-level compilers, starting with back-end code optimization and generation, and gradually expanding to front-end syntax parsing (Clang). Today (2015), Objective-C has a more suitable and excellent compiler suite choice besides GCC - LLVM compiler. LLVM includes complete front-end and back-end modules, and its latest version is 6.1 (2015). Objective-C is object-oriented, which is the most basic concept of Objective-C. Regarding object-orientation, certain algorithms (functions) and data (variables) are bound together by some kind of internal connection to form the most basic program structural unit. These structural units are the objects that are often mentioned. With the word "abstract", we call it an abstract object, and the term is abbreviated as class; through the assignment of variables (I think not only variables, but also logical operations such as closures can be used for assignment), entity objects are formed, and the term is abbreviated as object (Objective-C generally calls it instance). Objects are not completely independent from each other. Through clever ways, they can establish close connections, such as inheritance and derivation. The abstraction of things and the reuse of code have subtle and significant value. Brad Cox and Tom Lov published the first official Objective-C book, the title of which is "Object-Oriented Programming, An Evolutionary Approach". So, why do we need objects and why do we need object-orientation? This is a good question. Observing the general thinking of human beings, we understand that the most commonly used concept in understanding the world is objects. We are good at abstracting everything we perceive into objects. By understanding the composition of objects and the relationship between objects, we can achieve the purpose of understanding and acting on the world. This has always been very effective! Object-oriented programming is to apply the talent of human thinking and the accumulated intellectual wealth to programming, so that the efficiency and ability of programs to enhance production capacity/improve the quality of life will be greatly improved. /* The above picture shows the collection objects supported in FoundationKit - (immutable) arrays, which inherit from the root class NSObject and support a series of protocols (interfaces) including NSCopying. Count represents a read-only variable, and - (id)objectAtIndex:(NSUInteger)index represents the available methods (functions) supported by the array */ Message dispatch is the mode of calling Objective-C functions (Objective-C actually calls methods), which is also mentioned in the previous article. The concept is inherited from Smalltalk. When Objective-C objects call each other's functions, it is regarded as passing messages to the target object. The sender of the message is called the sender, the receiver of the message is called the receiver, and the string passed in the middle of the message is called the selector. /* The code in the figure above indicates that there are at least two obvious receivers. self.view is one of the message receivers. The message (string/selector) passed is "setBackgroundColor:". UIColor represents a class, which can also be a message receiver. The string/selector is "yellowColor" */ The message processing requires first determining the actual execution method and then jumping to it and executing it. We understand this as a response to the message. During compilation, the actual execution result cannot be determined from the syntax of "dispatching a message" alone. Only during program execution can the actual execution result be determined. This method of determining the actual execution during execution is called dynamic binding in Objective-C. The working mechanism of message dispatch is obviously different from another famous object-oriented programming language - C++. C++ calls the function of an object, and the relationship between the function and the object must be strictly determined during compilation. If there is no function named fly defined in car, the compiler will not pass, but will report an error. If Objective-C sends a selector with the string "fly" to car, even if car does not implement the fly method, the compiler can still pass, but it will throw an exception during runtime because it cannot obtain the actual execution method. This means that the design of message dispatch makes Objective-C very tolerant of the class to which the object belongs during compilation. As mentioned above, the same object has the same definition, called a class. The class itself can also be regarded as an object - a "class" object. The "class" object can be defined as a "class", such as comparison operations, hashing, descriptions, class names, etc. In short, everything is an object. In C++, we can customize the "class" based on what is called a template. Objective-C adds new definitions to all classes through a unified base class such as NSObject (not only NSObject, but also various root protocols). You can send any message you want to any object, including the null pointer nil. The message dispatching mechanism makes it easier to implement or hook the original target (method, variable, etc.) during runtime without recompiling, and has more practical application value. This requires the implementation mechanism of message dispatching and dynamic binding - Runtime, but Runtime does not only work for message dispatching and dynamic binding, it is also the implementer of Objective-C's object-oriented, memory model and other features. Before formally introducing Runtime, let's continue to introduce another important concept of Objective-C. I would like to say that it is the Objective-C memory management model. When the program is running, creating an object always occupies memory, and the total memory size is always limited. Therefore, when an object is no longer needed, the memory resources it occupies should be promptly recovered for new objects. The memory management principle of Objective-C is simply the "reference counting" mechanism. If a module needs to reference an object, the reference count value used for object statistics will be increased by 1 when referencing it, and recorded in the object's structural information. When the module no longer needs the object, it will be reduced by 1. When the reference count value of the object is 0, it can be considered that the object is no longer needed and the memory will be destroyed and released in time (resources will be recycled). The memory space of Objective-C objects is only allocated in the "heap space" and will definitely not be allocated on the "stack". We know that the occupation and recovery of the "stack" has strict data operation rules, referred to as "first in, last out". When a function is executed, the variables passed in (including object variables, of course) will be automatically pushed into the "stack" (occupying memory resources) according to the determined sequence rules. When the function is executed, these variables will be automatically popped out (releasing memory resources) according to the opposite sequence rules. Therefore, we can see that the "stack" is actually unable to implement the "reference counting" mechanism, and Objective-C denies the idea of using the "stack" to store objects. In terms of syntax, Objective-C cannot directly declare and create an object variable like C++, and cannot directly operate the object. Objective-C needs to create an object variable with a syntax similar to the C language application for heap memory blocks (alloc), and must use the object pointer as the access handle, which is very similar to the C language application for heap memory blocks. This "capricious" design of Objective-C also makes it so that when objects are nested (one object is a member variable of another object), the object is based on the reference counting mechanism, and its member variables must also recursively follow the reference counting mechanism. Because member variables are actually object pointers, it is very likely that they share the same object with other objects (the pointers all point to the same block of memory), and the reference counting mechanism is suitable for supporting the management of this "shared" memory. It should be noted that if an object variable can be created as a member variable like in C++, then the member variable will be stored in a continuous memory block where the object is located. When the object is destroyed, all the memory blocks occupied by the member variable can be automatically released and recovered. This is not in line with the reference counting mechanism. Therefore, it is further understood that object variables are not supported in Objective-C. /* The interface (method) in the figure above is the interface (method) related to memory management in Objective-C*/ Runtime (component) is generally translated as runtime component, a basic library (lib) written in pure C language. Programs written in Objective-C must be run by Runtime to work properly. In programming languages such as Java, PHP or Flash, everyone is familiar with Runtime, and Objective-C Runtime is actually the same thing. It is Runtime that implements many features of Objective-C. Objective-C's object-oriented, message dispatching, dynamic binding and memory management are all closely related to Runtime. So, in Objective-C, how are objects, classes, functions (methods) constructed and function? As mentioned earlier, classes in object-oriented programming are regarded as abstract objects, and Runtime also adheres to this concept. Runtime is written in pure C, using struct structures to describe objects (entity objects) and classes (abstract objects). The struct of the object is relatively simple, using *id as the pointer alias of the structure objc_object, and the ***struct member isa is a pointer variable of the Class type, which determines the class to which the object belongs. The Class type is also a struct, which is a pointer alias of the structure objc_class, used to describe the struct composed of classes. The *** member isa is also a pointer variable of the Class type (the design that the *** members of the two structures are pointer variables of the Class type allows us to further understand that in Runtime, classes are indeed treated the same as objects). The isa of the class will point to the struct called metaclass. The metaclass abstracts the characteristics of the class. The *** member of the metaclass is naturally also a pointer variable of the Class type of isa. The difference is that the isa of the metaclass ultimately points to itself. From this, we can observe that the class struct is a recursive nested design, which embodies the concept of object-oriented *** abstraction. The final implementation of pointing to itself is the need for actual engineering processing. Generally, we also believe that the objc_class struct stores the metadata of the class, such as the instance methods of the class, the instance variables of the class, and the superclass pointer of the class. The Runtime also allows us to query and dynamically expand the variables, methods, properties, protocols, etc. of all Objective-C classes through standard interfaces (C functions), thereby achieving our goal of enriching the language and class library features in our project. /* The above figure prints all variables, properties, and methods of UIView in UIKit through the standard Runtime API (C function)*/ Another important feature of Runtime is message dispatch. objc_msgSend is the core and most basic entry function of message dispatch. In addition, there are objc_msgsend_stret, objc_msgSend_fpret, objc_msgSendSuper and other functions. However, their importance and role are far less than objc_msgSend. The objc_msgSend function will call the appropriate method according to the receiver and selector. In order to complete this operation, the function needs to search for its "method list" in the class to which the receiver belongs. If a method that matches the selector string name can be found, it will jump to the method. If it cannot be found, it will continue to search upward along the inheritance system and jump after finding a suitable method. If it still cannot find a matching method in the end, it will perform the "message forwarding" operation. From this, we can see that calling a method seems to require quite a few steps. Each step is an overhead. Will it cause performance problems in Objective-C? Fortunately, obj_msgSend will cache the matching results in the "fast map". Each class has such a cache. If you need to send the same selector message to the class later, the execution will be much faster. Of course, this "fast path" is not as fast as the "statically bound function call", but through optimization techniques such as assembly, the query overhead of the mapping table is very small. It can be said that even compared with C++ static binding, Objective-C's message dispatch mechanism is no longer a performance bottleneck. If the above message dispatch mechanism is the entire content of Objective-C dynamic binding, it is not complete. When the object cannot find the relevant method and the message cannot be responded correctly, the "message forwarding" mechanism will be started. Yes, in addition to the list of methods that support "dynamic addition and replacement", we can also provide other normal response methods. Message forwarding is divided into several stages. First, the receiver or the class to which it belongs is asked whether it can dynamically add methods to handle the current "unknown selector". This is called "dynamic method resolution". The Runtime will seek support for dynamically added methods by calling back a class method. If the receiver still cannot respond normally after the Runtime completes the query for dynamically added methods, the Runtime will continue to ask the receiver whether there are other objects, that is, other receivers that can handle this message. If an object that can be processed is returned, the Runtime will forward the message to the returned object, and the message forwarding process will end. If no object is returned, the Runtime will encapsulate all the details related to the message into an NSInvocation object, and give the receiver one more chance to try to resolve the message that has not yet been processed. The message forwarding process can be summarized in the following chart: As can be seen from the chart, the receiver has the opportunity to process the message in each step. The further the step is, the greater the cumulative cost of processing the message. Therefore, if the message can be processed in the first step, the Runtime can also cache the method, further reducing the cost of the first query while completing the process in one step. It should be noted that in the first stage, two interfaces need to be used together. First, the formatted method object must be returned through the - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector interface. The next interface - (void)forwardInvocation:(NSInvocation *)anInvocation passes in the parameter NSInvocation object, which is dependent on this method object. If the NSMethodSignature object in the first step returns nil, the message forwarding process ends. *** Using the message forwarding mechanism, we implement an example that allows the NSString class to support NSArray instance methods, which is very helpful in reducing the crash rate of the program: We first implement a method replacement interface swizzle method to help us implement code injection into the parent class method without inheritance. Through the swizzle method (class_addMethod, class_replaceMethod, method_exchangeImplementations), in the resolveInstanceMethod: of the NSString class, three NSArray instance methods are injected by dynamic method resolution: Test cases: Test results: Finally, I have used a lot of space and code snippets to try to explain some of the most basic concepts of Objective-C, including object-orientation, message dispatching, memory management, etc., and also discussed the implementation of these concepts on Rumtime. This does not include properties, classifications, class families, protocols and other equally important features in Objective-C, nor does it elaborate on some of the coding details (regarding coding, you can always get many satisfactory answers through search engines). The author hopes to help readers quickly understand Objective-C and understand why it is like this and not like that in a limited space, and to help developers and engineers who want to further learn and use Objective-C. Reference links (partial):
|
<<: If the Internet and the economy really face a cold winter, I will give you a reassurance pill
>>: Six ways to use group analysis in life and mobile game operations
Under heavy pressure, TV manufacturers have resor...
Amazon quietly entered Tmall, seemingly not wanti...
According to Master Chan's analysis of the &q...
[[135484]] Earlier this week, iPhone users discov...
Is the ocean really blue? In a study published in...
As we all know, whole-site optimization refers to...
According to the official website of the State Ad...
Recently, the old series sitcom "Country Lov...
It is not difficult to make money. You just need ...
Recently, Microsoft replaced Nokia with Microsoft...
On January 15, 2019, the launch conference of GMI...
When it comes to glaciers, perhaps the only thing...
People familiar with the matter revealed that for...
The cost of acquiring traffic in Internet finance...
Xue Wei's Psychology Micro-Course 101 Episode...