ios runtime IMP指針 消息轉發機制Demo

本文代碼是根據消息轉發機制來寫的, 有不妥之處, 請大神指正


githubDemo地址歡迎大家下載


1. UIViewController (ViewDidLoadName)文件 UIViewController的category


在實現viewDidLoad系統方法的前提下 添加自定義的方法


2. Person類有一個run的方法(沒有實現),這裏展示了OC中的消息轉發機制, 使其不崩潰並實現方法,或者轉到Car的run方法來實現


直接上代碼(註釋很全, 簡單易懂)


ViewController.m文件

<span style="font-size:18px;">//  ViewController.m
//  MethodSwizzlingIMPdemo
//
//  Created by 帝炎魔 on 16/5/12.
//  Copyright © 2016年 帝炎魔. All rights reserved.
//

#import "ViewController.h"
#import "UIViewController+ViewDidLoadName.h"
#import "Person.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    // 創建Person對象 執行run方法, 但是Person類中並沒有實現run方法
    // 我們利用消息轉發機制防止其崩潰,或者用其他方法來代替[per run]的方法
    Person *per = [[Person alloc] init];
    [per run];
    
    // Do any additional setup after loading the view, typically from a nib.
}



- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end</span>


UIViewController (ViewDidLoadName).h文件

<span style="font-size:18px;">//  UIViewController+ViewDidLoadName.h
//  MethodSwizzlingIMPdemo
//
//  Created by 帝炎魔 on 16/5/12.
//  Copyright © 2016年 帝炎魔. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIViewController (ViewDidLoadName)

@end</span>
UIViewController (ViewDidLoadName).m文件

<span style="font-size:18px;">//  UIViewController+ViewDidLoadName.m
//  MethodSwizzlingIMPdemo
//
//  Created by 帝炎魔 on 16/5/12.
//  Copyright © 2016年 帝炎魔. All rights reserved.
//

#import "UIViewController+ViewDidLoadName.h"
#import <objc/runtime.h>

// 有返回值的IMP
typedef id (* _IMP) (id, SEL, ...);
// 沒有返回值的IMP(定義爲VIMP)
typedef void (* _VIMP) (id, SEL, ...);

@implementation UIViewController (ViewDidLoadName)


+(void)load
{
    // 保證交換方法只執行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        // 獲取原始方法
        Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
        // 獲取原始方法的實現指針(IMP)
        _VIMP viewDidLoad_IMP = (_VIMP)method_getImplementation(viewDidLoad);
        
        // 重新設置方法的實現
        method_setImplementation(viewDidLoad, imp_implementationWithBlock(^(id target, SEL action) {
            // 調用系統的原生方法
            viewDidLoad_IMP(target, @selector(viewDidLoad));
            // 新增的功能代碼
            NSLog(@"%@ did load", target);
        }));
        
    });
}

//+ (void)load
//{
//    // 保證交換方法只執行一次
//    static dispatch_once_t onceToken;
//    dispatch_once(&onceToken, ^{
//        // 獲取到這個類的viewDidLoad方法, 它的類型是一個objc_method結構體的指針
//        Method viewDidLoad = class_getInstanceMethod(self, @selector(viewDidLoad));
//        
//        // 獲取到自己剛剛創建的一個方法
//        Method myViewDidLoad = class_getInstanceMethod(self, @selector(myViewDidLoad));
//        
//        // 交換兩個方法的實現
//        method_exchangeImplementations(viewDidLoad, myViewDidLoad);
//        
//      
//        
//    });
//}
//
//- (void)myViewDidLoad
//{
//    // 調用系統的方法
//    [self myViewDidLoad];
//    NSLog(@"%@ did load", self);
//}

@end</span>

Person.h 文件

<span style="font-size:18px;">//  Person.h
//  MethodSwizzlingIMPdemo
//
//  Created by 帝炎魔 on 16/5/12.
//  Copyright © 2016年 帝炎魔. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject

- (void)run;

@end</span>

Person.m文件

