Runtime knowledge sorting

Sorting out iOS Runtime knowledge

The source code can be viewed here: https://opensource.apple.com/tarballs/objc4/

1, Understanding of isa

  • 1. Object pointing
    Point to its class object to find the method of the object. The relationship among object, class and metaclass is shown in the following figure:

  • 2. Type
    a. Pure pointer: points to the memory address
    b,NON_POINTER_ISA: points to the memory address and some other information

2, class_rw_t and class_ ro_ Understanding of T

  • 1,class_rw_t: It is readable and writable, and stores the properties, method list, protocol list, etc. in objc class
//Official source code
struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
        explicit_atomic<uintptr_t> ro_or_rw_ext;
        
        Class firstSubclass;
        Class nextSiblingClass;
    private:
        using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t *, class_rw_ext_t *>;
    
        const ro_or_rw_ext_t get_ro_or_rwe() const {
            return ro_or_rw_ext_t{ro_or_rw_ext};
        }
    
        void set_ro_or_rwe(const class_ro_t *ro) {
            ro_or_rw_ext_t{ro}.storeAt(ro_or_rw_ext, memory_order_relaxed);
        }
    
        void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
            // the release barrier is so that the class_rw_ext_t::ro initialization
            // is visible to lockless readers
            rwe->ro = ro;
            ro_or_rw_ext_t{rwe}.storeAt(ro_or_rw_ext, memory_order_release);
        }
    
        class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
    public:
        void setFlags(uint32_t set)
        {
            __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
        }
    
        void clearFlags(uint32_t clear) 
        {
            __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
        }
    
        // set and clear must not overlap
        void changeFlags(uint32_t set, uint32_t clear) 
        {
            ASSERT((set & clear) == 0);
    
            uint32_t oldf, newf;
            do {
                oldf = flags;
                newf = (oldf | set) & ~clear;
            } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
        }
    
        class_rw_ext_t *ext() const {
            return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();
        }
    
        class_rw_ext_t *extAllocIfNeeded() {
            auto v = get_ro_or_rwe();
            if (fastpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>();
            } else {
                return extAlloc(v.get<const class_ro_t *>());
            }
        }
    
        class_rw_ext_t *deepCopy(const class_ro_t *ro) {
            return extAlloc(ro, true);
        }
        //Point to the read-only structure and store the initial information of the class
        const class_ro_t *ro() const {
            auto v = get_ro_or_rwe();
            if (slowpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>()->ro;
            }
            return v.get<const class_ro_t *>();
        }
    
        void set_ro(const class_ro_t *ro) {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                v.get<class_rw_ext_t *>()->ro = ro;
            } else {
                set_ro_or_rwe(ro);
            }
        }
        //Method list
        const method_array_t methods() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
            }
        }
        //Attribute list
        const property_array_t properties() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>()->baseProperties};
            }
        }
        //Protocol list
        const protocol_array_t protocols() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>()->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
            }
        }
};
  • 2,class_ro_t: It is read-only and not writable. It stores the attributes, methods, protocols and other information determined during compilation
//Official source code
struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
        const uint8_t * ivarLayout;
        const char * name;
        //Method list
        method_list_t * baseMethodList;
        //Protocol list
        protocol_list_t * baseProtocols;
        //Attribute list
        const ivar_list_t * ivars;
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
        _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    
        _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                return _swiftMetadataInitializer_NEVER_USE[0];
            } else {
                return nil;
            }
        }
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
        class_ro_t *duplicate() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
                return ro;
            } else {
                size_t size = sizeof(*this);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                return ro;
            }
        }
};

3, Analysis of memory occupied by NSObject object

  • Limited by the memory allocation mechanism, an NSObject object will allocate 16byte s of memory. In fact, in a 64 bit system, only 8 bytes are used; In 32-bit system, only 4 bytes are used. In iOS, the allocated memory is a multiple of 16.
#import <objc/runtime.h>
size_t size = class_getInstanceSize([NSObject class]);
NSLog(@"class instance size  %zu",size);
//Printout results
2021-02-16 13:42:33.664592+0800 DSPracticeDemo[38554:2390822] class instance size  8
====================================================================================
#import <malloc/malloc.h>
NSObject *obj = [[NSObject alloc] init];
    size_t mallocSize = malloc_size((__bridge const void *)obj);
 NSLog(@"class instance malloc size  %zu",mallocSize);
 Printout results
 2021-02-16 13:56:14.341374+0800 DSPracticeDemo[38624:2442458] class instance malloc size  16
  • class_getInstanceSize view the source code. The essence is as follows
size_t class_getInstanceSize(Class cls)
{
        if (!cls) return 0;
        //Call the method of memory alignment size
        return cls->alignedInstanceSize();
}

4, runtime method cache, storage form, data structure and search method

  • Method cache: cache_t incremental extended hash table structure. Bucket is stored in the hash table structure_ t
  • Storage form: bucket_ The SEL and IMP key value pairs are stored in t
  • Data structure:
    a,cache_ Tdata structure:
