1. 程式人生 > >runtime實戰(二)動態新增方法

runtime實戰(二)動態新增方法

runtime的實戰二就是動態新增方法

一:建立一個Person類

我們在Person類的標頭檔案宣告一個eat方法,並不去實現它,當我們呼叫時,會出現如下的崩潰棧
2017-05-01 20:43:06.483 SH_Runtime[2133:104550] -[Person eat]: unrecognized selector sent to instance 0x618000018a20
2017-05-01 20:43:06.495 SH_Runtime[2133:104550] *** Terminating app due to uncaught exception 'NSInvalidArgumentException'
, reason: '-[Person eat]: unrecognized selector sent to instance 0x618000018a20' *** First throw call stack: ( 0 CoreFoundation 0x0000000103f71d4b __exceptionPreprocess + 171 1 libobjc.A.dylib 0x00000001039d321e objc_exception_throw + 48 2 CoreFoundation 0x0000000103fe1f04
-[NSObject(NSObject) doesNotRecognizeSelector:] + 132 3 CoreFoundation 0x0000000103ef7005 ___forwarding___ + 1013 4 CoreFoundation 0x0000000103ef6b88 _CF_forwarding_prep_0 + 120 5 SH_Runtime 0x00000001033fc5f1 -[ViewController viewDidLoad] + 209 6
UIKit 0x0000000104537a3d -[UIViewController loadViewIfRequired] + 1258 7 UIKit 0x0000000104537e70 -[UIViewController view] + 27 8 UIKit 0x00000001044014b5 -[UIWindow addRootViewControllerViewIfPossible] + 71 9 UIKit 0x0000000104401c06 -[UIWindow _setHidden:forced:] + 293 10 UIKit 0x0000000104415519 -[UIWindow makeKeyAndVisible] + 42 11 UIKit 0x000000010438df8d -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818 12 UIKit 0x00000001043940ed -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731 13 UIKit 0x000000010439126d -[UIApplication workspaceDidEndTransaction:] + 188 14 FrontBoardServices 0x000000010756d6cb __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24 15 FrontBoardServices 0x000000010756d544 -[FBSSerialQueue _performNext] + 189 16 FrontBoardServices 0x000000010756d8cd -[FBSSerialQueue _performNextFromRunLoopSource] + 45 17 CoreFoundation 0x0000000103f16761 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17 18 CoreFoundation 0x0000000103efb98c __CFRunLoopDoSources0 + 556 19 CoreFoundation 0x0000000103efae76 __CFRunLoopRun + 918 20 CoreFoundation 0x0000000103efa884 CFRunLoopRunSpecific + 420 21 UIKit 0x000000010438faea -[UIApplication _run] + 434 22 UIKit 0x0000000104395c68 UIApplicationMain + 159 23 SH_Runtime 0x00000001033fc98f main + 111 24 libdyld.dylib 0x0000000106dd368d start + 1 25 ??? 0x0000000000000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException

注意:第三、第四行我們看到Terminating app due to uncaught exception ‘NSInvalidArgumentException’,這個是什麼呢?
其實這是蘋果給開發者的一個捕獲異常的機會,NSObject類有方法:

// 捕獲未實現的類方法
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
// 捕獲未實現的例項方法
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);

在Person.m檔案實現這兩個方法,便實現動態新增的功能;

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

@implementation Person

// 捕獲未實現的類方法
+ (BOOL)resolveClassMethod:(SEL)sel
{

    return NO;
}

// 捕獲未實現的例項方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSLog(@"the sel is :%@",NSStringFromSelector(sel));
    if (sel == @selector(eat)) {
        // runtime 新增方法;
        // class: 所要新增的類;
        // SEL: 方法編號;
        // IMP(Implementation):方法實現,一個方法指標
        // type: 型別
        class_addMethod([Person class], sel, (IMP)eat, "");
        return YES;
    }
    return NO;
}

void eat() {
    NSLog(@"person eat");
}

@end

另:class_addMethod方法的第四個引數一般情況為空字串即程式碼沒有引數和返回值,對於有返回值和引數方法,看蘋果開發者文件:

Tables Are
Code Meaning
c A char
i An int
s A short
l A long l is treated as a 32-bit quantity on 64-bit programs.
q A long long
C An unsigned char
I An unsigned int
S An unsigned short
L An unsigned long
Q An unsigned long long
f A float
d A double
B A C++ bool or a C99 _Bool
v A void
* A character string (char *)
@ An object (whether statically typed or typed id)
# A class object (Class)
: A method selector (SEL)
[array type] An array
{name=type…} A structure
(name=type…) A union
bnum A bit field of num bits
^type A pointer to type
? An unknown type (among other things, this code is used for function pointers)

參閱Type Encoding表我們可知:

class_addMethod([self class], @selector(resolveThisMethodDynamically), (IMP) myMethodIMP, "[email protected]:");

An array of characters that describe the types of the arguments to the
method. For possible values, see Objective-C Runtime Programming Guide
Type Encodings. Since the function must take at least two arguments—self and _cmd, the second and third characters must be “@:”
(the first character is the return type).

方法的返回值為void,而@:是必須的引數。

我的GitHub程式碼地址:https://github.com/lvshaohua/SH_Runtime