The problem first

1. What are the Runtime applications? What is the difference between AOP and OOP?

Runtime

Objective-c is a dynamic language, which means it requires not only a compiler, but also a Runtime system to dynamically create classes and objects, do messaging and forwarding. Runtime is the Runtime system, written basically in C and assembly.

High-level programming language to be executable files need to be compiled to first assembly language to assembly language for the machines, machine language is also a computer can recognize the only language, but OC is not directly compiled to assembly language, but need to transfer is pure C language to compile and assembly operations, from OC to the transition of the C language is implemented by the runtime. However, we use OC for object-oriented development, while C is more process-oriented development, which requires the transformation of object-oriented classes into process-oriented structures.

SEL(objc_selector)

Objc.h /// An opaque type that represents a method selector. Typedef struct objc_selector *SEL;Copy the code
A method selector is A C string that has been registered (or "mapped") with the Objective-C runtime. Selectors generated  by the compiler are automatically mapped by the runtime when the class is loaded.Copy the code

A selector is just a C string mapped to a method, and you can get an SEL method selector using the objective-C compiler command @selector() or the Runtime sel_registerName function. If a selector is a string, it should be a combination of className and method. If a selector is of the same class, it cannot be repeated. If a selector is of a different class, it can be repeated

And one of the drawbacks of this is that when we’re writing C code, we often use function overloading, where the function has the same name and different arguments, but that doesn’t work in Objective-C, because the selector only remembers the name of the method, it doesn’t have any arguments, so you can’t tell one method from another.

IMP

/// A pointer to the function of a method implementation. Typedef id (*IMP)(id, SEL,...) ;Copy the code

Is a pointer to the memory address of the final implementation. In iOS Runtime, Method achieves quick query Method and implementation through selector and IMP properties, relatively improving performance and maintaining flexibility.

The Runtime application

Objective-c Associated Objects add instance variables to a classification

So let’s implement a Category of UIView and add our custom property defaultColor

#import "ViewController.h" #import "objc/runtime.h" @interface UIView (DefaultColor) @property (nonatomic, strong) UIColor *defaultColor; @end @implementation UIView (DefaultColor) @dynamic defaultColor; static char kDefaultColorKey; - (void)setDefaultColor:(UIColor *)defaultColor { objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)defaultColor { return objc_getAssociatedObject(self, &kDefaultColorKey); } @end @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. UIView *test = [UIView new]; test.defaultColor = [UIColor blackColor]; NSLog(@"%@", test.defaultColor); } @end /// Print result: 2018-04-01 15:41:44.977732+0800 OCram [2053:63739] UIExtendedGrayColorSpace 01Copy the code

Method Swizzling adds and replaces and KVO implementations

Let’s implement an example of a viewDidLoad method that replaces a ViewController.

@implementation ViewController + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class = [self class]; SEL originalSelector = @selector(viewDidLoad); SEL swizzledSelector = @selector(jkviewDidLoad); Method originalMethod = class_getInstanceMethod(class,originalSelector); Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector); // Add IMP to SEL BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); If (didAddMethod) {// Add successfully: Note The source SEL does not implement IMP. Replace the IMP of the source SEL with the IMP class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod) of the exchange SEL, method_getTypeEncoding(originalMethod)); Method_exchangeImplementations (originalMethod, swizzledMethod);} else {// Implementations implementations imPs. }}); } - (void)jkviewDidLoad {NSLog(@" replace method "); [self jkviewDidLoad]; } - (void)viewDidLoad {NSLog(@" native method "); [super viewDidLoad]; } @endCopy the code

Swizzling should only be done in +load. In the Objective-C runtime, each class has two methods that are called automatically. +load is called when a class is initially loaded, and + Initialize is called before applying the class method or instance method that first called the class. Both methods are optional and will only be called if the method is implemented.

Swizzling should only be done in Dispatch_once, since swizzling changes the global state, we need to make sure that every precaution is available at run time. Atomic operations are one such precaution to ensure that code is executed only once, even in different threads. Grand Central Dispatch’s Dispatch_once meets the required requirements and should be used as a standard for initializing the singleton method using Swizzling. The implementation diagram is shown below.

KVO implementation

The full name is key-value observing, which translates into key-value observing. Provides a mechanism for notifying the current object when other object properties are modified. In Cocoa, where MVC was the norm, the KVO mechanism was well suited for communicating between the Model and Controller classes.

The implementation of KVO relies on objective-C’s powerful Runtime. When viewing object A, KVO dynamically creates A subclass of object A’s current class and overrides the setter method for the observed property keyPath for the new subclass. Setter methods are then responsible for notifying the observed object of changes to its properties.