struct cache_t {
    #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
        explicit_atomic<struct bucket_t *> _buckets;
        explicit_atomic<mask_t> _mask;
    #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        explicit_atomic<uintptr_t> _maskAndBuckets;
        mask_t _mask_unused;
        ...
}

b,bucket_ Tdata structure:

struct bucket_t {
        private:
        // IMP-first is better for arm64e ptrauth and no worse for arm64.
        // SEL-first is better for armv7* and i386 and x86_64.
        #if __arm64__
        explicit_atomic<uintptr_t> _imp;
        explicit_atomic<SEL> _sel;
        #else
        explicit_atomic<SEL> _sel;
        explicit_atomic<uintptr_t> _imp;
        #endif
        ...
}
  • Search method: orderly, using binary search method; Unordered, direct traversal

5, dealloc release mechanism of NSObject

  • 1. Call release: reference count becomes 0
  • 2. Call [self dealloc]
  • 3. Dealloc is called by the parent class (every inheritance layer will call dealloc)
  • Middle note 4. Call NSObject dealloc: in OC, only one thing is done, that is, calling object_ in runtime. The dispose method handles C + + related code, reference count table, weak reference table, associated objects, etc
//Official source code
//dealloc method call of NSObject_ objc_rootDealloc method
- (void)dealloc {
        _objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
        ASSERT(obj);
        obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
        if (isTaggedPointer()) return;  // fixme necessary?
        //Judgment: Isa Nonpointer, weak reference table, association object, C + + code, reference count table, etc
        if (fastpath(isa.nonpointer  &&  
                     !isa.weakly_referenced  &&  
                     !isa.has_assoc  &&  
                     !isa.has_cxx_dtor  &&  
                     !isa.has_sidetable_rc))
        {
            assert(!sidetable_present());
            free(this);
        } 
        else {
            object_dispose((id)this);
        }
}
id object_dispose(id obj)
{
        if (!obj) return nil;
        objc_destructInstance(obj);    
        free(obj);
        return nil;
}
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory. 
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj) 
{
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
            // This order is important.
            if (cxx) object_cxxDestruct(obj);
            if (assoc) _object_remove_assocations(obj);
            obj->clearDeallocating();
        }
        return obj;
}
inline void objc_object::clearDeallocating()
{
        if (slowpath(!isa.nonpointer)) {
            // Slow path for raw pointer isa.
            sidetable_clearDeallocating();
        }
        else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
            // Slow path for non-pointer isa with weak refs and/or side table data.
            clearDeallocating_slow();
        }
        assert(!sidetable_present());
}
NEVER_INLINE void objc_object::clearDeallocating_slow()
{
        ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
        SideTable& table = SideTables()[this];
        table.lock();
        if (isa.weakly_referenced) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        if (isa.has_sidetable_rc) {
            table.refcnts.erase(this);
        }
        table.unlock();
}

6, Method Swizzling

  • Method exchange, calling a middle note in OC is actually sending a message to an object, and the only basis for finding the message is the name of selector. Using the dynamic characteristics of OC, the corresponding implementation of selector can be exchanged at run time. There is a method list in each class, which stores the mapping relationship between the method name and the method implementation. The essence of the selector is the method name, and the imp is the pointer to the function implementation. The corresponding imp can be found through the selector, and then the method implementation can be exchanged.
  • Methods can be exchanged through these three methods_ Exchange implementations, class_replaceMethod_ Setimplementation (implementation of setting method)

7, Add attribute to classification

  • The associated objects are stored in the global singleton in the form of hash table
@interface NSObject (Extension)
@property (nonatomic,copy) NSString *name;
@end
@implementation NSObject (Extension)
- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY);
}
- (NSString *)name{
    return objc_getAssociatedObject(self, @selector(name));
}
@end

8, Class object data structure

  • The data structure is relatively rich, which is inherited from objc_object
struct objc_class : objc_object {
        // Class ISA;
        Class superclass;//Parent class pointer
        //Method cache
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
        //Data, encapsulated class_rw_t
        class_rw_t *data() const {
            return bits.data();
        }
        ...
}

9, Timing, purpose and implementation principle of merging Category and original class

  • Merger Timing
    1. After the program is started and compiled, the runtime will initialize and call__ objc_init
    2. Then map_images
    3. Next, call map_images_nolock
    4,read_images reads all the information in the class
    5, then call reMethodizeClass to rebase note.
    Middle note 6. Calling attachCategories in reMethodize calls categories merge with the original class.

  • purpose
    1. Add methods and properties to system classes
    2. A class has a large number of methods, which can be classified by name

  • Implementation principle
    At runtime, methods are inserted into the method list of the original class in reverse order. Therefore, when adding the same method to different categories, the last method is actually effective

  • Please indicate the source of reprint
  • If there is any misunderstanding, please criticize and point out

Tags: iOS Runtime

Posted by anolan13 on Sat, 16 Apr 2022 04:46:20 +0930