iOS - Objective-C Runtime Mechanism
1) Data Structure Of Objective-C instance and class object
1.1) objc_object
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
1.2) Class
typedef struct objc_class *Class;
1.3)objc_class
struct objc_class {
// points to MetaClass which stores Class method and properties
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
//pointer to Super Class
Class _Nullable super_class OBJC2_UNAVAILABLE;
//Class name
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
// size of instance object
long instance_size OBJC2_UNAVAILABLE;
// member variables info(name and type of the variables but not their values(values are stored in objc_object struct))
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
// method list (pointet to pointer)
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
// method cache list (methods called will be cached for quick calling next time)
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
1.4)objc_ivar_list
struct objc_ivar_list {
// number of variables
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
// space required for this objc_ivar_list
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
What is variable length structure:
Flexible Array Member used in C++ struct.
e.g.
typedef struct mytest{
int a;
//c does not takes space, it represents an address and has to be the last member of the struct.
char c[];
}mt;
Usage:
constchar* tmp_buf = "abcdefghijklmnopqrstuvwxyz";
intntmp_buf_size = strlen(tmp_buf);
mt* pmt = (mt*)malloc(sizeof(mt) + intntmp_buf_size + 1);
memset(pmt,0, sizeof(mt) + intntmp_buf_size + 1);
memcpy((char*)(pmt->c),tmp_buf, ntmp_buf_size);
In this way, the whole struct occupies a continutous memory space.
'free(pmt)' is enough to release all the memory.
1.5) objc_ivar
struct objc_ivar {
// name of the variable
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
// type of the variable
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
// the offset of the stored variable
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
// space needed to store this variable
int space OBJC2_UNAVAILABLE;
#endif
}
1.6) id
typedef struct objc_object *id;
In sum, the value of member variables in an instance is all store in the instance object(objc_object).
The instance methods are all stored in methodLists of the Class object(pointed by isa in the instance object struct).
The instance member variables info is all stored in ivars of the class object.
The class methods are all stored in classMethodLists of the Meta Class object(pointer by isa in the class object struct).
2) How much memory does an NSObject takes?
class_getInstanceSize(obj)
// get the size of all the member variables of the object
malloc_size((__bridge const void *)(obj))
// get the size of the object pointer by obj
Therefore, class_getInstanceSize can be taken as the actual memory space an object requires while malloc_size
is the space allocated by the system part of which is free.
Based on the above info, we can infer that system must has done something special to allocate memory space
for an object. Let’s try to find out what happens under the hood.
This is the method stack of alloc method:
alloc -> _objc_rootAlloc -> callAlloc -> class_createInstance -> _class_createInstanceFromZone
Inside _class_createInstanceFromZone method, memory align is applied:
The memory bytes allocated should be times of 16 bytes and at least 16 bytes.
Also, we should know that there is bytes align in a struct:
Each variable will take the same memory space consistent with the variable which occupies the largest memory space.
Combined what is mentioned above(memory align and struct bytes align), we can analyze NSObject example now:
Analysis
Since isa takes 8 bytes, combined with memory aligning, an NSObect is allocated 16 bytes. But it only takes 8
bytes actually, the rest 8 bytes are all 0.
One more example:
//interface
@interface Animal: NSObject{
int weight;
long num;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Animal *animal = [[Animal alloc] init];
NSLog(@"animal对象实际需要的内存大小: %zd", class_getInstanceSize([animal class]));
NSLog(@"animal对象实际分配的内存大小: %zd", malloc_size((__bridge const void *)(animal)));
}
return 0;
}
//Printint Result:
animal对象实际利用的内存大小: 24
animal对象实际占用的内存大小: 32
Analysis
Int takes 4 bytes, isa takes 8 bytes, long takes 8 bytes, due to bytes aligning in struct, an Animal instance object
takes 8 + 8 + 8 = 24 bytes, and the system allocates 32 bytes(times of 16 bytes).
3)What is class_rw_t and class_ro_t?
struct objc_class : objc_object {
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
}
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
As can be seen, data method returns the class_rw_t obj.
struct class_rw_t
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; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
cache_t
cache_t is used to cache previously called methods.
1)
struct cache_t {
struct bucket_t *_buckets; // bucket_t array
mask_t _mask; // the length of the bucket
mask_t _occupied; // the number of cached methods
};
2)
struct bucket_t {
private:
cache_key_t _key; // SEL is the Key
IMP _imp; // function pointer
};
This is how it works:
index= Hash(SEL) ----> bucket_t* bucket = buckets[index]
----> bucket._key = SEL
----> bucket._imp = IMP
class method_array_t: list_array_tt<method_t, method_list_t> -> 2D array (list of list)
class property_array_t: list_array_tt<property_t, property_list_t> -> 2D array (list of list)
class protocol_array_t: list_array_tt<protocol_ref_t, protocol_list_t> -> 2D array (list of list)
Take method_array_t as example:
class method_t
struct method_t {
SEL name; // 函数名
const char *types; // 编码(返回值类型,参数类型)
IMP imp; // 指向函数的指针(函数地址)
};
SEL
We can take SEL as char* under the hood.
Usage:
We can get SEL through the following 2 methods:
1) SEL sel1 = @selector(test);
2) SEL sel2 = sel_registerName("test");
On the other hand, we can also convert SEL to String through the following 2 methods:
1) char *string = sel_getName(sel1);
2) NSString *string2 = NSStringFromSelector(sel2);
Since SEL is identified by the String it represents, methods of the same name in different class has the same SEL which is
shown as follows:
NSLog(@"%p,%p", sel1,sel2);
Runtime-test[23738:8888825] 0x1017718a3,0x1017718a3
// As you can see, the two SEL are the same one stored in address 0x1017718a3.
Some useful methods for debugging:
//Locates and returns the address of the receiver’s implementation of a method so it can be called as a function.
- (IMP)methodForSelector:(SEL)aSelector;
// Describes the instance methods implemented by a class.
typedef struct objc_method *Method;
Method _Nonnull * class_copyMethodList(Class cls, unsigned int *outCount);
typesYou can take types as the encoded string result representing the return value as well as the parameter of the method.
IMP
IMP is an address which stores the implementation of the method.(You can take it as the function ptr). For example, Printing description of data->methods->first.imp: (IMP) imp = 0x000000010c66a4a0 (Runtime-test`-[Person testWithAge:Height:] at Person.m:13)
struct class_ro_t
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_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } };
struct class_ro_t is read-only.
4) What is the relationship between the class_rw_t obj and class_ro_t obj inside a Class object?
In the beginning, the property, member variables info, method_list and so on are all stored in ro(returned by data method),
when there is category for the class, realizeClass method will be executed to copy these info into rw.static Class realizeClass(Class cls) { runtimeLock.assertWriting(); const class_ro_t *ro; // read-only class_rw_t *rw; Class supercls; Class metacls; bool isMeta; if (!cls) return nil; if (cls->isRealized()) return cls; assert(cls == remapClass(cls)); // In the very beginnning, cls->data refers to ro ro = (const class_ro_t *)cls->data(); if (ro->flags & RO_FUTURE) { // If rw is already initialzied and allocated space rw = cls->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { // If rw is not initialzied yet rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); // Allocate space for rw rw->ro = ro; // rw->ro refers to the original ro rw->flags = RW_REALIZED|RW_REALIZING; // data method should return rw cls->setData(rw); } }
5) How does Category work?
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; // list of method_t struct method_list_t *classMethods; // list of method_t struct protocol_list_t *protocols; // list of protocol_t struct property_list_t *instanceProperties; // properties // Fields below this point are not always present on disk. struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };
instanceMethods example
classMethods example
protocols example
instanceProperties example
Pseudocode of mechanism of Category Realization
``` objc_init{
category_ t **catlist = _getObjc2CategoryList(); // obtain the category list of the class object // instance method implementation in category
if (cat-›instanceMethods || cat-›protocols || cat-›instanceProperties) {
remethodizeClass(cls);
}
// class method implementation in category
if (cat-›classMethods || cat-›protocols || (hasClassProperties && cat-›_classProperties)) {
remethodizeClass(cls->ISA());
}
}
void remethodizeClass(Class cls) {
category_list *cats = unattachedCategoriesForClass(cls, false/not realizing/);
attachCategories(cls, cats, true /flush caches/);
}
void attachCategories(Class cls, category_list *cats, bool flush_caches) {
// Allocate space for method_list, protocol_list and propeties_list
method_list_t **mlists = (method_list_t **)malloc(cats-›count * sizeof(mlists));
property_list_ t *proplists = (property_list_t **)malloc(cats-›count * sizeof(proplists));
protocol_list_t *protolists = (protocol_list_t **)malloc(cats-›count * sizeof(protolists));
int mount = 0;
int propcount = 0;
int protocount = 0;
int i = cats-›count;
bool fromBundle = NO;
// Traverse the category_list
// Count backwards through cats to get newest categories first
while (i–) {
auto& entry = cats-›list[i];
// put the method_list_t in mlists
method_list_t *mlist = entry.cat-›methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
}
// put the property_list_t in proplists
property_list_t *proplist = entry.cat-›propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// put the protocol_list_t in protolists
protocol_list_t *protolist= entry.cat-›protocols;
if (protolist) {
protolists[protocount++] = protolist;
} } // After the above traversal of catlist, all the added list has been stored in mlist, proplists, protolists respectively.
// Add all the added list in the original class rw struct
auto rw = cls-›data();
rw-›methods.attachLists(mlists,mcount);
free(mlists);
rw-›properties.attachLists(proplists,propcount);
free(proplists);
rw-›protocols.attachLists(protolists, protocount);
free(protolists);
}
// In this way, the original array will expand with addestLists added to the front of the array.
void attachLists (List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray( )) {
uint32_t oldCount = array()-›count;
uint32_t newCount = oldCount + addedCount:
setArray( (array_t *)realloc(array(), array_t: :byteSize(newCount)));
array()-›count = newCount;
// void *memmove(void *__dst, const void *__src, size_t __len) -> Move "__len" bytes from address "__src" to address "__dst"
memmove(array()-›lists + addedCount, array()-›lists,oldCount * sizeof(array()-›lists(0]));
// void *memmove(void *__dst, const void *__src, size_t __len) -> Copies "__len" bytes from address "__src" to address "__dst"
memcpy(array()-›lists, addedLists,addedCount * sizeof(array()-›lists[0])); } } ``` Based on the pseudocode, we can summarize that the class object has expanded its method_array_t, protocol_array_t as well as properties_array_t. For better comprehension, let us learn about one essential method in objc_msg_send: ``` //Through this method, the received find the corresponding IMP by SEL. getMethodNoSuper_nolock(Class cls, SEL sel) {
for (auto mlists = cls->data()->methods.beginLists(), end = cls->data()->methods.endLists(); mlists != end;++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil; } // Since the method_list_t in category is added in the beginning of method_array_t(method_list_t *), its IMP is first found. ``` 5) It is possible to add properties and member variables to class by category? ``` 1) Properties can be added to category but the setter and getter method will not be generated automatically. We can use assocation manager to achieve this. 2) Memeber variabled cannot be added by category since ivars info is fixed in the class object in compiling time. Ivars is also read-only in class_rw_t struct. ``` 6) What is the difference between **load** and **initialize** method ? **Initialize**: Called after main function mechanism: obj_send_message The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. Superclasses receive this message before their subclasses. If initialize method of subclass is not implemented, it will call super class initialize method. ``` callInitialize(Class cls) { ((void(*) (Class, SEL))objc_msgSend) (cls, SEL_initialize); } As can be seen callInitialize is through function objc_msgSend which is introduced in the above that utilizes search_method_list method to find corresponding IMP. // The order of initialize methods: if the category has implemented initialize method, only the most front initialize method in metho_array_t will be executed. ``` What should be paid attention to category is that the order of compiling sources determines the priority of method of a Class Object. The source file compiled later will be merged in the Class object later, in other words, the method of the last source file will be added to the most front position of method_array_t. **Example** ``` // In Parent.m + (void)initialize {
NSLog(@"Initialize Parent, caller Class %@", [self class]); }
// In Child.m
// Initialized method is commented.
// In main.m
Child *child = [Child new];
The printing result is :
2017-04-05 22:25:56.056 load[742:32295] Initialize Parent, caller Class Parent
2017-04-05 22:25:57.179 load[742:32295] Initialize Parent, caller Class Child
In the above example, 'Child new' sends message to Child class object. Based on Apple documentation, initialize message is sent to
Class object only once when it receives the first message in the program. Superclasses receive this message before their subclasses.
Therefore, Parent Class object receive initialize message first, then comes to the Child class. Since Child class does not implement
its own initialize method, initialize method of Parent Class is called twice.
**Load**:
Called before main function.
mechanism: function_address
If load method of subclass is not implemented, it will not call super class load method.
void call_class_loads() {
// As can be seen load_method is called through function address. (Function address of load method of Class cls).
for (i = 0; i < used; it+) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
(*load_method) (cls, SEL_load);
}
}
void call load methods(void) {
// call load method of class
call_class_loads();
// call load methods of all categories
more_categories = call_category_loads();
}
// The order of load methods: class load methods -> category load methods
7) How to add properties by using category?
This is an example of adding properties by category.
@interface Person (Fatter)
@property (nonatomic, copy) NSString *liking;
@end
const void *kPersonLikingKey; // The key of the associated object
@implementation Person (Fatter)
pragma mark - getter&setter
-
(NSString *)liking {
NSString *str = objc_getAssociatedObject(self, kPersonLikingKey);
return str;
} - (void)setLiking:(NSString *)liking {
objc_setAssociatedObject(self, kPersonLikingKey, liking, OBJC_ASSOCIATION_COPY);
}
@end![avatar](https://ririripley.github.io/assets/img/category_add_properties.png) **objc_setAssociatedObject** **objc_getAssociatedObject** objc_setAssociatedObject(self, kPersonLikingKey, liking, OBJC_ASSOCIATION_COPY);
AssociationHashMap:
{
key: id
value: ObjectAssociationMap
}
ObjectAssociationMap:
{
key: void* (key of the property, i.e. kPersonLikingKey)
value: value of the property
}
In sum, what objc_setAssociatedObject(self, kPersonLikingKey, liking, OBJC_ASSOCIATION_COPY) does is :
AssociationHashMap[self][kPersonLikingKey] = liking;
what objc_getAssociatedObject(self, kPersonLikingKey) does is :
return AssociationHashMap[self][kPersonLikingKey];6) When is the category merged into the class object ?
Before main function is executed, _objc_init method will be called. What does _objc_init method do ?
It will call the ‘remethodizeClass’ function as we mention before to merge category info into the class object. Load methods of all
class objects will also be called inside _objc_init.7) What is Class extension? What is the difference between extension and category ? **Example of Class Extension**
// CutomizedView_extension.h @interface CutomizedView ()
- (void) test;
@end
// UIView.m
#import ‘CutomizedView_extension.h’
#import ‘CutomizedView.h’
@implementation CutomizedView
- (void) test {
NSLog(@”test”);
}
@endOR
// UIView.m
@interface CutomizedView () - (void) test;
@end
@implementation CutomizedView
- (void) test {
NSLog(@”test”);
}
@end**Diff NO.1**: As can be seen, Class Extension requires modification of the .m file of the class, in other words, the source code of the Class needs to be open. Therefore, Class Extension cannot be applied in System Class while Class Category can. **Diff NO.2**: Class Extension is added to Class in compiling time while Class Category is added to class in run time. Using the command 'clang -rewrite-objc $ocFile_name -o $cpp_file_name', we can find that extension related code can be found in the project while category related code cannot. Actually, for category, the related code is added when the program begins running and executing **objc_init** method. **Diff NO.2**: Extension can add member variables while Category cannot. This is because in Class object class_rw_t struct, the ivars is read only which has been determined in compiling time. 8) What is Method Swizzling? Method swizzling is the process of changing the implementation of an existing selector. Here is an example of Method Swizzling for UIViewController Class. (Hook the system viewDidLoad method of UIViewController)
@interface UIViewController (swizzle)
@end
@implementation UIViewController (swizzle)
// FYI, load method of each Class Object will be called before main function of the program is called.
-
(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self swizzle_viewDidLoad];
});
} -
(void)swizzle_viewDidLoad {
[self exchangeMethodWithSelecter:@selector(viewDidLoad) toSelecter:@selector(swiz_viewDidLoad)];
} -
(void)swiz_viewDidLoad {
// Self-defined code you wish to execute when viewVidLoad is called.
NSLog(@”My own code”);
// After executing the self-defined code, execute the function corresponding to SEL swiz_viewDidLoad.
// Since method_exchangeImplementations exchanges the implementations of SEL ‘viewDidLoad’ and SEL ‘swiz_viewDidLoad’
// The SEL - IMP mapping is :
// viewDidLoad - > swiz_viewDidLoad
// swiz_viewDidLoad -> viewDidLoad[self swiz_viewDidLoad]; // Executing the original viewDidLoad method.
}
/**
@param origin (The original method to be replaced)
@param destination (Method to replace the original one)
*/
- (void)exchangeMethodWithSelecter:(SEL)origin toSelecter:(SEL)destination {
Method originMethod = class_getInstanceMethod(self, origin);
Method destinationMethod = class_getInstanceMethod(self, destination);
method_exchangeImplementations(originMethod, destinationMethod);
}
@end
8) How to add properties and methods to class object at runtime ?
8.1) Adding properties at runtime
Add properties by using category. (objc_getAssociatedObject and objc_setAssociatedObject)
8.2) Adding methods at runtime
Let me introduce the message sending mechanism of object-c first.
Here is an example of sending messages to an instance object.
Person *p = [Person new]; [p sayHello];
1. get isa of obj_object struct
2. isa points to the Person Class object.
3. Look up SEL 'sayHello' in Person Class object's cached_methods, if failed, then look it up in method_array_t.
4. If Step 3 succeeds, execute the IMP corresponding to SEL 'sayHello'.
Otherwise, get superClass of Person Class object which points to the super Class object of Person Class and repeat Step 3.
Step 4 will be recursively executed until finding the super Class which has the corresponding IMP.
Here is one thing to pay attention to, the trace of class method is different from intance method.
instance method: class -> super class of the class -> super class of the class' super class -> ...... -> NSObject
class method: meta class -> super class of meta class -> super class of the meta class' super class -> ...... -> NSObject
5. The upmost super class is NSObject, if the search all the way up to NSObject Class failed, there comes the **dynamically
resolving** methods step -> **Fast Forwarding** -> **Normal Forwarding**.
8.3) **Dynamically Resolving Methods**
// Source Code of Dynamically Resolving Methods
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// Instance Method Resolving
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// Class Method Resolving
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/initialize/, YES/cache/, NO/resolver/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
At runtime, when the corresponding IMP of the selector cannot be found, resolveInstanceMethod or resolveClassMethod
will be called as the second opportunity for developers to add methods. It can be notified that class method resolving involves
both **class_resolveClassMethod** and **class_resolveInstanceMethod**. It is easy to understand the function of class_resolveClassMethod
here, it adds method to the meta class.
But what is **class_resolveInstanceMethod** for?
If **class_resolveClassMethod** failed, the system will check whether the meta class has implemented **class_resolveInstanceMethod**,
if it has, it means that methods maybe add to the meta class itself. Therefore, it is necessary to check the implementation
of **class_resolveInstanceMethod** in case of missing the IMP. Usually, the struct of meta class is not attainable for developers,
but there is one exception: NSObject class. NSObject class is the super class of NSObject meta class. In other words,
NSObject class is the root class of the meta class. Therefore, if NSObject class implemented **class_resolveInstanceMethod**,
the class resolving process will search all the way up to NSObject class finally and add corresponding IMP.
Here is an example for **instance method**:
#import <objc/runtime.h>
void globalMethod() {
NSLog(@”call global method”);
}
@interface Person : NSObject
- (void) instanceMethod;
@end
@implementation Person
+(BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@”call resolveInstanceMethod”);
if (sel == @selector(instanceMethod)) {
class_addMethod(self, sel, (IMP)globalMethod, “@v:”);
}
return true;
}
@end
int main(int argc, char * argv[])
{
@autoreleasepool {
Person *person = [[Person alloc] init];
[person instanceMethod];
return 0;
}
}
The printing result is:
call resolveInstanceMethod
call global method
FYI, class_addMethod: Adds a new method to a class with a given name and implementation:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
cls : The class to which to add a method.
name: A selector that specifies the name of the method being added.
imp: A function which is the implementation of the new method. The function must take at least two arguments—self and _cmd.
As can be seen, the system will search whether Person has implemented **resolveInstanceMethod** static method, if not, then the search will
further apply to Person Class's super class, which is NSObject class here. If searching all the way up to NSObject Class failed, it means the
dynamically resolving methods failed, then the system will turn to *forwardingTargetForSelector* method, which will be introduced later.
If the system found the implementation of **resolveInstanceMethod**, then the corresponding IMP towards the selector will be added.
This time, the method searching will be successful.
As for dynamically resolving methods for **class method**, it is kind of different.
Here is an example for **class method**:
@interface Person : NSObject
- (void) classMethod;
@end
@implementation Person
+(BOOL)resolveClassMethod:(SEL)sel {
NSLog(@”call resolveClassMethod”);
if (sel == @selector(classMethod)) {
class_addMethod(objc_getMetaClass(NSStringFromClass(self).UTF8String), sel, (IMP)globalMethod, “@v:”);
}
return true;
}
@end
int main(int argc, char * argv[])
{
@autoreleasepool {
Person *person = [[Person alloc] init];
[Person classMethod];
return 0;
}
}
The printing result is:
call resolveClassMethod
call global method
The process is the same as **resolveInstanceMethod**. The system will search whether Person has implemented **resolveClassMethod** static method.
The searching will go all the way up to NSObject Class.
One more example for **class method** using **resolveInstanceMethod**:
#import <objc/runtime.h>
void globalMethod() {
NSLog(@”call global method”);
}
@implementation NSObject (instanceMethod)
- (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@”call resolveInstanceMethod”);
if (sel == @selector(classMethod)) {
class_addMethod(objc_getMetaClass(NSStringFromClass(self).UTF8String), sel, (IMP)globalMethod, “@v:”);
}
return true;
}
@end
@interface Person : NSObject
- (void) classMethod;
- (void) instanceMethod;
@end
@implementation Person
@end
int main(int argc, char * argv[])
{
@autoreleasepool {
[Person classMethod];
return 0;
}
}
The printing result is:
call resolveInstanceMethod
call global method
8.3) **Fast Forwarding**
Fast Forwarding can forward the message to the other object.
Example:
@interface Student : NSObject
+(void)classMethodTestFastForwarding:(NSString *)strValue;
-(void)instanceMethodTestFastForwarding:(NSString *)strValue;
@end
@implementation Student
-(void)instanceMethodTestFastForwarding:(NSString *)strValue {
NSLog(@”Student instanceMethodTestFastForwarding value=%@”,strValue);
}
+(void)classMethodTestFastlForwarding:(NSString *)strValue {
NSLog(@”Student classMethodTestFastForwarding value=%@”,strValue);
}
@end
@interface Person : NSObject
+(void)classMethodTestFastForwarding:(NSString *)strValue;
-(void)instanceMethodTestFastForwarding:(NSString *)strValue;
@end
@implementation Person
-(id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@”instanceMethodTestFastForwarding:”]) {
Student* student = [[Student alloc]init];
if ([student respondsToSelector:aSelector]) {
return student;
}
}
return [super forwardingTargetForSelector:aSelector];
}
+(id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@”classMethodTestFastForwarding:”]) {
if ([Student respondsToSelector:aSelector]) {
return [Student class];
}
}
return [super forwardingTargetForSelector:aSelector];
}
@end
int main(int argc, char * argv[])
{
@autoreleasepool {
Person* person = [[Person alloc] init];
[person instanceMethodTestFastForwarding: @”instanceMethodTestFastForwarding”];
[Person classMethodTestFastForwarding: @”classMethodTestFastForwarding”];
return 0;
}
}
The printint result is :
Student instanceMethodTestFastForwarding value=instanceMethodTestFastForwarding
Student classMethodTestFastForwarding value=classMethodTestFastForwarding
8.4) **Normal Forwarding**
Normal Forwarding can forward the message to more than one object.
Example:
@interface Teacher : NSObject
+(void)classMethodTestNormalForwarding:(NSString *)strValue;
-(void)instanceMethodTestNormalForwarding:(NSString *)strValue;
@end
@implementation Teacher
-(void)instanceMethodTestNormalForwarding:(NSString *)strValue {
NSLog(@”Teacher instanceMethodTestNormalForwarding value=%@”,strValue);
}
+(void)classMethodTestNormalForwarding:(NSString *)strValue {
NSLog(@”Teacher classMethodTestNormalForwarding value=%@”,strValue);
}
@end
@interface Student : NSObject
+(void)classMethodTestNormalForwarding:(NSString *)strValue;
-(void)instanceMethodTestNormalForwarding:(NSString *)strValue;
@end
@implementation Student
-(void)instanceMethodTestNormalForwarding:(NSString *)strValue {
NSLog(@”Student instanceMethodTestNormalForwarding value=%@”,strValue);
}
+(void)classMethodTestNormalForwarding:(NSString *)strValue {
NSLog(@”Student classMethodTestNormalForwarding value=%@”,strValue);
}
@end
@interface Person : NSObject
+(void)classMethodTestNormalForwarding:(NSString *)strValue;
-(void)instanceMethodTestNormalForwarding:(NSString *)strValue;
@end
@implementation Person
-(void)forwardInvocation:(NSInvocation )anInvocation {
SEL selector = anInvocation.selector;
BOOL found = FALSE;
if ([NSStringFromSelector(selector) isEqualToString:@”instanceMethodTestNormalForwarding:”]) {
Teacher teacher = [[Teacher alloc] init];
Student* student = [[Student alloc] init];
if ([teacher respondsToSelector:selector]) {
[anInvocation invokeWithTarget:teacher];
found = YES;
}
if ([student respondsToSelector:selector]){
[anInvocation invokeWithTarget:student];
found = YES;
}
// optional
if (!found) {
[self doesNotRecognizeSelector:selector];
}
} }
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature * sign = [super methodSignatureForSelector:aSelector];
if (!sign) {
sign = [NSMethodSignature signatureWithObjCTypes:”v@:@”];
}
return sign;
}
+(void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = anInvocation.selector;
BOOL found = FALSE;
if ([NSStringFromSelector(selector) isEqualToString:@”classMethodTestNormalForwarding:”]) {
if ([Teacher respondsToSelector:selector]) {
[anInvocation invokeWithTarget:[Teacher class]];
found = YES;
}
if ([Student respondsToSelector:selector]){
[anInvocation invokeWithTarget:[Student class]];
found = YES;
}
// optional
if (!found) {
[self doesNotRecognizeSelector:selector];
}
} }
+(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature * sign = [super methodSignatureForSelector:aSelector];
if (!sign) {
sign = [NSMethodSignature signatureWithObjCTypes:”v@:@”];
}
return sign;
}
@end
int main(int argc, char * argv[])
{
@autoreleasepool {
Person *person = [[Person alloc] init];
[person performSelector:@selector(instanceMethodTestNormalForwarding:) withObject:@"instanceMethodTestNormalForwarding"];
[Person performSelector:@selector(classMethodTestNormalForwarding:) withObject:@"classMethodTestNormalForwarding"];
return 0;
} } The printint result : Teacher instanceMethodTestNormalForwarding value=instanceMethodTestNormalForwarding Student instanceMethodTestNormalForwarding value=instanceMethodTestNormalForwarding Teacher classMethodTestNormalForwarding value=classMethodTestNormalForwarding Student classMethodTestNormalForwarding value=classMethodTestNormalForwarding ``` 8.5) What is **Tagged Pointer**? In 64-bit system, a pointer occupies 8 bytes while the value it stores does not require so much space. Thus, the 8 bytes memory space is divided by half. One half stores the value while the other half stores the address. The optimization can also apply to isa pointer. Half of the isa bytes can store the reference number while the other half stores the address of the class object. Such an isa pointer is called **NON_POINTER_ISA**. 9)Convert Dictionary to Object Model ``` @{ @"name" : @"Xiaoming", @"age" : @18, @"sex" : @"男" } ``` Get the ivars info of the class object, fill the value of each member variables of the class object. Ivar _Nonnull * class_copyIvarList(Class cls, unsigned int *outCount); - (void)setValue:(id)value forKeyPath:(NSString *)keyPath; ``` #import "NSObject+Model.h" #import <objc/runtime.h>
@implementation NSObject (Model)
-
(instancetype)ModelWithDict:(NSDictionary *)dict {
NSObject * obj = [[self alloc] init];
[obj transformDict:dict];
return obj;
} -
(void)transformDict:(NSDictionary *)dict {
Class cla = self.class;
// count: number of ivars
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(cla, &outCount);// traverse all the ivars and fill the value
for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
// get the name of ivar
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];// get the property name corresponding to the ivar key = [key substringFromIndex:1]; id value = dict[key]; if (value == nil) continue; // set the value of the property [self setValue:value forKeyPath:key]; } free(ivars); } ``` 10)Objects Archiving And Unarchiving Classes whose instances need to be archived and unarchived must conform to the NSCoding protocol and implement its two required methods, encodeWithCoder: and initWithCoder:. @protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (instancetype)initWithCoder:(NSCoder *)aDecoder;
@end
``` #import “Items.h”
@interface Items ()@property (nonatomic, copy) NSString *name; @property (nonatomic, copy) NSString *age; @end
@implementation Items
-
(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_name forKey:@”name”];
[aCoder encodeObject:_age forKey:@”age”];
} -
(instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
_name = [coder decodeObjectForKey:@”name”];
_age = [coder decodeObjectForKey:@”age”];
}
return self;
}
@end
@interface Person
@property (nonatomic) NSMutableArray *privateItems;
- (BOOL)saveChanges;
@end
@implementation Person
-
(NSString *)itemArchivePath
{
// Make sure that the first argument is NSDocumentDirectory
// and not NSDocumentationDirectory
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);// Get the one document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];return [documentDirectory stringByAppendingPathComponent:@”items.archive”];
} -
(void) createItem {
Items *item = [[Items alloc] init];
[self.privateItems addObject:item];
}
// Archive all the items in the file
- (BOOL)saveChanges
{
NSString *path = [self itemArchivePath];
// Returns YES on success, archiveRootObject will send encodeWithCoder messsage to each item in the privateItems array
return [NSKeyedArchiver archiveRootObject:self.privateItems toFile:path];
}
// Unarchive all the items from the file
- (instancetype)initPrivate
{
self = [super init];
if (self) {
_privateItems = [[NSMutableArray alloc] init];
NSString *path = [self itemArchivePath];
// unarchiveObjectWithFile will send initWithCoder messsage to form the _privateItems array
_privateItems = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
// If the array hadn’t been saved previously, create a new empty one
if (!_privateItems) {
_privateItems = [[NSMutableArray alloc] init];
}
}
return self;
}Instead of performing encode and decode task for each property of the object, we can utilize the runtime characteristic to optimize the code. Encode and decode can be written as follows:
(void)encodeWithCoder:(NSCoder *)aCoder{
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSString * key = [NSString stringWithUTF8String:name];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
} - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if (self) {
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSString * key = [NSString stringWithUTF8String:name];
id value = [aDecoder decodeObjectForKey:key];
[self setValue:value forKey:key];
}
}
return self;
}11)What is the difference between 'calling a method' and 'sending a message' ? Calling a method:
Function calls are really just jumping to a certain spot in memory and executing code. There’s no dynamic behavior involved.
Sending a message:
Objects can choose to not respond to messages, or forward messages on to different objects, or whatever. More details are entailed
in objc_msgSend() function.12)Runtime Methods Cache To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used. The mechanism is entailed in the **cache_t** struct introduced in section **(1.6) id**. 13) Type Encoding The Objective-C compiler generates type encodings for all the types.
- For example,
- -(void)hello:(NSString*)value; —–> “v@:@”
- v : return value is void
- @ : receiver (type: id)
- : SEL
@ : parameter (type: id)
``` 14)Multiple-inheritance
Objective-C doesn’t support multiple inheritance. Use composition or protocols.
15)Mechanism Of Perform Selector
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2 {
IMP msg;
if (aSelector == 0) {
[NSException raise: NSInvalidArgumentException format: @"%@ null selector given", NSStringFromSelector(_cmd)];
}
// find the coreresponding IMP of selector
msg = objc_msg_lookup(self, aSelector);
if (!msg) {
[NSException raise: NSGenericException format: @"invalid selector '%s' passed to %s", sel_getName(aSelector), sel_getName(_cmd)];
return nil;
}
return (*msg)(self, aSelector, object1, object2);
}
16)Runtime Get Class Related Method
Class object_getClass(id obj);
The class object of which object is an instance, or Nil if object is nil.
Class object_getClass(id obj)
{
if (obj)
return obj->getIsa();
else
return Nil;
}
id objc_getClass(const char *name);
return the Class object for the named class, or nil if the class is not registered with the Objective-C runtime.
- (Class)class;
```
//Returns the class object. - (Class)class {
return self;
}
``` - (Class)class;
```
Returns the class object for the receiver’s class. - (Class)class {
return object_getClass(self);
}id objc_getMetaClass(const char *name);
Returns the metaclass definition of a specified class.
```Reference
https://cloud.tencent.com/developer/article/1156752
https://www.jianshu.com/p/fa66c8be42a2
https://www.jianshu.com/p/51e4c117d596
https://anyeler.top/2018/07/29/Category%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/
https://cloud.tencent.com/developer/article/1391529
https://www.jianshu.com/p/ab5d7c81b810
https://www.incredibuild.com/integrations/clang
https://juejin.cn/post/6890350559128190983#heading-5
https://cloud.tencent.com/developer/article/1799493
https://juejin.cn/post/7029343711112724517
https://blog.csdn.net/u010105969/article/details/62233752
https://davedelong.tumblr.com/post/58428190187/an-observation-on-objective-c
https://www.jianshu.com/p/6fb4641e6ec5
https://stackoverflow.com/questions/4192203/objective-c-multiple-inheritance https://blog.chenyalun.com/2018/09/30/PerformSelector%E5%8E%9F%E7%90%86/