This article first nuggets, original link

IOS Runtime combat, one-time debugging crematorium pit

Speaking of this black magic, it is still a concept hearsay a few years ago, I have no idea what this is really about. This article is the notes in study, and also the first part of the series of tutorials, mainly to understand the operation principle of black magic, and use it in actual combat, pay attention to the place in use.

The principle of

IMP is found in the system according to SEL, and they are one-to-one corresponding. First of all, let’s understand the implementation principle of Method Swizzling through two pictures

The original correspondence in the system:

SEL1
IMP1
SEL2
IMP2
SEL2
IMP2
SEL3
IMP3

using

Method Swizzling use

Swap method functions

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
Copy the code

Let’s test the theory:

+(void)load{
	[UIViewController exchange];
}
+ (void)exchange{
	Method m1 = class_getInstanceMethod(UIViewController.class, @selector(viewDidLoad));
	Method m2 = class_getInstanceMethod(self, @selector(fy_viewDidload));
	NSLog(@"viewDidLoad excheng before:%p",method_getImplementation(m1));
	NSLog(@"fy_viewDidload excheng before:%p",method_getImplementation(m2));
	if(! class_addMethod(self, @selector(fy_viewDidload), method_getImplementation(m2), method_getTypeEncoding(m2))) { method_exchangeImplementations(m1, m2); } NSLog(@"viewDidLoad excheng after:%p",method_getImplementation(m1));
	NSLog(@"fy_viewDidload excheng after:%p",method_getImplementation(m2));
}
Copy the code

Output:

viewDidLoad     excheng before:0x10a74adf9
fy_viewDidload  excheng before:0x106ca5040
viewDidLoad     excheng after:0x106ca5040
fy_viewDidload  excheng after:0x10a74adf9
Copy the code

So you can see that fy_viewDidload and THE IMP of viewDidLoad are swapped. When we call it twice, the IMP should be restored to its original state.

+(void)load{ [UIViewController exchange]; [UIViewController exchange]; } // exchange the first time viewDidLoad excheng before:0x111626df9 fy_viewDidload excheng before:0x10db81040 viewDidLoad excheng After: 0x10DB81040 fy_viewDidload excheng after: 0x111626DF9 // swap the second time viewDidLoad excheng before: 0x10DB81040 fy_viewDidload  excheng before:0x111626df9 viewDidLoad excheng after:0x111626df9 fy_viewDidload excheng after:0x10db81040Copy the code

As you can see, the IMP has been switched back. We call it in +load because the +load method is thread-safe and only executes once, so we don’t have to worry about concurrency. Load is compiled in file order at compile time, so we can put swap files in the first place. So let’s look at the order in which the system loads the files:

Let’s take page statistics for example, and this is a common requirement for many companies, some SDKS call statistics in viewDidload in the ViewController base class. So how do we make a business-friendly code?

So let’s create a new Category in the ViewController, and then switch viewDidload and fy_viewDidload in the +load, and then fy_viewDidload can add statistics code.

static NSMutableSet *set; +(void)initialize{ [self fy_countViewDidLoad]; } // Count the length of the viewDidLoad method in other subclasses + (void)fy_countViewDidLoad{if (set== nil) {
        set= [[NSMutableSet alloc]init]; } //UIViewcontroller subclass statisticsif([self isSubclassOfClass:UIViewController.class] && self ! = UIViewController.class) {if ([set containsObject:self]) {
            return;
        }else{[setaddObject:self]; } SEL sel = @selector(viewDidLoad); Method m1 = class_getInstanceMethod(self, @selector(viewDidLoad)); IMP imp1 = method_getImplementation(m1); Imp1Func (I,s); The internal ID is nil, causing the function to fail. void(*imp1Func)(id,SEL) = (void*)imp1; Imp1 void (^block)(id,SEL) = ^(id I,SEL s){//code hereprintf("Start \ n");
            NSDate *date =[NSDate new];
            imp1Func(i,s);
            NSLog(@"%@ time:%d", NSStringFromClass(self),(int)[[NSDate date] timeIntervalSinceDate:date]);
            printf("End \ n"); }; IMP imp2 = imp_implementationWithBlock(block); class_replaceMethod(self, sel, imp2, method_getTypeEncoding(m1)); }}Copy the code

First record the original function IMP, use class_replaceMethod exchange SEL and IMP so that SEL points to a new block, execute SEL will execute the block IMP, do not take the path of forwarding messages, higher performance.

