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_A
Class 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 subclass
setter
Methods 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
Aspects
Two are providedAOP
Method, one for the class and one for the instance. In determining thehook
After that,Aspects
Allow us to choosehook
Is 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 in
Mach-O
file_DATA
Section of the symbol table for each referenced systemC
The 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 running
C
The function is dynamically bound once the first time it is called, and then willMach-O
In the_DATA
The 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/custom
C
Function. - 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 Disassembly
Analyze the underlying implementation.
For the entrance
objc_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
location
Said:__weak
Address of pointernewObj
: 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
:weak
Whether the pointer has already pointed to a weak referenceHaveNew
:weak
Whether the pointer needs to point to a new referenceCrashIfDeallocating
: 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 for
isTaggedPointer
anddeallocating
conditional - The object that will be weakly referenced
weak_table
In theweak_entry_t
Hash the array to get the correspondingweak_entry_t
- if
weak_entry_t
If it does not exist, a new one is created and inserted - If present, it points to the address of the weakly referenced object
referrer
Through the functionappend_referrer
Insert into the correspondingweak_entry_t
Reference 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