Runtime Series
Data structure Data structure Message mechanism The nature of super The nature of Super
1. objc_object
Objective-c object orientation is implemented based on C/C++ data structures called constructs. All objects we normally use are of type ID, which corresponds to the Runtime objC_Object structure.
// A pointer to an instance of a class.
typedef struct objc_object *id;
Copy the code
struct objc_object {
private:
isa_t isa;
/ *... Isa operation related weak reference related Associated object related Memory management related... * /
};
Copy the code
2. objc_class
The Class pointer is used to point to an Objective-C Class, which is an objc_class structure type, so the Class and meta-class underlying structures are both objC_class structures, and objC_class inherits from ObjC_Object. So it also has an ISA pointer, which is also an object.
// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
Copy the code
struct objc_class : objc_object {
// Class ISA;
Class superclass; // point to the parent class
cache_t cache; // formercache pointer and vtable
class_data_bits_t bits; Class_rw_t * plus custom RR /alloc flags class_rw_t * plus custom RR /alloc flags
class_rw_t *data() {
returnbits.data(); }};Copy the code
2.1 class_data_bits_t
class_data_bits_t
Is mainly toclass_rw_t
The package can be passedbits & FAST_DATA_MASK
To obtainclass_rw_t
.
struct class_data_bits_t {
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() {
return(class_rw_t *)(bits & FAST_DATA_MASK); }};Copy the code
class_rw_t
Represents read and write information related to the class, which is trueclass_ro_t
Encapsulation;class_rw_t
The method list, attribute list and protocol list of the class are mainly stored in.class_rw_t
The inside of themethods
,properties
,protocols
Inherit fromlist_array_tt
A two-dimensional array, readable and writable, contains the initial contents of the class and the contents of the classification.
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; // List of methods
property_array_t properties; // Attribute list
protocol_array_t protocols; // Protocol list
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
Copy the code
class_ro_t
Represents read-only information related to a class;class_ro_t
The main storage of the class member variable list, class name, etc.class_ro_t
The inside of thebaseMethodList
,baseProtocols
,ivars
,baseProperties
Is a one-dimensional array that is read-only and contains the initial contents of the class;- At first, the information of the class was stored
class_ro_t
When the program runs, it passes through a series of function call stacks, inrealizeClass()
In the function, we willclass_ro_t
I’m going to merge what’s in there with what’s classifiedclass_rw_t
And let mebits
Point to theclass_rw_t
.
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // The memory space occupied by the instance object
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; / / the name of the class
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // List of member variables
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
returnbaseMethodList; }};Copy the code
method_array_t
withmethod_list_t
.
2.2 cache_t
- Used to quickly find method execution functions;
- Is the increment extensible hash table structure, using the hash table to cache used methods, can improve the method search speed (space for time: sacrifice memory space for execution efficiency);
- Is the best application of the locality principle (such as some method calls with high frequency, stored in
cache
The next call to these methods will have a higher hit ratio); - The hash function is
f(@selector()) = index, @selector() & _mask
; - When we call a method,
runtime
Will cache this method tocache
, the next time this method is called,runtime
Priority tocache
In the search.
struct cache_t {
struct bucket_t *_buckets; / / a hash table
mask_t _mask; // Hash table length -1
mask_t _occupied; // The number of methods already cached
};
struct bucket_t {
private:
cache_key_t _key; // SEL
IMP _imp; // IMP function memory address
};
Copy the code
2.2.1 Cache Search process
Mm (objc4) / / objc - cache.
bucket_t * cache_t::find(cache_key_t k, id receiver) // find it by k, which is at sign selector{ assert(k ! =0);
bucket_t *b = buckets(); / / get _buckets
mask_t m = mask(); / / get _mask
mask_t begin = cache_hash(k, m); // Calculate the starting index
mask_t i = begin;
do {
// Select the value from the _buckets hash table according to index I
Cache_fill_nolock (); // If bucket_t has _key = 0, the bucket_t has not been cached at the index location
// If the _key of bucket_t is k, the query is successful. Bucket_t is displayed
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
// select I -1 from the __arm64__ hash table
// Until I points to the first element (index = 0), assign mask to I so that it points to the last element of the hash table, and continue traversing backwards
// If bucket_t for k is not found or bucket_t is empty, the loop ends, the search fails, and the bad_cache() function is called
// Go to methods in class_rw_t
} while((i = cache_next(i, m)) ! = begin);// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
static inline mask_t cache_next(mask_t i, mask_t mask) {
// return (i+1) & mask; // __arm__ || __x86_64__ || __i386__
return i ? i- 1 : mask; // __arm64__
}
Copy the code
2.2.2 Cache Adding Process
Mm (objc4) / / objc - cache.
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if(! cls->isInitialized())return; // If the class is not initialized, return it directly
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return; // It is possible that another thread has preempted this method, so check the cache and return it if it exists
cache_t *cache = getCache(cls); // ️ fetches cache_t for the class
cache_key_t key = getKey(sel); // ️ get _key according to sel
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1; _occupied in cache_t +1: the number of methods that have been cached; this is just to determine whether the cache is full
mask_t capacity = cache->capacity(); // Get cache capacity = _mask + 1
if (cache->isConstantEmptyCache()) { // If the cache is read-only, reapply for cache space
// Cache is read-only. Replace it.cache->reallocate(capacity, capacity ? : INIT_CACHE_SIZE);// Apply for new cache space and free the old one
}
else if (newOccupied <= capacity / 4 * 3) { // ️ If the number of methods currently cached +1 <= 3/4 of the cache capacity, proceed
// Cache is less than 3/4 full. Use it as-is.
}
else { // ️ If the preceding conditions are not met, the cache is full. Expand the cache
// Cache is too full. Expand it.
cache->expand();
}
// Scan for the first unused slot and insert there. // Scan the first unused slot (bucket_t) and insert it
// There is guaranteed to be an empty slot because the...
// Minimum size is 4 and we resized at 3/4 full
bucket_t *bucket = cache->find(key, receiver); // ️ a cache lookup called find() invariably yields an empty bucket_t
if (bucket->key() == 0) cache->incrementOccupied(); // ️ If bucket_t is empty, _occupied is the number of cached methods + 1
bucket->set(key, imp); // ️ add cache
}
void cache_fill(Class cls, SEL sel, IMP imp, id receiver)
{
#if ! DEBUG_TASK_THREADS
mutex_locker_t lock(cacheUpdateLock);
cache_fill_nolock(cls, sel, imp, receiver);
#else
_collecting_in_critical();
return;
#endif
}
Copy the code
2.2.3 Cache Capacity Expansion Process
- ① Set a new cache
bucket_t
, capacity = twice as old; - ② Set a new
_mask
=bucket_t
Length -1; - ③ Release the old cache (in
runtime
Dynamic swap methods also release the cache when implemented.
Mm (objc4) / / objc - cache.
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
// ️ double the size of the cache. If it is called for the first time, set the initial size of the cache to 4
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if((uint32_t)(mask_t)newCapacity ! = newCapacity) {// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity); // ️ apply for new cache space and free the old one
}
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2)
};
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity)
{
bool freeOld = canBeFreed(); // ️ determine if the cache is empty. If it is, there is no need to free space
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
assert(newCapacity > 0);
assert((uintptr_t)(mask_t)(newCapacity- 1) == newCapacity- 1);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
cache_collect_free(oldBuckets, oldCapacity);
cache_collect(false); }}bool cache_t::canBeFreed()
{
return! isConstantEmptyCache(); }bool cache_t::isConstantEmptyCache()
{
return
occupied() == 0 &&
buckets() == emptyBucketsForCapacity(capacity(), false);
}
Copy the code
- More about
cache_t
Please see:
Simple Runtime (3) : message mechanism
3. Isa pointer
isa
Pointers are used to maintain relationships between objects and classes and to ensure that objects and classes passisa
Pointers find corresponding methods, instance variables, properties, protocols, etc.- Before the ARM64 architecture,
isa
It’s just a regular pointer, pointing straight toobjc_class
That storeClass
,Meta-Class
Object memory address.instance
The object’sisa
Point to theclass
Object,class
The object’sisa
Point to themeta-class
Object; - Starting with the ARM64 architecture, yes
isa
Optimized to become a common body (union
) structure, and also uses bitfields to store more information. There are a lot of things to store in 64 bits of memory, of which 33 bits are used for storageclass
,meta-class
Object memory address information. It’s going to be done by the bit operationisa
The value of the& ISA_MASK
To obtain the maskclass
,meta-class
Object memory address.
struct objc_object {
Class isa; // Before arm64 architecture
};
struct objc_object {
private:
isa_t isa; // Start with the ARM64 architecture
};
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if SUPPORT_PACKED_ISA
// extra_rc must be the MSB-most field (so it matches carry/overflow flags)
// nonpointer must be the LSB (fixme or get rid of it)
// shiftcls must occupy the same bits that a real class pointer would
// bits + RC_ONE is equivalent to extra_rc + 1
// RC_HALF is the high bit of extra_rc (i.e. half of its range)
// future expansion:
// uintptr_t fast_rr : 1; // no r/r overrides
// uintptr_t lock : 2; // lock for atomic property, @synch
// uintptr_t extraBytes : 1; // allocated with extra bytes
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MASK 0x0000000FFFFFF8ull # define ISA_MASK 0x0000000FFFFFF8ull
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1; // 0: represents a common pointer, storing the memory address of Class and meta-class objects
// 1: indicates that it is optimized to use bitfields to store more information
uintptr_t has_assoc : 1; // Whether the associated object is set, if not, the release will be faster
uintptr_t has_cxx_dtor : 1; // whether there is a C++ destructor (.cxx_destruct), if not, the release will be faster
uintptr_t shiftcls : 33; // Store the memory address information of Class and meta-class objects
uintptr_t magic : 6; // Used to tell if an object is not initialized during debugging
uintptr_t weakly_referenced : 1; // If there is a weak reference, the release will be faster
uintptr_t deallocating : 1; // Whether the object is being released
uintptr_t has_sidetable_rc : 1; // If it is 1, the reference count is too large to be stored in ISA, and the excess reference count is stored in a RefCountMap hash table called SideTable
uintptr_t extra_rc : 19; // The value stored inside is the reference count retaincoun-1
# define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) }; };Copy the code
3.1 ISA and Superclass pointer pointing
3.2 Class and Meta-Class Objects
class
,meta-class
The underlying structure is allobjc_class
Structure,objc_class
Inherited fromobjc_object
So it also hasisa
Pointer, so it’s also an object;class
Store instance methods, member variables, attributes, protocols, etc.meta-class
Stored in class methods and other information;isa
Pointers andsuperclass
Pointer pointing (as shown above);- The base class
meta-class
thesuperclass
Points to the base classclass
, determines the property that when we call a class method, it passesclass
theisa
A pointer to findmeta-class
In themeta-class
If the method does not exist, pass themeta-class
thesuperclass
The pointer looks up the parent step by stepmeta-class
, all the way to the base classmeta-class
If you haven’t found the method, you’ll look for the base classclass
An implementation of the instance method of the same name in.
3.3 How to get a class or meta-class
- There are three ways to get a class
- (Class)class;
+ (Class)class;
Class object_getClass(id obj); // Pass parameters: instance object
Copy the code
- There is only one way to get a meta-class
Class object_getClass(id obj); // Pass parameter: Class object
Copy the code
Sample code is as follows
NSObject *object1 = [NSObject alloc] init];
NSObject *object2 = [NSObject alloc] init];
// objectClass1 ~ objectClass5 are NSObject class objects
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
// objectMetaClass1 ~ objectMetaClass4 are metaclass objects of NSObject
Class objectMetaClass1 = object_getClass([object1 class];
Class objectMetaClass2 = object_getClass([NSObject class]);
Class objectMetaClass3 = object_getClass(object_getClass(object1));
Class objectMetaClass4 = object_getClass(objectClass5);
Copy the code
Method implementation
- (Class)class {
return object_getClass(self);
}
+ (Class)class {
return self;
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
objc_object::getIsa()
{
if(! isTaggedPointer())returnISA(); . } objc_object::ISA() { assert(! isTaggedPointer());#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
#if __ARM_ARCH_7K__ >= 2
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
Copy the code
3.4 Why design meta-class?
The goal is to separate the list of related methods and build information for instances and classes so that each can perform its own duties and conform to the single responsibility design principle.
4. method_t
Method
ismethod_t
Pointer to a type;method_t
Is the encapsulation of a method/function (function four elements: function name, return value, parameter, function body).
typedef struct method_t *Method;
Copy the code
struct method_t {
SEL name; / / the method name
const char *types; // Encode (return value type, parameter type)
IMP imp; // The address/implementation of the method
};
Copy the code
4.1 SEL
- SEL, also known as a “selector,” is a point to method
selector
Pointer to, representing method/function names; - The SEL is maintained in a global Map, so it is globally unique, and the SEL is the same for methods of the same name in different classes.
typedef struct objc_selector *SEL;
Copy the code
- SEL can be obtained in the following ways
SEL sel1 = @selector(selector);
SEL sel2 = sel_registerName("selector");
SEL sel3 = NSSelectorFromString(@"selector");
Copy the code
- SEL can be converted to a string in the following ways
char *string1 = sel_getName(sel1);
NSString *string2 = NSStringFromSelector(sel1);
Copy the code
4.2 IMP
- IMP is a function pointer to a method implementation;
- We call the method, which is actually looking for IMP based on SEL;
method_t
There’s actually a mapping between SEL and IMP.
#if ! OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... * / );
#else
typedef id _Nullable (*IMP)(id_Nonnull, SEL _Nonnull, ...) ;#endif
Copy the code
4.3 Type Encodings
- The Type Encodings coding technique is coordination
runtime
The return value type and parameter type of a method are described as strings. @encode()
The directive can convert a Type to a Type Encodings string encoding, such as@encode(int)
=i
;OC
Methods take two implicit arguments, the method caller(id)self
And the method name(SEL) _cmd
So we can use it in methodsself
and_cmd
;- Such as
-(void)test
, its code is”v16@0:8
“, can be shortened to”v@:
“v
: indicates that the return value type is void@
: indicates that parameter 1 is of id type:
: indicates that the type of parameter 2 is SEL16
: indicates the total number of bytes of all parameters0
: indicates the byte from which parameter 1 is stored8
: indicates the byte from which parameter 2 is stored - The following figure shows the Type Encodings corresponding to the Type:
- Type Encodings in
runtime
Will be used in message forwarding; - For more information about Type Encodings, see the official document Type Encodings.