When B inherits from A,A inherits from UIViewController, and B implements initialize itself,B misses the statistics. In addition, statistical data of A will be mixed with data of B, resulting in distortion of statistical data.

So when C viewDidload is going to do B, and B viewDidload is going to do A, the subclass is going to duplicate the parent class

A few changes in the scheme can solve this problem.

The key code

//B2
[self addObserver:[YQKVOObserver shared] forKeyPath:kUniqueFakeKeyPath options:NSKeyValueObservingOptionNew context:nil]; // Setup remover of KVO, automatically remove KVO when VC dealloc. YQKVORemover *remover = [[YQKVORemover alloc] init]; remover.target = self; remover.keyPath = kUniqueFakeKeyPath; objc_setAssociatedObject(self, &kAssociatedRemoverKey, remover, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // NSKVONotifying_ViewController Class kvoCls = object_getClass(self); class_addMethod(kvoCls, @selector(viewDidLoad), (IMP)fy_viewDidLoad, originViewDidLoadEncoding); static void fy_viewDidLoad(UIViewController *kvo_self, SEL _sel) { Class kvo_cls = object_getClass(kvo_self); Class origin_cls = class_getSuperclass(kvo_cls); IMP origin_imp = method_getImplementation(class_getInstanceMethod(origin_cls, _sel)); assert(origin_imp ! = NULL); void (*func)(UIViewController *, SEL) = (void (*)(UIViewController *, SEL))origin_imp; CFAbsoluteTime beginTime = CFAbsoluteTimeGetCurrent(); func(kvo_self, _sel); CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent(); // Count the loading time and the number of times}Copy the code

It is a good choice for subclasses that require statistics to accurately count times and times without affecting performance

Runtime for a while, always use straight, debug crematorium

Use of the time feel very cool, can do so 🐂, but other students to debug the time, out of the problem is also very difficult to find. I made this tool to record the runtime dark magic log, it is also very simple to use.

platform :ios, '9.0'
use_frameworks!
target 'MyApp' do
	pod 'FYMSL'
end
Copy the code

Function lifecycle and time-consuming operation callbacks

// The callback of each function can be set independently. FYVCcall *cll = [FYVCcall shared]; [cllsetCallback:^(CFAbsoluteTime loadTime, UIViewController * _Nonnull vc, NSString * _Nonnull funcName,NSString *str) {
	const char *clsName = NSStringFromClass(vc.class).UTF8String;
	printf("cls:%s func:%s %f %s \n",clsName,funcName.UTF8String,loadTime,str.UTF8String);
}];
Copy the code

Output log:

CLS :ViewController func:viewDidLoad 2.001058 2019 09-03 16:25:45 CLS :ViewController func:viewWillAppear: 0.000000 2019-09-03 16:25:45 CLS :ViewController func:viewDidAppear: 0.000000 2019-09-03 16:25:45 CLS :ViewController func:viewDidAppear: 0.000000 2019-09-03 16:25:45Copy the code

View the MethodSwizzling total record

NSLog(@"% @",[FYNodeManger shared].description); ↴ : replace ⇄ : exchange example: example 1:test2 totest1, and then switch to 2test3, and ultimately the imp is 0 x105c6c630 ⇄ | +test2 - >test1 - >test3 -> imp: 0x105C6c630testThe IMP of 1 was replaced by 0x105C6c660, then replaced by 0x105C6C690, then replaced by 0x105C6C600, then swapped againtestTwo, we switched againtest3-> Switched againtest4

↴ | + test1 - > imp: 0 x105c6c660 ↴ | +test1 - > imp: 0 x105c6c690 ↴ | +test1 - > imp: 0 x105c6c600 ⇄ | +test1 - >test2 - > imp: 0 x105c6c600 ⇄ | +test1 - >test3 - > imp: 0 x105c6c630 ⇄ | +test1 - >test4 -> imp:0x105c6c660
Copy the code

View a single SEL record

	NSLog(@"\n%@",[FYNodeManger objectForSEL:@"test1"]);
  
↴ | + test1 - > imp: 0 x10b5de550 ↴ | +test1 - > imp: 0 x10b5de580 ↴ | +test1 - > imp: 0 x10b5de4f0 ⇄ | +test1 - >test2 - > imp: 0 x10b5de4f0 ⇄ | +test1 - >test3 - > imp: 0 x10b5de520 ⇄ | +test1 - >test4 -> imp:0x10b5de550
Copy the code

Give a start if you like

The resources

  • A KVO based page loading, rendering time monitoring method
  • method swizzling log repo