A brief analysis of Objective-C Runtime

When I first learned Objective-C, I felt that there were many unfamiliar and strange syntax and features.

For example, syntax such as NSObject *obj = [[NSObject alloc] init];; for example, trying to call a function with a null pointer will not cause a crash. It wasn’t until I had the opportunity to learn more about Objective-C Runtime that I had some understanding.

If you are also curious about this seemingly strange way of writing, I believe this article can answer your questions.

Background

Dynamic programming language

The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically.

Objective-C is a “dynamic programming language”, which means that decisions are postponed to runtime as much as possible.

What is Objective-C Runtime

The definition given by Apple’s official documentation is as follows:

The Objective-C runtime is a runtime library that provides support for the dynamic properties of the Objective-C language, and as such is linked to by all Objective-C apps.

The dynamics of Objective-C are mainly reflected in the following three aspects:

  • Dynamic typing: The type of an object is determined at runtime
  • Dynamic binding: the calling method of the object is determined at runtime
  • Dynamic loading: Load the required resources or code only at runtime

The runtime is a library that provides the above-mentioned dynamic features for Objective-C. The runtime gives the C language object-oriented capabilities.

Why you need to understand Runtime

The dynamic features provided by Runtime for Objective-C provide developers with more flexibility. This flexibility can provide more solutions when solving some complex problems.
Method Swizzling is a good example. You may wish to think about the solutions to the following problems:

Set a unified background color for all views in the existing project. (Or for a more practical problem, in some cases it is necessary to gray out all pages in the App)

The general solution that comes to mind may be to uniformly inherit a parent class and implement the above requirements in the parent class. Runtime-based Method Swizzling can provide a more convenient solution.

In addition, understanding Runtime can also assist debugging from the bottom level.

In short, Runtime is an important knowledge point. Next, let’s take a deeper look at the running mechanism of Runtime.

Runtime

There are no secrets behind the source code – “Analysis of STL Source Code”

Implementation of objects and classes

As mentioned above, Runtime provides dynamic features such as dynamic typing and dynamic binding for Objective-C, and only determines the object type, calling method and other information at runtime. To do this, we first need to understand how objects are represented in Objective-C. In the source code objc.h, structures are used to represent objects:

/// Represents an instance of a class.
struct objc_object {<!-- -->
    Class _Nonnull isa;
};

It can be seen that the object is just a simple encapsulation of Class. Let’s continue to explore the meaning of Class:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

objc_class is defined in runtime.h:

struct objc_class {<!-- -->
    Class _Nonnull isa;
    Class _Nullable super_class;
    const char * _Nonnull name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list * _Nullable ivars;
    struct objc_method_list * _Nullable * _Nullable methodLists;
    struct objc_cache * _Nonnull cache;
    struct objc_protocol_list * _Nullable protocols;
};
/* Use `Class` instead of `struct objc_class *` */

The above structure is the representation of a class in Objective-C, and its more important members include:

  • ivars represents the member variables of the Objective-C class.

    struct objc_ivar {<!-- -->
        char * _Nullable ivar_name;
        char * _Nullable ivar_type;
        int ivar_offset;
    };
    
    struct objc_ivar_list {<!-- -->
        int ivar_count;
        /* variable length structure */
        struct objc_ivar ivar_list[1];
    };
    
  • methodLists, representing the functions in this Objective-C class

    struct objc_method {<!-- -->
        SEL _Nonnull method_name;
        char * _Nullable method_types;
        IMP _Nonnull method_imp;
    };
    
    struct objc_method_list {<!-- -->
        struct objc_method_list * _Nullable obsolete;
        int method_count;
        /* variable length structure */
        struct objc_method method_list[1];
    };
    
  • cache, used to cache functions to improve performance. Regarding the role of cache in performance optimization, the article “In-depth Understanding of Objective-C: Method Caching” by the Meituan technical team is worth reading.

    struct objc_cache {<!-- -->
        unsigned int mask /* total = mask + 1 */;
        unsigned int occupied;
        Method _Nullable buckets[1];
    };
    
  • protocols: Protocols (linked list) in this Objective-C class

    struct objc_protocol_list {<!-- -->
        struct objc_protocol_list * _Nullable next;
        long count;
        __unsafe_unretained Protocol * _Nullable list[1];
    };
    

Objects, classes and metaclasses