<span style="font-size:18px;">//  Person.m
//  MethodSwizzlingIMPdemo
//
//  Created by 帝炎魔 on 16/5/12.
//  Copyright © 2016年 帝炎魔. All rights reserved.
//

#import "Person.h"
#import <objc/runtime.h>
#import "Car.h"

@implementation Person

/**
 *  首先,該方法在調用時,系統會查看這個對象能否接收這個消息(查看這個類有沒有這個方法,或者有沒有實現這個方法。),如果不能並且只在不能的情況下,就會調用下面這幾個方法,給你“補救”的機會,你可以先理解爲幾套防止程序crash的備選方案,我們就是利用這幾個方案進行消息轉發,注意一點,前一套方案實現後一套方法就不會執行。如果這幾套方案你都沒有做處理,那麼程序就會報錯crash。
    
    方案一:
 
    + (BOOL)resolveInstanceMethod:(SEL)sel
    + (BOOL)resolveClassMethod:(SEL)sel
 
    方案二:
 
    - (id)forwardingTargetForSelector:(SEL)aSelector
 
    方案三:
 
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
    - (void)forwardInvocation:(NSInvocation *)anInvocation;
 
*/

void run (id self, SEL _cmd)
{
    // 程序會走我們C語言的部分
    NSLog(@"%@ %s", self, sel_getName(_cmd));
}


/**
 *   方案一
 *
 *   爲Person類動態增加了run方法的實現
    由於沒有實現run對應的方法, 那麼系統會調用resolveInstanceMethod讓你去做一些其他操作
 */

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
//    if(sel == @selector(run)) {
//        class_addMethod([self class], sel, (IMP)run, "v@:");
//        return YES;
//    }
    return [super respondsToSelector:sel];
}

/** 方案二
 *  現在不對方案一做任何的處理, 直接調用父類的方法
    系統會走到forwardingTargetForSelector方法
 */

//- (id)forwardingTargetForSelector:(SEL)aSelector
//{
//    return [[Car alloc] init];
//}


/**
 *   不實現forwardingTargetForSelector,
     系統就會調用方案三的兩個方法
    methodSignatureForSelector 和 forwardInvocation
 */

/**
 *  方案三
    開頭我們要找的錯誤unrecognized selector sent to instance原因,原來就是因爲methodSignatureForSelector這個方法中,由於沒有找到run對應的實現方法,所以返回了一個空的方法簽名,最終導致程序報錯崩潰。
 
     所以我們需要做的是自己新建方法簽名,再在forwardInvocation中用你要轉發的那個對象調用這個對應的簽名,這樣也實現了消息轉發。
  */

/**
 *  methodSignatureForSelector
 *  用來生成方法簽名, 這個簽名就是給forwardInvocation中參數NSInvocation調用的
 *
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSString *sel = NSStringFromSelector(aSelector);
    // 判斷你要轉發的SEL
    if ([sel isEqualToString:@"run"]) {
        // 爲你的轉發方法手動生成簽名
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        
    }
    return [super methodSignatureForSelector:aSelector];
}
/**
 *  關於生成簽名類型"v@:"解釋一下, 每個方法會默認隱藏兩個參數, self, _cmd
    self 代表方法調用者, _cmd 代表這個方法SEL, 簽名類型就是用來描述這個方法的返回值, 參數的, 
    v代表返回值爲void, @表示self, :表示_cmd
 */
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL selector = [anInvocation selector];
    // 新建需要轉發消息的對象
    Car *car = [[Car alloc] init];
    if ([car respondsToSelector:selector]) {
        // 喚醒這個方法
        [anInvocation invokeWithTarget:car];
    }
}

@end</span>

Car .m文件

<span style="font-size:18px;">//  Car.m
//  MethodSwizzlingIMPdemo
//
//  Created by 帝炎魔 on 16/5/12.
//  Copyright © 2016年 帝炎魔. All rights reserved.
//

#import "Car.h"

@implementation Car


- (void)run
{
     NSLog(@"%@ %s", self, sel_getName(_cmd));
}
@end</span>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章