Apple used ISa-Swizzling to implement KVO. When observing object A, the KVO mechanism dynamically creates A new object named: NSKVONotifying_A is A new class that inherits from object A, and KVO is the setter method for NSKVONotifying_A to override the observed property. The setter method is responsible for notifying all observed object property values of changes before and after the original setter method is called.

  • NSKVONotifying_AClass analysis
NSLog(@"self->isa:%@",self->isa);
NSLog(@"self class:%@",[self class]);
Copy the code

Before establishing KVO monitor, print the following result:

self->isa:A
self class:A
Copy the code

After the KVO listener is established, the print result is:

self->isa:NSKVONotifying_A
self class:A
Copy the code

In this process, the isa pointer of the observed object is changed from pointing to the original class A by the KVO mechanism to pointing to the newly created subclass NSKVONotifying_A to realize the monitoring of the change of the current class attribute value. So when we look at the application level, we are completely unaware that there is a new class, this is the system “hiding” the underlying implementation of KVO, making us think it is the original class. However, if we create a new class named “NSKVONotifying_A”, we will find that the program will crash when the system runs to register the KVO code, because the system dynamically created a middle class named NSKVONotifying_A, and pointed to this middle class.

  • A subclasssetterMethods analyze the

KVO’s key-value observation notifications depend on NSObject methods willChangeValueForKey: and didChangeValueForKey:, which are called before and after a value is stored: Before the observed property changes, willChangeValueForKey: is called to inform the system that the value of the keyPath property is about to change; When the change occurs, didChangeValueForKey: is called to inform the system that the keyPath property value has changed; After observeValueForKey: ofObject: change: context: will be invoked. And override setter methods for observing properties. This inheritance injection is implemented at run time, not compile time.

KVO calls access methods for the observer property override of subclasses.

- (void)setName:(NSString *)newName { [self willChangeValueForKey:@"name"]; //KVO always calls [super setValue:newName forKey:@"name"] before calling the accessor method; // Call the accessor method of the parent class [self didChangeValueForKey:@"name"]; //KVO always calls} after calling the accessor methodCopy the code

Message forwarding (Hot update) Fixes bugs (JSPatch)

JSPatch is an iOS dynamic update framework that allows you to use JavaScript to call any objective-C native interface to get the benefits of scripting: dynamically adding modules to your project, or dynamically fixing bugs by replacing project native code, simply by introducing a minimal engine into your project.

As for message forwarding, it has been mentioned above that message forwarding is divided into three levels. We can implement substitution function at each level to realize message forwarding without causing a crash. JSPatch can not only forward messages, but also add and replace methods.

NSCoding automatic archiving and automatic file solution

Principle description: Use the functions provided by Runtime to traverse all attributes of Model, and encode and decode the attributes. Core methods: Override methods in Model base classes:

- (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { unsigned int outCount; Ivar * ivars = class_copyIvarList([self class], &outCount); for (int i = 0; i < outCount; i ++) { Ivar ivar = ivars[i]; NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)]; [self setValue:[aDecoder decodeObjectForKey:key] forKey:key]; } } return self; } - (void)encodeWithCoder:(NSCoder *)aCoder { unsigned int outCount; Ivar * ivars = class_copyIvarList([self class], &outCount); for (int i = 0; i < outCount; i ++) { Ivar ivar = ivars[i]; NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)]; [aCoder encodeObject:[self valueForKey:key] forKey:key]; }}Copy the code

Implement automatic transformation of dictionaries and models (MJExtension)

Principle description: Use the functions provided by Runtime to traverse all the attributes of Model. If the attributes have corresponding values in JSON, the values will be assigned. Core methods: Add methods to the class of NSObject