As can be seen from the above source code, there is only one class structure pointer in the object structure. The object can find the corresponding class through this pointer, and then member variables, member functions and other information can be found in the class structure.

The class structure also contains two class structure pointers:

  • isa
  • super_class

super_class is easier to understand. It is a pointer to the parent class. Inheritance can be achieved using this pointer.

What is the role of the isa pointer? Personally, I understand that an important role is to implement class functions.

In Objective-C, a class is itself an object with an opaque type calledClass. Classes can’t have properties defined using the declaration syntax shown earlier for instances, but they can receive messages.

Class functions, as the name suggests, are functions owned by a class. If the class itself is also an object, you can find the class function as long as you find its corresponding class structure (meta-class).

The class corresponding to the “object” of a class is called a meta-class, and each class has a corresponding meta-class.
Although this design is convenient, it also brings some problems, such as where does isa point to in the metaclass? Where does the super_class of the metaclass point to?
The picture below gives the answer:

  • Where does the isa of the metaclass point to?
    All metaclass isa pointers point to the base class’s metaclass. If a class has no parent class, its metaclass points to itself.
  • Where does the super_class of the metaclass point to?
    The super_class of the corresponding metaclass of a class points to the metaclass of the parent class of the class.

Function call

In Objective-C, messages aren’t bound to method implementations until runtime. The compiler converts a message expression [receiver message] into a call on a messaging function,objc_msgSend. This function takes the receiverand the name of the method mentioned in the message -that is, the method selector-as its two principal parameters objc_msgSend(receiver, selector). Any argumentspassed in the message are also handed toobjc_msgSend objc_msgSend(receiver, selector, arg1, arg2, …)

We know that in Objective-C, [object methodName] means calling the methodName method of the object object. Different from the direct access according to the function address in C language, the function call in Objective-C is implemented through objc_msgSend() in Runtime, that is, [object methodName] is translated into objc_msgSend(id self, SEL op, ...).

In other words, Objective-C converts function calls into message transmission. It is this transformation that causes the abnormal phenomenon mentioned at the beginning of the article that trying to call a null pointer function will not cause a crash.
Calling a function of an object means sending a message to the object. The SEL carried in the message can be understood as the ID of the method. Through SEL, it can be used in objc_class< methodLists in /code> finds the specific implementation of the method and then executes it.

If the function is not found in the class, it will search in the parent class through the super_class pointer of the class, as shown in the figure, and iterate until the base class.

If it is not found in the base class either, the message is discarded but does not cause a crash.

  • class function call
    As mentioned above, classes in Objective-C are also objects, and class functions are stored in the corresponding metaclass. Therefore, class function calls are found and executed through metaclasses.

At this point, we have a basic understanding of Objective-C classes and function calls. Now look at the syntax mentioned at the beginning of the article NSObject *obj = [[NSObject alloc] init];. Do you have it? What about your own understanding?

[NSObject alloc] is to send a message to NSObject, that is, to call its alloc method:

Returns a new instance of the receiving class.

According to the official documentation, the function is to get an instance of class NSObject. Then call the initialization function of this instance init

Reference

  1. Objective-C Runtime Programming Guide https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008048-CH1-SW1
  2. iOS Programmer Interview Written Test Guide https://weread.qq.com/web/bookDetail/338322907192ea37338e0dc
  3. Method Swizzling: What, Why & amp; How https://medium.com/@grow4gaurav/method-swizzling-what-why-how-cdbcdff98141
  4. https://github.com/apple-oss-distributions/objc4/blob/main/runtime/objc.h#L41
  5. https://github.com/apple-oss-distributions/objc4/blob/rel/objc4-781/runtime/runtime.h#L55
  6. https://github.com/apple-oss-distributions/objc4/blob/rel/objc4-781/runtime/runtime.h#L1954
  7. Deep understanding of Objective-C: method caching https://tech.meituan.com/2015/08/12/deep-understanding-object-c-of-method-caching.html
  8. Programming with Objective-C https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#:~:text=In Objective-C, a class,but they can receive messages
  9. Objective-C Runtime Programming Guide https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html#//apple_ref/doc/uid/TP40008048-CH104-SW1
  10. alloc https://developer.apple.com/documentation/objectivec/nsobject/1571958-alloc?language=objc
  11. init https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
syntaxbug.com © 2021 All Rights Reserved.