Deep understanding of Objective-c: Optimizing Your Code

Source: Internet
Author: User


As long as we use objective-c, we deal with method calls every day. We all know that the method of OBJECTIVE-C resolution is dynamic, but in the bottom of a method is how to find, how the method cache and how the operation is little known.

This paper probes into the implementation of the Objective-c method resolution (methods resolving) and method cache in the runtime layer from the source point of view.


This article is a study of the OBJECTIVE-C runtime source, the main analysis of the objective-c in the runtime layer of the method resolution process and method cache.

As we all know, calling a method in Objective-c is like this:

[object MethodA];

This means we want to call the MethodA of object.
But what does it mean to call a method inside a objective-c, or is it just like C + +, that any non-virtual method is compiled into a unique symbol that is called to look up the symbol table, find the method and then invoke it?
The answer is in the negative. When a method is called in Objective-c, the runtime will translate the call into a

Objc_msgsend (id Self, SEL op, ...)

And how is objc_msgsend specifically distributed? Let's look at the source code of the runtime layer objc_msgsend.
In OBJC-MSG-ARM.S, the code for Objc_msgsend is as follows:

ENTRY objc_msgsend# Check whether receiver isNilteq A1, #0beq lmsgsendnilreceiver# Save registers and load receiver'S class for CachelookupSTMFD sp!, {A4,v1}ldr v1, [A1, #ISA]# Receiver isnon-Nil:search the Cachecachelookup A2, V1, lmsgsendcachemiss# cache Hit (ImpinchIP) and Cachelookup returns with Nonstret (eq)Set, restore registers and CALLLDMFD SP!{a4,v1}bx ip# cache Miss:go Search the method Listslmsgsendcachemiss:ldmfd sp!, {a4,v1}b _objc_msgsend_uncachedlmsgsendnilreceiver:mov A2, #0bx lrlmsgsendexit:end_entry objc_msgsendstatic_entry objc_msgsend_uncached# Push stack framestmfd sp!, {a1-a4,r7,lr}add R7, SP, # -# Loadclassand Selectorldr A3, [A1, #ISA]/*class = Receiver->isa*//*selector already in A2*//*receiver already in A1*/# do the lookupmi_call_external (__class_lookupmethodandloadcache3) MOVE IP, a1# Prep forforwarding, Pop stack frame and call Impteq v1, v1/*set Nonstret (eq)*/LDMFD SP!, {a1-A4,R7,LR}BX IP

As you can see from the code above, the Objc_msgsend (for ARM platforms) message distribution is divided into the following steps:

    • Determine if receiver is nil, that is, the first parameter of Objc_msgsend self, which is the object to which the method to invoke belongs

    • Search from the cache, find it, distribute it, or

    • Using _class_lookupmethodandloadcache3 method in to find selector

      • If GC is supported, ignore methods for non-GC environments (retain, etc.)
      • From the method list of this class, look for selector, if found, populate the cache, and return selector, otherwise
      • Look for the method list of the parent class and look up in turn until selector is found, populated into the cache, and returns selector, otherwise
      • Call _class_resolvemethod, if you can dynamically resolve for a selector, do not cache, the method returns, otherwise
      • Forward this selector, otherwise
    • Error, throw exception


From the above analysis we can see that when a method in the "upper" class, compared with the "lower layer" (the upper and lower layers of the inheritance relationship) object to call, if there is no cache, then the entire lookup chain is quite long. Even if the method is in this class, when the method is more, every time to find is also a laborious thing.
Consider one of the following calling procedures:

 for int 0 100000; + +i)    {*myobject = myobjects[i];    [MyObject MethodA];}

When we need to call a method hundreds of thousands of times or more, the consumption of the lookup method becomes very significant.
Even if our usual non-mass calls, unless a method is called only once, the cache is useful. At runtime, so many objects, so many method calls, the time saved is also very considerable.

what is the method cache ?

In the face of the source code, no secret principle, we look at the source of the method of caching in the end is what, in, Objc_cache is defined as follows:

struct Objc_cache {    uintptr_t mask;             /*  */    uintptr_t occupied           ; *buckets[1];};

Well, the definition of Objc_cache looks simple, it contains the following three variables:
1), Mask: can be considered to be the current maximum index (starting from 0), so the cached size (total) is mask+1
2), occupied: occupied slots, because the cache is in the form of a hash table exists, so there will be empty slots, and occupied represents the current number of occupied
3), buckets: Hash table represented by an array, cache_entry type, each Cache_entry represents a method cache
(Buckets defined at the end of Objc_cache, indicating that this is a variable-length array)

The definition of Cache_entry is as follows:

struct {    SEL name;      // same layout as struct Old_method    void *unused;    IMP imp;   // same layout as struct Old_method} Cache_entry;

The Cache_entry definition also contains three fields, namely:
1), name, cached method name
2), unused, reserved fields, not yet used.
3), IMP, method realization

Caching and hashing

