5, 000 words, 30 minutes expected reading timeCopy the code

Main references:

  1. IOS Runtime (Runtime) +Demo
  2. Objective-C Runtime
  3. Objective C Runtime discharge day 3 – How to use Runtime correctly

Run-time Introduction

Objective-c is a dynamic language that takes a lot of the work that static languages do at compile and link time and puts it into runtime. To Objective-C, this runtime system is like an operating system: it makes everything work. Runtime is basically written in C and assembly, a library that brings object-oriented capabilities to C. In Runtime, objects can be represented as C constructs, and methods can be implemented as C functions, plus some additional features. These constructs and functions, encapsulated by the Runtime functions, make object-oriented programming of OC possible. Find the final execution code of the method: When the program executes [Object doSomething], it sends a message to the message receiver (Object doSomething), and the Runtime reacts differently depending on whether the message receiver can respond to the message.

Basic data structures for classes and objects

The Objective-C Class is represented by the Class type, which is actually a pointer to the objc_class structure.

typedef struct object_class *Class
Copy the code

It is defined as follows:

struct object_class{ Class isa OBJC_ISA_AVAILABILITY; #if ! __OBJC2__ Class super_class OBJC2_UNAVAILABLE; // Parent class const char *name OBJC2_UNAVAILABLE; // Class name long version OBJC2_UNAVAILABLE; // Class version information, default is 0 long info OBJC2_UNAVAILABLE; // Class information, some bit identifier for run-time use long instance_size OBJC2_UNAVAILABLE; Struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; Struct objc_method_list *methodLists OBJC2_UNAVAILABLE; Struct objc_cache *cache OBJC2_UNAVAILABLE; Struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // Protocol list #endif}OBJC2_UNAVAILABLE;Copy the code

Objc_object is a structure that represents an instance of a class. It is defined as follows:

struct objc_object{
     Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
Copy the code

As you can see, this structure has only one font, the ISA pointer to its class. This way, when we send a message to an Objective-C object, the runtime library finds the class to which the instance object belongs based on the isa pointer to the instance object. The Runtime library looks in the class’s method list and the parent class’s method list for the method to which the corresponding selector points, and then runs that method.

A meta-class is a Class of a Class object. Above we mentioned that all classes are themselves objects to which we can send messages (that is, call class methods). Since it is an object, it is also an objc_Object pointer that contains an ISA pointer to its class. So what does this ISA pointer point to?

The answer is that in order to call class methods, the isa pointer to the class must point to an objC_class structure that contains those class methods. This leads to the concept of a meta-class, where all the class methods of a class are stored.

So, the isa pointer to the class object that calls the class method is meta-class and when we send a message to an object, the Runtime looks for methods in the list of methods of the class that the object belongs to; When you send a message to a class, you look it up in the list of methods in that class’s Meta-class.

A meta-class is also a class and can send a message to it. What does its ISA point to? To prevent this structure from going on indefinitely, the objective-C designers made all the IsAs of meta-classes point to the meta-class of the base class as their parent class.

That is, any meta-class inherited from NSObject uses the meta-class of NSObject as its parent class, and the isa pointer to the meta-class of the base class points to itself.

With the above description and an analysis of the super_class pointer in the objc_class structure, we can draw a class and corresponding meta-class inheritance system as follows:

A Category is a pointer to a classification structure. It is defined as follows:

typedef struct objc_category *Category struct objc_category{ char *category_name OBJC2_UNAVAILABLE; // Category name char *class_name OBJC2_UNAVAILABLE; Struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; Struct objc_method_list *class_methods OBJC2_UNAVAILABLE; Struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // List of protocols implemented by categorization}Copy the code

This structure mainly contains instance methods and class methods defined by the classification, where the instance_methods list is a subset of the method list in objc_class and the class_methods list is a subset of the metaclass method list. You can see that there is no ivAR member variable pointer in the category, which means that instance variables and attributes cannot be added to the category. Unless associated objects are used, only setter and getter methods, not member variables, are generated for attributes in a Category

#import "UIButton+ClickBlock.h" #import static const void *associatedKey = "associatedKey"; @implementation UIButton (ClickBlock) //Category properties, just generate setter and getter methods, -(void)setClick:(clickBlock)click{objc_setAssociatedObject(self, associatedKey, click, OBJC_ASSOCIATION_COPY_NONATOMIC); [self removeTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside]; if (click) { [self addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside]; } } -(clickBlock)click{ return objc_getAssociatedObject(self, associatedKey); } -(void)buttonClick{ if (self.click) { self.click(); } } @endCopy the code

Then, in code, you can use UIButton properties to listen for click events:

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = self.view.bounds;
[self.view addSubview:button];
button.click = ^{
    NSLog(@"buttonClicked");
};
Copy the code

Use of associated objects

1. Set the associated values. Parameter description: Object: associated with who, usually pass self key: unique key, when obtaining a value, usually use static const void * to declare value: associated with the set value policy: Memory management policies, such as using copy void objc_setAssociatedObject(ID object, const void *key, ID value, objC _AssociationPolicy policy)Copy the code
Parameter description: object: with whom to associate, usually pass self, when setting the association of the specified object with which the key of the object: Objc_getAssociatedObject (id object, const void *key)Copy the code
3. Disassociate void objc_removeAssociatedObjects(ID object)Copy the code
Application scenarios of association policies: Uintptr_t, objc_AssociationPolicy){OBJC_ASSOCIATION_ASSIGN = 0, Usually the basic data type OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // stands for strong reference to the associated object, is thread-safe OBJC_ASSOCIATION_COPY_NONATOMIC = 3, // stands for copy of the associated object, OBJC_ASSOCIATION_COPY = 01403; OBJC_ASSOCIATION_RETAIN = 01401; OBJC_ASSOCIATION_COPY = 01403;Copy the code

Methods and messages

1, SEL

SEL, also known as a selector, is a pointer to a method’s selector, which is defined as follows:

Typedef struct objc_selector *SEL;Copy the code

Method selector is used to represent the name of the runtime method. During compilation, Objective-C generates a unique integer identifier (address of type Int) based on the name and parameter sequence of each method. This identifier is SEL. Between two classes, as long as the method name is the same, the SEL of the method is the same, and each method corresponds to an SEL. So in Objective-C, you can’t have two methods with the same name in the same class (and its inheritance system), even if the argument type is different

- (void)setWidth:(int)width;
- (void)setWidth:(double)width;
Copy the code

Of course, different classes can have the same selector, and that’s fine. When instance objects of different classes perform the same selector, they will find their corresponding IMP according to the selector in their respective method list. Engineering of all of the SEL a Set the Set, if we think of this method in the collection to find a method, just need to find the way to the corresponding SEL, SEL is actually according to the method name hash of a string, and for the string comparison only need to compare their address is ok, It can be said that the speed is incomparable! Essentially, an SEL is just a pointer to a method (specifically, a KEY that is hashed based on the method name and uniquely represents a method), and it exists only to speed up method queries.

SEL can be obtained using three methods: a, sel_registerName function B, @selector() provided by the Objective-C compiler C, and NSSelectorFromString()Copy the code

2, IMP

IMP is actually a function pointer to the address of the method implementation. Its definition is as follows:

id (*IMP)(id, SEL,...)
Copy the code

The first argument is a pointer to self (in the case of an instance method, the memory address of the class instance; A pointer to a metaclass if it is a class method. The second argument is the method selector, followed by a list of the method’s arguments.

The SEL described earlier is to find the method that ultimately implements IMP. Since each method corresponds to a unique SEL, we can easily and accurately obtain its corresponding IMP through SEL. The search process will be discussed below. After obtaining IMP, we have the entry point to execute the method code, at this point, we can call the function pointer as normal C language function.

3, Method

Method is used to represent a Method in a class definition.

typedef struct objc_method *Method struct objc_method{ SEL method_name OBJC2_UNAVAILABLE; // Method name char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; // method implementation}Copy the code

We can see that the structure contains a SEL and an IMP, which actually represents a mapping between SEL and IMP. With SEL, we can find the corresponding IMP to invoke the method’s implementation code.

4, message,

Messages are sent in Objc with brackets ([]) around the receiver and message, and messages are not bound to method implementations until runtime.

For details about message sending and message forwarding mechanisms, seeThis article.


There are a lot of apis in Cocoa, and you just look up documents and make calls. Remember when you first learned Objective-C, you thought of [Receiver Message] as a simple method call, ignoring the profound meaning of “send a message.” In fact, [receiver Message] is converted by the compiler to:

objc_msgSend(receiver, selector)
Copy the code

If the message contains parameters, then:

objc_msgSend(receiver, selector, arg1, arg2, ...)
Copy the code

If the receiver of the message can find the corresponding selector, then the specific method of the receiver object is executed directly; Otherwise, the message is either forwarded, or the corresponding implementation of the selector is temporarily added to the recipient dynamically, or it simply crashes.

You can now see that [Receiver Message] is really not a simple method call. Since it is only at compile time that the message is determined to be sent to the receiver, how the receiver will respond to the message depends on what happens at runtime.

It looks like objc_msgSend is returning data, but objc_msgSend never returns data but your method is called and returns data. The following steps are described in detail:

1. Check whether the selector is to be ignored. Mac OS X development, for example, has garbage collection and ignores functions like retain and release. 2. Check if this target is nil. The property of ObjC is to allow any method to be executed on a nil object without crashing because it will be ignored. 3, if the above two have passed, then start to find the IMP of this class, first from the cache to find, then jump to the corresponding function to execute. 4, if the cache can not find a way to find published. 5. If you can't find the subtable, go to the subtable of the superclass and keep looking until you find the NSObject class. 6. If you can't find it, start dynamic method parsing, which will be discussed later. The method selector is associated with the method implementation address.Copy the code

The compiler will call one of objc_msgSend, objc_msgSend_stret, objc_msgSendSuper, or objc_msgSendSuper_stret, depending on the situation. If the message is passed to a superclass, the function with the name “Super” is called; If the message returns a data structure rather than a simple value, the function with the name “Stret” is called. Permutation and combination are exactly four ways.

The objc_msgSend_fpret function is used to process messages that return floating-point numbers. This is because the ABI(Application Binary Interface) corresponding to functions that return floating-point numbers is incompatible with the ABI of functions that return integers. At this point objc_msgSend is no longer applicable, so objc_msgSend_fpret is used to do special processing to the floating-point register. You don’t need to bother with PPC or PPC64.

PS: Have you noticed the naming rules of these functions? “Super” is the message passed to the superclass; “Stret” can be divided into “ST” + “RET”, representing “struct” and “return” respectively. “Fpret” is “FP” + “ret”, which stands for “floating-point” and “return” respectively.

5. Dynamic method analysis

You can dynamically provide an implementation of a method. For example, we can use the @dynamic keyword to decorate a property in the class’s implementation file:

@dynamic propertyName;
Copy the code

This means that we will provide access to this property dynamically, which means that the compiler will no longer generate setPropertyName: and propertyName methods for us by default, but will need us to provide them dynamically. We can add instance method implementations and class method implementations by overloading the resolveInstanceMethod: and resolveClassMethod: methods, respectively. Because the Runtime system cannot find a method to execute in the Cache and method table (including superclasses), it calls resolveInstanceMethod: or resolveClassMethod: to give the programmer a chance to dynamically add method implementations. We need to add a specific method implementation to a specific class using the class_addMethod function:

void dynamicMethodIMP(id self, SEL _cmd) { // implementation .... } @implementation MyClass + (BOOL)resolveInstanceMethod:(SEL)aSEL { if (aSEL == @selector(resolveThisMethodDynamically))  { class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSEL]; } @endCopy the code

The above example for resolveThisMethodDynamically method to add the implementation content, namely dynamicMethodIMP method with the code. Where “v@:” represents the return value and argument, and this symbol refers to Type Encoding

PS: Dynamic method resolution is performed before the message forwarding mechanism is immersed. If respondsToSelector: or instancesRespondToSelector: method is executed, the dynamic method the parser will be the first to provide the method to give a selector corresponding IMP. If you want the method selector to be passed to the forward mechanism, then resolveInstanceMethod: returns NO.

Note: For example, parse class methods:.h

#import <Foundation/Foundation.h>

@interface Student : NSObject
+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;
@end
Copy the code

.m

#import "Student.h"
#import <objc/runtime.h>

@implementation Student
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(learnClass:)) {
        class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(myClassMethod:)), "v@:");
        return YES;
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(goToSchool:)) {
        class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

+ (void)myClassMethod:(NSString *)string {
    NSLog(@"myClassMethod = %@", string);
}

- (void)myInstanceMethod:(NSString *)string {
    NSLog(@"myInstanceMethod = %@", string);
}
@end
Copy the code

Object_getClass (self) and object_getClass([self class]).

[self class] is equivalent to object_getClass(self) when self is an instance object, because the former calls the latter. Object_getClass ([self class]) gets the metaclass. When self is a class object, [self class] returns self, or self. Object_getClass (self) is equivalent to object_getClass([self class]).Copy the code

6. Message forwarding

redirect

Before the message forwarding mechanism implementation, the Runtime system will give us the opportunity to once removed, by overloading – (id) forwardingTargetForSelector: (SEL) aSelector method to replace the message receiver for other objects:

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if(aSelector == @selector(mysteriousMethod:)){
        return alternateObject;
    }
    return [super forwardingTargetForSelector:aSelector];
}
Copy the code

Since message forwarding takes more time, it’s a good idea to use this opportunity to redirect the message to someone else. If this method returns nil or self, it goes to the forwardInvocation; Otherwise the message is resend to the returned object.

If you want to replace the class methods recipient, need to overwrite the + (id) forwardingTargetForSelector (SEL) aSelector method, and returns the class object:

+ (id)forwardingTargetForSelector:(SEL)aSelector {
	if(aSelector == @selector(xxx)) {
		return NSClassFromString(@"Class name");
	}
	return [super forwardingTargetForSelector:aSelector];
}
Copy the code

forwarding

Message forwarding is triggered when dynamic method resolution returns NO without processing. The forwardInvocation: method is invoked at this point and we can override this method to define our forward logic:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    if ([someOtherObject respondsToSelector:
            [anInvocation selector]])
        [anInvocation invokeWithTarget:someOtherObject];
    else
        [super forwardInvocation:anInvocation];
}
Copy the code

The only argument to the message is an NSInvocation object, which encapsulates the original message and the message’s parameters. We can implement the forwardInvocation: method to do some default processing for messages that cannot be processed, or to forward the message to another object for processing without throwing an error.

The thing to note here is where the anInvocation comes from? Actually in forwardInvocation: before the message is sent, the Runtime system will send object methodSignatureForSelector: news, and to return to the method signature is used to generate NSInvocation object. So we rewrite forwardInvocation: at the same time also to rewrite methodSignatureForSelector: method, otherwise will be throw exceptions.

When an object cannot respond to a message because there is no method implementation, the runtime system notifies the object via forwardInvocation: message. Each object inherits the forwardInvocation: method from the NSObject class. Method of NSObject, however, simply call the doesNotRecognizeSelector:. By implementing our own forwardInvocation: method, we can forward messages to other objects in this method implementation.

ForwardInvocation: the method is like a distribution center for unrecognized messages that are forwarded to different receiving objects. Or it can act like a transport station and send all messages to the same receiving object. It can translate one message into another, or simply “eat” some messages, so there are no responses and no errors. The forwardInvocation method can also provide the same response for different messages, depending on how the method is implemented. What this method provides is the ability to link different objects to a message chain.

Note: forwardInvocation: the method is only called if the message cannot be properly responded to in the message receiving object. So, if we want an object to forward the negotiate message to other objects, that object cannot have an negotiate method. Otherwise, forwardInvocation: will not be called.

Forwarding and multiple inheritance

Forwarding is similar to inheritance and can be used to add some multi-inheritance effects to Objc programming. As shown in the figure below, an object forwards messages as if it borrowed or “inherited” methods from another object.

This allows two classes in branches of different inheritance systems to “inherit” each other’s methods. In the figure above, Warrior and Diplomat have no inheritance relationship, but Warrior forwards the Negotiate message to Diplomat as if Diplomat were a superclass of Warrior.

Message forwarding compensates for Objc’s lack of support for multiple inheritance and avoids the bloated complexity of a single class caused by multiple inheritance. It breaks the problem down to a fine-grained level, forwards it only for the methods it wants to borrow, and the forwarding mechanism is transparent.

Forwarding and Inheritance

Although forwarding is a lot like inheritance, the NSObject class does not confuse the two. Methods like Respondto Selector and isKindOfClass only take into account inheritance, not forward chains. For example, a Warrior object in the figure above is asked if it can respond to the Negotiate message:

if ( [aWarrior respondsToSelector:@selector(negotiate)] )
    ...
Copy the code

The result is NO, although it is able to accept the Negotiate message without an error because it responds to the message by forwarding it to the Diplomat class.

If you want to “fiddle” for some purpose by making someone think Warrior has inherited Diplomat’s negotiate method, you need to re-implement respondsToSelector: and isKindOfClass: to add your forwarding algorithm:

- (BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it.  Return YES if it can. */ } return NO; }Copy the code

In addition to respondsToSelector: and isKindOfClass: instancesRespondToSelector: should also be written in a forwarding algorithm. If a protocol is used, conformto Protocol: also participates. Similarly, if an object forward it accepts any remote message, it must give a methodSignatureForSelector: to return accurate description methods, this method will be forwarded message final response. Such as an object to its replacement object forwarding messages, it needs as follows for methodSignatureForSelector: :

- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector { NSMethodSignature* signature = [super methodSignatureForSelector:selector]; if (! signature) { signature = [surrogate methodSignatureForSelector:selector]; } return signature; }Copy the code

7. Method Swizzling

The message forwarding mentioned above, while powerful, requires us to understand and be able to change the source code of the corresponding class because we need to implement our own forwarding logic. What if we don’t have access to the source code of a class, but want to change the implementation of a method of that class? It might be an idea to inherit class override methods, but sometimes that doesn’t cut it. This is Method Swizzling, which does this by remapping the corresponding implementation of a Method. Compared to message forwarding, Method Swizzling’s approach is more subtle, even risky, and makes debugging more difficult.

Swizzling's principle is to call a method in Objective-C, and essentially send a message to an object, and the only way to find that message is by the name of the selector. So, we can use the objective-c runtime mechanism to swap selector implementations at runtime to do that. Each class has a list of methods that hold the name of the selector and the mapping between the method implementation. IMP is a bit like a function pointer to a specific Method implementationCopy the code

This is code written by Mattt himself in his NSHipster article.

#import <objc/runtime.h> @implementation UIViewController (Tracking) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class aClass = [self class]; // When swizzling a class method, use the following: // Class aClass = object_getClass((id)self); SEL originalSelector = @selector(viewWillAppear:); SEL swizzledSelector = @selector(xxx_viewWillAppear:); Method originalMethod = class_getInstanceMethod(aClass, originalSelector); Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector); BOOL didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); }}); } #pragma mark - Method Swizzling - (void)xxx_viewWillAppear:(BOOL)animated { [self xxx_viewWillAppear:animated]; NSLog(@"viewWillAppear: %@", self); } @endCopy the code

The above code swaps the viewWillAppear: method of the UIViewController class with the implementation of the xxx_viewWillAppear: method of the Tracking class by adding a Tracking class to the UIViewController class. Swizzling should be implemented in the +load method because +load is called when a class is first loaded. Dispatch_once is a method in GCD that ensures that a block of code is executed only once and makes it an atomic operation. Thread-safety is important.

If there are no methods to replace, add and replace the implementation of the two methods with the class_addMethod and class_replaceMethod functions. If there is already a Method in the class that you want to replace, then we call method_exchangeImplementations which swaps the IMP of the two methods, which is a convenient way That Apple gives us to implement Method Swizzling.

Some of you may have noticed this line:

// When swizzling a class method, use the following:
// Class aClass = object_getClass((id)self);
// ...
// Method originalMethod = class_getClassMethod(aClass, originalSelector);
// Method swizzledMethod = class_getClassMethod(aClass, swizzledSelector);

Copy the code

Object_getClass ((id)self) and [self class] both return a result type of class, but the former is a metaclass and the latter is itself, because self is a class and not an instance. Note the difference between [NSObject Class] and [object Class] :

+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}
Copy the code

PS: class_replaceMethod is equivalent to calling class_addMethod to add an implementation of the method to the class if there is no original method that you want to replace. Otherwise, the types parameter is ignored when the method_setImplementation method is called. Method_exchangeImplementations the method does something equivalent to the atomic operation:

IMP imp1 = method_getImplementation(m1);
IMP imp2 = method_getImplementation(m2);
method_setImplementation(m1, imp2);
method_setImplementation(m2, imp1);

Copy the code

Finally xxx_viewWillAppear: The method definition looks like a recursive call causes an endless loop, but it doesn’t. Because the [self xxx_viewWillAppear:animated] message dynamically finds the implementation of the xxx_viewWillAppear: method, whose implementation we’ve swapped with the viewWillAppear: method implementation, this code not only doesn’t loop, If you replace [self xxx_viewWillAppear:animated] with [self viewWillAppear:animated] it will trigger an endless loop. The +load method is inherently thread-safe because it is called at the beginning of the program and rarely encounters concurrency problems.

1, iOS- three implementations of UIButton to prevent repeated clicks 2, Swift Runtime analysis: still like OC Runtime?