- (instancetype)initWithDict:(NSDictionary *)dict {if (self = [self init]) {//(1) obtain class attributes and their corresponding type NSMutableArray * keys = [NSMutableArray array]; NSMutableArray * attributes = [NSMutableArray array]; /* * Example * name = value3 attribute = T@"NSString",C,N,V_value3 * name = value4 Attribute = T^ I,N,V_value4 */ unsigned int  outCount; objc_property_t * properties = class_copyPropertyList([self class], &outCount); for (int i = 0; i < outCount; i ++) { objc_property_t property = properties[i]; / / get the name of the property by property_getName function nsstrings * propertyName = [nsstrings stringWithCString: property_getName (property) encoding:NSUTF8StringEncoding]; [keys addObject:propertyName]; // The property_getAttributes function gets the name of the property and the @encode encoding NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding]; [attributes addObject:propertyAttribute]; } // Free the memory pointed to by properties immediately; For (NSString * key in keys) {if ([dict valueForKey:key] == nil) continue; // if ([dict valueForKey:key] == nil) continue; [self setValue:[dict valueForKey:key] forKey:key]; } } return self; }Copy the code

AOP,OOP

OOP (object-oriented programming) Abstracts and encapsulates the entities, attributes and behaviors of business processes to achieve a clearer and more efficient division of logical units. AOP: Aspect Oriented Programming, translated into the section Oriented Programming, is through the way of pre-compilation and runtime dynamic implementation, without modifying the source code, to the program dynamic unified add function technology.

Method Swizzle is probably the first thing that comes to mind for AOP, but here’s a look at some of the major AOP frameworks.

Aspects is a lightweight AOP library on iOS. It makes use of the Method Swizzling technique to add extra code to an existing class or instance Method.

/// Adds a block of code before/instead/after the current `selector` for a specific class. /// /// @param block Aspects replicates the type signature of the method being hooked. /// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method. /// These parameters are optional and will be filled to match the block signature. /// You can even use an empty block, or one that simple gets `id<AspectInfo>`. /// /// @note Hooking static methods is not supported. /// @return A token which allows to later deregister the aspect. + (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; /// Adds a block of code before/instead/after the current `selector` for a specific instance. - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; /// Deregister an aspect. /// @return YES if deregistration is successful, otherwise NO. id<AspectToken> aspect = ... ; [aspect remove];Copy the code

AspectsTwo are providedAOPMethod, one for the class and one for the instance. In determining thehookAfter that,AspectsAllow us to choosehookIs it before or after the method is executed, or can even replace the method’s implementation directly.

It’s not officially recommended for use in production environments, that’s the point

ReactiveCocoa and RxSwift use the isa-Swizzle principle of KVO runtime to dynamically create subclasses, rewrite related methods, and add the method we want, and then call the original method in this method, so as to achieve the purpose of hook.

ReactiveCocoa as an example source code

internal func swizzle(_ pairs: (Selector, Any)... , hasSwizzledKey: AssociationKey<Bool>) { AnyClass = swizzleClass(self) ReactiveCocoa.synchronized(subclass) { let subclassAssociations = Associations(subclass as  AnyObject) if ! subclassAssociations.value(forKey: hasSwizzledKey) { subclassAssociations.setValue(true, forKey: hasSwizzledKey) for (selector, body) in pairs { let method = class_getInstanceMethod(subclass, selector)! let typeEncoding = method_getTypeEncoding(method)! if method_getImplementation(method) == _rac_objc_msgForward { let succeeds = class_addMethod(subclass, selector.interopAlias, imp_implementationWithBlock(body), typeEncoding) precondition(succeeds, "RAC attempts to swizzle a selector that has message forwarding enabled with a runtime injected implementation. This is Unsupported in the current version.")} else {// Create a new IMP through the block, add this method implementation for the generated subclass. let succeeds = class_addMethod(subclass, selector, imp_implementationWithBlock(body), typeEncoding) precondition(succeeds, "RAC attempts to swizzle a selector that has already a runtime injected implementation. This is unsupported in the current version.") } } } } } internal func swizzleClass(_ instance: NSObject) -> AnyClass { if let knownSubclass = instance.associations.value(forKey: knownRuntimeSubclassKey) { return knownSubclass } let perceivedClass: AnyClass = instance.objcClass let realClass: AnyClass = object_getClass(instance)! let realClassAssociations = Associations(realClass as AnyObject) if perceivedClass ! = realClass { // If the class is already lying about what it is, it's probably a KVO // dynamic subclass or something else that we shouldn't subclass at runtime. synchronized(realClass)  { let isSwizzled = realClassAssociations.value(forKey: runtimeSubclassedKey) if ! IsSwizzled {// override the -class and +class methods to hide the realClass type replaceGetClass(in: realClass, decoy: decoy) perceivedClass) realClassAssociations.setValue(true, forKey: runtimeSubclassedKey) } } return realClass } else { let name = subclassName(of: perceivedClass) let subclass: AnyClass = name.withCString { cString in if let existingClass = objc_getClass(cString) as! AnyClass? { return existingClass } else { let subclass: AnyClass = objc_allocateClassPair(perceivedClass, cString, 0)! // Override the -class and +class methods to hide the real subclass type replaceGetClass(in: subclass, decoy: decoy) perceivedClass) objc_registerClassPair(subclass) return subclass } } object_setClass(instance, subclass) instance.associations.setValue(subclass, forKey: knownRuntimeSubclassKey) return subclass } }Copy the code

Fishhook takes advantage of MachO’s dynamic binding mechanism. Apple’s shared cache library is not compiled into our MachO file, but is rebound when dynamically linked (relying on dyld). Apple adopted the technology of position-Independent Code (PIC) to successfully make the bottom layer of C dynamic:

  • Compile time inMach-Ofile_DATASection of the symbol table for each referenced systemCThe function creates a pointer (8 bytes of data, all zeros) that is used to relocate the function implementation to the shared library during dynamic binding.
  • When the system is runningCThe function is dynamically bound once the first time it is called, and then willMach-OIn the_DATAThe corresponding pointer in the segment symbol table to the external function (its actual memory address in the shared library).

Fishhook uses PIC technology to do two things:

  • Rebind Pointers to system methods (external functions) to internal functions/customCFunction.
  • Points a pointer to an internal function to the address of a system method when dynamically linked.

OOP encapsulates the properties and behavior of an object, while AOP deals with steps and phases from which to extract aspects. For example, logging, what if we just used OOP and manually added logging at the beginning, end, and exception of each method? If you use AOP to do these repetitive operations with proxies, you can reduce the coupling between the parts of the logical process. They are best combined with each other.

Weak underlying principle

create

We do this by opening assemblyAlways Show DisassemblyAnalyze the underlying implementation. For the entranceobjc_initWeak

/** * Initialize a fresh weak pointer to some object location. * It would be used for code like: * * (The nil case) * __weak id weakPtr; * (The non-nil case) * NSObject *o = ... ; * __weak id weakPtr = o; * * This function IS NOT thread-safe with respect to concurrent * modifications to the weak variable. (Concurrent weak clear is safe.) * * @param location Address of __weak ptr. * @param newObj Object ptr. */ id objc_initWeak(id *location,  id newObj) { if (! newObj) { *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); }Copy the code

The notes are pretty clear

  • locationSaid:__weakAddress of pointer
  • newObj: The object referenced
static id storeWeak(id *location, objc_object *newObj) { ASSERT(haveOld || haveNew); if (! haveNew) ASSERT(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; /// code omitted...... Weak_unregister_no_lock (&oldTable->weak_table,) {weak_unregister_no_lock(&oldtable ->weak_table,); oldObj, location); } // Assign new value, If (haveNew) {newObj = (objc_object *) // if (haveNew) {newObj = (objc_object *) // if (haveNew) {newObj = (objc_object *); // If (haveNew) {newObj = (objc_object *); // If (haveNew) {newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table.  if (! newObj->isTaggedPointerOrNil()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; The storage is not changed.} else {// No new value. The storage is not changed return (id)newObj; }Copy the code

StoreWeak is the main logical point

  • HaveOld:weakWhether the pointer has already pointed to a weak reference
  • HaveNew:weakWhether the pointer needs to point to a new reference
  • CrashIfDeallocating: Indicates whether to weakly reference an object when it is being destructedcrash
/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table.  * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */ id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions) {/ / / get a weak reference object objc_object * referent = (objc_object *) referent_id; objc_object **referrer = (objc_object **)referrer_id; If (referent->isTaggedPointerOrNil()) return referent_id; // ensure that the referenced object is viable if (deallocatingOptions == ReturnNilIfDeallocating || deallocatingOptions  == CrashIfDeallocating) { bool deallocating; if (! referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { // Use lookUpImpOrForward so we can avoid the assert in // class_getInstanceMethod, since we intentionally make this // callout with the lock held. auto allowsWeakReference = (BOOL(*)(objc_object *, SEL)) lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference), referent->getIsa()); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, @selector(allowsWeakReference)); } // if destructor or release is in progress, If (deallocating) {if (deallocatingOptions == CrashIfDeallocating) {_objc_fatal("Cannot form weak reference to  instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", (void*)referent, object_getClassName((id)referent)); } else { return nil; Weak_entry_t Weak_entry_t weak_entry_t weak_entry_t weak_entry_t weak_entry_t weak_entry_t weak_entry_t weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, Append_referrer (entry, referrer))) {/// insert the referrer into weak_entry_t (entry, referrer); Weak_entry_t new_entry(referent, referrer); weak_entry_t (referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; }Copy the code
  • Mainly forisTaggedPointeranddeallocatingconditional
  • The object that will be weakly referencedweak_tableIn theweak_entry_tHash the array to get the correspondingweak_entry_t
  • ifweak_entry_tIf it does not exist, a new one is created and inserted
  • If present, it points to the address of the weakly referenced objectreferrerThrough the functionappend_referrerInsert into the correspondingweak_entry_tReference to an array

The creation process is as follows

Destruction see dealloc analysis in iOS- Trivia (1)

Answer questions first

1. What are the Runtime applications? See 👆Runtime->Runtime Applications 2. What is the difference between AOP and OOP? See 👆 AOP, OOP