The cached storage uses a hash table.
Why use a hash table? Since the hash table is faster to retrieve, let's look at how the method cache is hashed and retrieved:

 //  Scan for the first unused slot and insert there.  //  there is guaranteed to being an empty slot because the  //  minimum size is 4 and we resized at 3/4 full.  Buckets = (cache_entry * *) cache->buckets;  for       (index = Cache_hash (sel, Cache->mask);      Buckets[index] != NULL;    Index  = (Index+1 ) & Cache->mask) {  //  empty   = entry; 

This is a code snippet for storing a method in the method cache, and we can see that the SEL was hashed and found an empty slot placed in the buckets, and the cache_hash is defined as follows:

#define Cache_hash (SEL, Mask) ((uintptr_t) (SEL) >>2) & (Mask))

This code uses the SEL's pointer address and mask to do a simple calculation.
The cache from the hash table is written using assembly language (compiled for highly optimized objc_msgsend). We look at the Cachelookup method inside the OBJC-MSG-ARM.MM:

. Macro Cachelookup/*Selreg, Classreg, Misslabel*/MOVE R9, $0, LSR #2          /*index = (sel >> 2)*/Ldr A4, [$1, #CACHE]/*cache = Class->cache*/add A4, A4, #BUCKETS/*buckets = &cache->buckets*//*Search the Cache*//*A1=receiver, A2 or A3=sel, R9=index, A4=buckets, $1=method*/1: LDR IP, [A4, #NEGMASK]/*mask = Cache->mask*/and R9, R9, IP/*Index &= Mask*/LDR $1, [A4, R9, LSL #2]/*method = Buckets[index]*/TEQ $1, #0                  /*if (method = = NULL)*/Add R9, R9, #1              /*index++*/beq $2                      /*Goto Cachemisslabel*/LDR IP, [$1, #METHOD_NAME]/*Load Method->method_name*/TEQ $0Ip/*if (method->method_name! = sel)*/BNE 1b/*Retry*//*Cache hit, $ = = Method Triplet Address*//*Return triplet in $ and imp in IP*/LDR IP, [$1, #METHOD_IMP]/*imp = Method->method_imp*/. Endmacro

Although it is a compilation, but the comments are too exhaustive, it is not difficult to understand, or to seek a hash, go to buckets, find not to follow the rules of the hash conflict continue downward, until the end.


After understanding the definition of the method cache, we ask a few questions and answer

  • Where does the method cache exist?
    Let's go through the definition of class, in OBJECTIVE-C 2.0, the definition of class is roughly the same (see OBJC-RUNTIME.MM)

      struct _class_t {  struct _class_t *Isa;   struct _class_t *superclass;   void *cache;   void *vtable;   struct _class_ro_t *ro;  };

    We see that there is a cache field in the definition of the class, yes, all caches of the class exist on Metaclass, so each class has only one copy of the method cache, not one for each class object.

  • Does the parent class method cache only the parent class, or does the subclass cache the method of the parent class?
    In the first section of Objc_msgsend's retrospective, we can see that even the methods taken from the parent class will be in the method cache of the class itself. When a parent object is used to invoke that method, a copy is also cached in the parent class's metaclass.

  • Is there a limit to the method cache size of the
  • class?
    to answer this question, we need to look at the source code, in there is a variable defined as follows:

     /*   when _class_slow_grow was Non-zero, any Given cache is actually grown * with the odd-numbered times it becomes full;  On the even-numbered * times, it is simply emptied and re-used. When the this flag was zero, * caches is grown every time.  */ static  const  int  _class_slow_grow = 1 ; 

    does not have to look further at the code snippet, but we can see the answer to the question only from the comments. Note that when _class_slow_grow is a value of 0, the size of the method cache will grow only if the method caches the odd number of times (using more than 3/4 slots) (the cache will be emptied, otherwise the hash value will be incorrect); When the odd number of times is full, The method cache is emptied and re-exploited. If the _class_slow_grow value is 0, then each time the method cache is full, its size will grow.
    so as far as the problem is concerned, the answer is no limit, although the value is set to 1, the method cache size will be slower, but there is no limit.

  • Why is the method list of the class not directly into the hash list, make list, also want to cache separately, how much trouble?
    This question, I think there are the following three reasons:

    • The hash list is not sequential, the Objective-c method list is a listing, is sequential, objective-c in the search method will follow the list in turn, and the category method in front of the original method list, need to be found first, If you use the hash method directly, the order of the methods cannot be guaranteed.
    • The list method also preserves many other properties besides selector and Imp.
    • The hash table is empty slot, it wastes space
Cache-Performance optimized for Balm?

Non-Also, even if there is a method of objective-c itself cache, we still have a lot of methods to optimize the optimization of space, for this matter, this article is very detailed, we can take a self-observation Artikel/optimization/opti-3-imp-deluxe.html (strongly recommended, although we generally do not encounter the need for such intensity optimization, but this spirit and thought is worthy of our study)


Deep understanding of Objective-c: Optimizing Your Code

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.