關(guān)于iOS runtime,主要總結(jié)了大部分runtime函數(shù),舉例說明什么時候需要用到runtime

一.認識一下runtime類


runtime

二.The Runtime


  • 1.Objective-C:是一門簡單的語言,95%是C。只是在語言層面上加了些關(guān)鍵字和語法。真正讓Objective-C如此強大的是它的運行時。它很小但卻很強大。它的核心是消息分發(fā)。
  • 2.Messages
    執(zhí)行一個方法,有些語言,編譯器會執(zhí)行一些額外的優(yōu)化和錯誤檢查,因為調(diào)用關(guān)系很直接也很明顯。但對于消息分發(fā)來說,就不那么明顯了。在發(fā)消息前不必知道某個對象是否能夠處理消息。你把消息發(fā)給它,它可能會處理,也可能轉(zhuǎn)給其他的 Object 來處理。一個消息不必對應一個方法,一個對象可能實現(xiàn)一個方法來處理多條消息。
    Objective-C:中,消息是通過objc_msgSend()這個 runtime 方法及相近的方法來實現(xiàn)的。這個方法需要一個target,selector,還有一些參數(shù)。理論上來說,編譯器只是把消息分發(fā)變成objc_msgSend來執(zhí)行。比如下面這兩行代碼是等價的:
    [array insertObject:foo atIndex:5];objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);`
  • 3.Objects, Classes, MetaClasses
    大多數(shù)面向?qū)ο蟮恼Z言里有 classes 和 objects 的概念。Objects通過Classes生成。但是在Objective-C中,classes本身也是objects(譯者注:這點跟python很像),也可以處理消息,這也是為什么會有類方法和實例方法。具體來說,Objective-C 中的 Object 是一個結(jié)構(gòu)體(struct),第一個成員是isa,指向自己的class。這是在objc/objc.h中定義的。
    typedef struct objc_object { Class isa;} *id;
    object的class保存了方法列表,還有指向父類的指針。但classes也是objects,也會有isa變量,那么它又指向哪兒呢?這里就引出了第三個類型: metaclasses。一個 metaclass被指向class,class被指向object。它保存了所有實現(xiàn)的方法列表,以及父類的metaclass
  • 4.Methods, Selectors and IMPs*
    我們知道了運行時會發(fā)消息給對象。我們也知道一個對象的class保存了方法列表。那么這些消息是如何映射到方法的,這些方法又是如何被執(zhí)行的呢?
    第一個問題的答案很簡單。class的方法列表其實是一個字典,key 為 selectors ,IMPs 為value 。一個IMP是指向方法在內(nèi)存中的實現(xiàn)。很重要的一點是,selector和IMP之間的關(guān)系是在運行時才決定的,而不是編譯時。這樣我們就可以在其中進行操作啦。
    IMP通常是指向方法的指針,。下面演示了Method和IMP
    - (id)doSomethingWithInt:(int)aInt{}
    id doSomethingWithInt(id self, SEL _cmd, int aInt){} 第一個參數(shù)是self,類型為id,第二個參數(shù)是_cmd,類型為SEL,余下的是方法的參數(shù)。這也是self和_cmd被定義的地方
  • 5運行時到底能做什么呢?
    所有的運行時方法都有特定的前綴。下面是一些有意思的方法:
    class
    class開頭的方法是用來修改和自省classes。方法如class_addIvar, class_addMethod, class_addProperty和class_addProtocol允許重建classes。class_copyIvarList, class_copyMethodList, class_copyProtocolList和class_copyPropertyList能拿到一個class的所有內(nèi)容。而class_getClassMethod, class_getClassVariable, class_getInstanceMethod, class_getInstanceVariable, class_getMethodImplementation和class_getProperty返回單個內(nèi)容。

也有一些通用的自省方法,如class_conformsToProtocol, class_respondsToSelector, class_getSuperclass。最后,你可以使用class_createInstance來創(chuàng)建一個object。

ivar
這些方法能讓你得到名字,內(nèi)存地址和Objective-C type encoding。

method
這些方法主要用來自省,比如method_getName, method_getImplementation, method_getReturnType等等。也有一些修改的方法,包括method_setImplementation和method_exchangeImplementations,這些我們后面會講到。

objc
一旦拿到了object,你就可以對它做一些自省和修改。你可以get/set ivar, 使用object_copy和object_dispose來copy和free object的內(nèi)存。最NB的不僅是拿到一個class,而是可以使用object_setClass來改變一個object的class。待會就能看到使用場景。

property
屬性保存了很大一部分信息。除了拿到名字,你還可以使用property_getAttributes來發(fā)現(xiàn)property的更多信息,如返回值、是否為atomic、getter/setter名字、是否為dynamic、背后使用的ivar名字、是否為弱引用。

protocol
Protocols有點像classes,但是精簡版的,運行時的方法是一樣的。你可以獲取method, property, protocol列表, 檢查是否實現(xiàn)了其他的protocol。

sel
最后我們有一些方法可以處理 selectors,比如獲取名字,注冊一個selector等等
Classes And Selectors From Strings
通過一個字符串生成Classes和Selectors。NSClassFromString和NSSelectorFromString:
Class stringclass = NSClassFromString(@"NSString");
這樣做有以下優(yōu)點:1.可以得知是否存在某個class,NSClassFromString 會返回nil,2根據(jù)不同的輸入返回不同的class或method進行相應的操作,比如你在解析一些數(shù)據(jù),每個數(shù)據(jù)項都有要解析的字符串以及自身的類型(String,Number,Array)。你可以在一個方法里搞定這些,也可以使用多個方法。
Method Swizzling
我們知道方法由兩個部分組成。Selector相當于一個方法的id;IMP是方法的實現(xiàn)。這樣分開的一
個便利之處是selector和IMP之間的對應關(guān)系可以被改變。比如一個 IMP 可以有多個 selectors 指向它。而 Method Swizzling 可以交換兩個方法的實現(xiàn)。或許你會問“什么情況下會需要這個呢?”。我們先來看下Objective-C中,兩種擴展class的途徑。首先是 subclassing。你可以重寫某個方法,調(diào)用父類的實現(xiàn),這也意味著你必須使用這個subclass的實例,但如果繼承了某個Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。這種情況下,你會想添加一個方法到NSArray,也就是使用Category。99%的情況下這是OK的,但如果你重寫了某個方法,就沒有機會再調(diào)用原先的實現(xiàn)了。

Method Swizzling 可以搞定這個問題。你可以重寫某個方法而不用繼承,同時還可以調(diào)用原先的實現(xiàn)。通常的做法是在category中添加一個方法(當然也可以是一個全新的class)。可以通過method_exchangeImplementations這個運行時方法來交換實現(xiàn)。來看一個demo,這個demo演示了如何重寫addObject:方法來紀錄每一個新添加的對象。

#import  <objc/runtime.h>

@interface NSMutableArray (LoggingAddObject)
- (void)logAddObject:(id)aObject;
@end

@implementation NSMutableArray (LoggingAddObject)

+ (void)load {
    Method addobject = class_getInstanceMethod(self, @selector(addObject:));
    Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));




    method_exchangeImplementations(addObject, logAddObject);


}

- (void)logAddObject:(id)aobject {
    [self logAddObject:aObject];
    NSLog(@"Added object %@ to array %@", aObject, self);
}

@end

我們把方法交換放到了load中,這個方法只會被調(diào)用一次,而且是運行時載入。如果指向臨時用一下,可以放到別的地方。注意到一個很明顯的遞歸調(diào)用logAddObject:。這也是Method Swizzling容易把我們搞混的地方,因為我們已經(jīng)交換了方法的實現(xiàn),所以其實調(diào)用的是addObject:

IMP互換過程

動態(tài)繼承、交換
我們可以在運行時創(chuàng)建新的class,這個特性用得不多,但其實它還是很強大的。你能通過它創(chuàng)建新的子類,并添加新的方法。

但這樣的一個子類有什么用呢?別忘了Objective-C的一個關(guān)鍵點:object內(nèi)部有一個叫做isa的變量指向它的class。這個變量可以被改變,而不需要重新創(chuàng)建。然后就可以添加新的ivar和方法了。可以通過以下命令來修改一個object的class.
object_setClass(myObject, [MySubclass class]);
這可以用在Key Value Observing。當你開始observing an object時,Cocoa會創(chuàng)建這個object的class的subclass,然后將這個object的isa指向新創(chuàng)建的subclass。點擊這里查看更詳細的解釋。
動態(tài)方法處理
目前為止,我們討論了方法交換,以及已有方法的處理。那么當你發(fā)送了一個object無法處理的消息時會發(fā)生什么呢?很明顯,"it breaks"。大多數(shù)情況下確實如此,但Cocoa和runtime也提供了一些應對方法。

首先是動態(tài)方法處理。通常來說,處理一個方法,運行時尋找匹配的selector然后執(zhí)行之。有時,你只想在運行時才創(chuàng)建某個方法,比如有些信息只有在運行時才能得到。要實現(xiàn)這個效果,你需要重寫+resolveInstanceMethod: 和/或 +resolveClassMethod:。如果確實增加了一個方法,記得返回YES。

+ (BOOL)resolveInstanceMethod:(SEL)aSelector {
    if (aSelector ==@selector(myDynamicMethod)) {
        class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES;
    } return [super resolveInstanceMethod:aSelector];
}

消息轉(zhuǎn)發(fā)
如果 resolve method 返回NO,運行時就進入下一步驟:消息轉(zhuǎn)發(fā)。有兩種常見用例。1) 將消息轉(zhuǎn)發(fā)到另一個可以處理該消息的object。2) 將多個消息轉(zhuǎn)發(fā)到同一個方法。

消息轉(zhuǎn)發(fā)分兩步。首先,運行時調(diào)用-forwardingTargetForSelector:,如果只是想把消息發(fā)送到另一個object,那么就使用這個方法,因為更高效。如果想要修改消息,那么就要使用-forwardInvocation:,運行時將消息打包成NSInvocation,然后返回給你處理。處理完之后,調(diào)用invokeWithTarget:。

Cocoa有幾處地方用到了消息轉(zhuǎn)發(fā),主要的兩個地方是代理(Proxies)和響應鏈(Responder Chain)。NSProxy是一個輕量級的class,它的作用就是轉(zhuǎn)發(fā)消息到另一個object。如果想要惰性加載object的某個屬性會很有用。NSUndoManager也有用到,不過是截取消息,之后再執(zhí)行,而不是轉(zhuǎn)發(fā)到其他的地方。

響應鏈是關(guān)于Cocoa如何處理與發(fā)送事件與行為到對應的對象。比如說,使用Cmd+C執(zhí)行了copy命令,會發(fā)送-copy:到響應鏈。首先是First Responder,通常是當前的UI。如果沒有處理該消息,則轉(zhuǎn)發(fā)到下一個-nextResponder。這么一直下去直到找到能夠處理該消息的object,或者沒有找到,報錯。
使用Block作為Method IMP

iOS 4.3帶來了很多新的runtime方法。除了對properties和protocols的加強,還帶來一組新的以 imp 開頭的方法。通常一個 IMP 是一個指向方法實現(xiàn)的指針,頭兩個參數(shù)為 object(self)和selector(_cmd)。iOS 4.0和Mac OS X 10.6 帶來了block,imp_implementationWithBlock() 能讓我們使用block作為 IMP,下面這個代碼片段展示了如何使用block來添加新的方法。

IMP myIMP = imp_implementationWithBlock(^(id _self, NSString *string) {
    NSLog(@"Hello %@", string);
});
class_addMethod([MYclass class], @selector(sayHello:), myIMP, "v@:@");

可以看到,Objective-C 表面看起來挺簡單,但還是很靈活的,可以帶來很多可能性。動態(tài)語言的優(yōu)勢在于在不擴展語言本身的情況下做很多很靈巧的事情。比如Key Value Observing,提供了優(yōu)雅的API可以與已有的代碼無縫結(jié)合,而不需要新增語言級別的特性。

三.runtime 優(yōu)秀博客

Objective-C對象模型及應用-
objc/runtime 探索
iOS開發(fā)教程之Objc Runtime筆記

四.初識runtime

1.objc的Types定義:
包括objc_method結(jié)構(gòu)體方法****Method****;
objc_ivar結(jié)構(gòu)體實例變量****Ivar****;
objc_category結(jié)構(gòu)體類目****Category ;
類中聲明的objc_property結(jié)構(gòu)體屬性****objc_property_t****;
calass類objc_class結(jié)構(gòu)體;****

/* Types */
#if !OBJC_TYPES_DEFINED   //objc_types_defined

(1)/// An opaque type that represents(代表) a method in a class definition(定義). // 代表一個類定義中的一個方法
typedef struct objc_method *Method; 

-----------------
以前方法的定義;
struct objc_method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
}

SEL selector的簡寫,俗稱方法選擇器,實質(zhì)存儲的是方法的名稱
IMP implement的簡寫,俗稱方法實現(xiàn),看源碼得知它就是一個函數(shù)指針
Method 對上述兩者的一個包裝結(jié)構(gòu).
例如:

獲取類方法:Method class_getClassMethod(Class cls, SEL name)
(2).Ivar:實例變量,成員變量;———— --只能獲取自己類的實例變量,因為封裝性,其他類的實例變量不能獲取
typedef struct objc_ivar *Ivar;
例:獲取實例變量name;
用屬性聲明的時候必須要@synthesize name才能有實例變量;
@property(nonatomic,copy)NSString * name; @synthesize name;
或者:
@interface MasterViewController : UITableViewController { NSString *name; }

(3).An opaque type that represents a category. //代表一個類目
typedef struct objc_category *Category;

(4).代表oc類中聲明的屬性; — 多個屬性的列表; (包括獲取私有屬性);
typedef struct objc_property *objc_property_t;

(5).objc_class:類對象的isa指向元類;類class在runtime中的表示:(在實際 中用Class 替代struct objc_class)

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY; //指針,類對象的isa指向元類

#if !__OBJC2__
    Class super_class                                      OBJC2_UNAVAILABLE;//父類
    const char *name                                      OBJC2_UNAVAILABLE;//類名
    long version                                              OBJC2_UNAVAILABLE;//類版本號,默認是0

    long info                                                   OBJC2_UNAVAILABLE;//類信息,供運行期使用的一些位標識。
    long instance_size                                     OBJC2_UNAVAILABLE;//該類的實例變量大小
    struct objc_ivar_list *ivars                          OBJC2_UNAVAILABLE;//成員變量鏈表
    struct objc_method_list **methodLists       OBJC2_UNAVAILABLE;//方法定義鏈表

    struct objc_cache *cache                            OBJC2_UNAVAILABLE;//方法緩存,對象接到一個消息會根據(jù)isa指針查找消息對象,這時會在methodLists中遍歷,如果cache了,常用的方法調(diào)用時就能夠提高調(diào)用的效率。
    struct objc_protocol_list *protocols            OBJC2_UNAVAILABLE;//協(xié)議鏈表.
#endif} OBJC2_UNAVAILABLE;/* Use `Class` instead of `struct objc_class *` */#endif

2.定義協(xié)議結(jié)構(gòu)體struct objc_object Protocol;

#ifdef __OBJC__
@class Protocol;
#else
typedef struct objc_object Protocol;
#endif

**3.定義一個方法method的結(jié)構(gòu)體objc_method_description:方法名name,方法參數(shù)types;
**

SEL name;               /**< The name of the method */方法名
char *types;            /**< The types of the method arguments */方法參數(shù)};

**4.定義一個聲明屬性結(jié)構(gòu)體objc_property_attribute_t: 屬性名name,屬性值value;
**

typedef struct {
    const char *name;           /**< The name of the attribute */特性名稱
    const char *value;          /**< The value of the attribute (usually empty) */特性值;
} objc_property_attribute_t;

特性相關(guān)編碼
屬性的特性字符串 以 T@encode(type) 開頭, 以 V實例變量名稱 結(jié)尾,中間以特性編碼填充,通過property_getAttributes
即可查看
特性編碼
具體含義
R --- readonly
C --- copy
& --- retain
N --- nonatomic
G(name) --- getter=(name)
S(name) --- setter=(name)
D --- @dynamic
W --- weak
P --- 用于垃圾回收機制

五.認識一下runtime的函數(shù)

1. 對象拷貝id object_copy(id obj, size_t size); 即[obj copy]-----arc中不能用;*
*2.釋放給定對象的內(nèi)存object_dispose(id obj)-釋放對象,同[obj release]——arc中不能使用;
*3.獲取對象obj的類---Class object_getClass(id obj); 同oc的[obj class];
*4.設置id對象obj的類為cos:Class object_setClass(id obj, Class cos);
*5.判斷一個id對象是否是class類:BOOL object_isClass(id obj) ——如果是類或者元類就返回YES,如果是對象就返回NO,因為對象不符合objc_class結(jié)構(gòu)體中包含該有的成員變
例如:

ViewController *obj = [ViewController new];
BOOL fi = object_isClass(obj);
NSLog(@"%@",@(fi)); //0,NO,因為obj是對象,不是類;

6.獲取對象obj的類名,const char *object_getClassName(id obj); obj:是一個對象;
例如
:

 ViewController *obj = [ViewController new];
 NSLog(@"%s",object_getClassName(obj));//ViewController

7.不支持arc,不能用object_getIndexedIvars(id obj)

8.獲取一個對象的一個實例變量的值
id object_getIvar(id obj, Ivar ivar)//只能獲取自己實例變量的值;
Ivar class_getInstanceVariable(Class cls, const char *name)結(jié)合使用;
跟oc的:[obj valueForKey:ivar],self.語法獲取類似!
9.void object_setIvar(id obj, Ivar ivar, id value)設置一個對象(obj)的一個實例變量(ivar)的Ivar class_getInstanceVariable(Class cls, const char *name)得到一個類中成員變量名字XX的成員變量Ivar
Example:

//獲取一個類中名字為age的實例變量
Ivar var  = class_getInstanceVariable([self class], "age");
 //給這個實力變量賦值
object_setIvar(self, var, @22);

10.設置一個實例變量的實例Ivar:Ivar object_setInstanceVariable(id obj, const char *name, void *value)arc不能使用
11.獲取一個實例變量的實例Ivar:Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)—arc不能用;
12.根據(jù)class的name獲取Class類Class objc_getClass(const char *name)等同于oc的:
NSClassFromString();返回這個名字類的對象的元類; 如果沒有注冊過,將返回nil;
Example:

 Class vc = objc_getClass("ViewController");
 Class vc1 =   NSClassFromString(@"ViewController");

13.根據(jù)class的name獲取Class元類-Class objc_getMetaClass(const char *name);
什么是元類呢?
Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結(jié)構(gòu)體的指針。它的定義如下typedef struct objc_class *Class;

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;
 
#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父類
    const char *name                        OBJC2_UNAVAILABLE;  // 類名
    long version                            OBJC2_UNAVAILABLE;  // 類的版本信息,默認為0
    long info                               OBJC2_UNAVAILABLE;  // 類信息,供運行期使用的一些位標識
    long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變量大小
    struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變量鏈表
    struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表
    struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協(xié)議鏈表
#endif
 
} OBJC2_UNAVAILABLE;

isa:需要注意的是在Objective-C中,所有的類自身也是一個對象,這個對象的Class里面也有一個isa指針,它指向metaClass(元類)
如下圖:


認識oc中的類

獲取類定義的方法有三個:objc_lookUpClass, objc_getClass和objc_getRequiredClass。如果類在運行時未注冊,則objc_lookUpClass會返回nil,而objc_getClass會調(diào)用類處理回調(diào),并再次確認類是否注冊,如果確認未注冊,再返回nil。而objc_getRequiredClass函數(shù)的操作與objc_getClass相同,只不過如果沒有找到類,則會殺死進程。objc_getMetaClass函數(shù):如果指定的類沒有注冊,則該函數(shù)會調(diào)用類處理回調(diào),并再次確認類是否注冊,如果確認未注冊,再返回nil。不過,每個類定義都必須有一個有效的元類定義,所以這個函數(shù)總是會返回一個元類定義,不管它是否有效。
14.獲取注冊類的總數(shù),列表bufferint objc_getClassList(Class *buffer, int bufferCount);buffer:緩存class的所有value的數(shù)組; bufferCount:緩存所有的類的個數(shù)
// 創(chuàng)建并返回一個指向所有已注冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );
Example:利用objc_getClassList輸出項目所有類

 int numClasses;
    Class *classes = NULL;
    
    classes = NULL;
    numClasses = objc_getClassList(NULL, 0);
    NSLog(@"Number of classes: %d", numClasses);
    
    if (numClasses > 0 )
    {
        classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        for (int i = 0; i < numClasses; i++) {
            NSLog(@"Class name: %s", class_getName(classes[i]));
        }
        free(classes);//malloc 分配內(nèi)存 需要free釋放
    }

15.獲取類的類名----const char *class_getName(Class cls)

Example:

const char *className = class_getName(self.class);
NSLog(@"%s",className);
//轉(zhuǎn)換為NSString字符串
 NSString *strName = [NSString stringWithCString:className encoding:NSUTF8StringEncoding];

16.判斷這個class類是否是元類BOOL class_isMetaClass(Class cls);
獲取這個class類的父類** Class class_getSuperclass(Class cls);
設置class類cls的父類為新的newSuper類Class class_setSuperclass(Class cls, Class newSuper)
@warning You should not use this function.//你不應該使用這個功能;
獲取這個類的實例變量大小size_t class_getInstanceSize(Class cls)不知道有什么卵用

17.成員變量(ivar)- 獲取類cls中指定name的實例成員變量 Ivar class_getInstanceVariable(Class cls, const char *name)— 可以用這個來獲取api的私有方法;
成員變量(ivars)- 獲取類中指定name的變量Ivar class_getClassVariable(Class cls, const char *name)
Example:

 self.name = @"iOS";
 Ivar var = class_getInstanceVariable([self class], "name"); 
 id nameVar = object_getIvar(self, var);  //同get;
    NSLog(@"%@", nameVar);

Ivar var = class_getClassVariable([self class], "name");   
 object_setIvar(self, var, @"iOS");   
 id nameVar = object_getIvar(self, var);
    NSLog(@"%@", nameVar);

18.獲取類中所有實例成員變量Ivar Ivar *class_copyIvarList(Class cls, unsigned int *outCount)這個比較常用
class_copyPropertyList返回的僅僅是對象類的屬性(@property申明的屬性),而class_copyIvarList返回類的所有屬性和變量(包括在@interface大括號中聲明的變量)
獲取cls類指定名字的屬性objc_property_t class_getProperty(Class cls, const char *name)
Example:

- (NSMutableArray *)getArrayValue:(NSArray *)array{
     NSMutableArray *valueArray = [NSMutableArray array];    //value數(shù)組
    for (NSObject *object in array) {
        unsigned int numberofIvars = 0;
// 獲取類成員變量列表,numberofIvars 為類成員數(shù)量
        Ivar* ivars = class_copyIvarList([object class], &numberofIvars);
        NSMutableArray *objectArray = [NSMutableArray array];
        for(const Ivar* p = ivars; p< ivars+numberofIvars;p++){
            Ivar const ivar = *p ;
  //獲取變量名;
            NSString* key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            NSString *value = [object valueForKey:key];
            [objectArray addObject:value];
        }
        [valueArray addObject:objectArray];
    }
    free(ivars);//(需要free釋放 否者內(nèi)存泄露)
    return valueArray;
}
//獲取cls類指定名字的屬性
 objc_property_t properties = class_getProperty(objc_getClass("Class"), [@"name"UTF8String]);   
 const char *propertyName = property_getName(properties); 
  NSString *str = [NSString stringWithUTF8String:propertyName];
  NSLog(@"%@",str);

19.獲取實例方法(Method)— 獲取類中的某個實例方法(減號方法):Method class_getInstanceMethod(Class cls, SEL name)
獲取類中的某個類方法(加號方法): Method class_getClassMethod(Class cls, SEL name)
獲取類中的SEL方法的實現(xiàn):IMP class_getMethodImplementation(Class cls,SEL name)
IMP方法實現(xiàn)這個參數(shù)可以被用在方法的實現(xiàn)替換函數(shù)class_replaceMethod;
添加方法class_addMethod(Class cls, SEL name, IMP imp, const char \\\\*types)等同oc的方法:- (IMP)methodForSelector:(SEL)aSelector;
.判斷這個方法是否添加成功
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
替換cls類中已有方法SEL-name的實現(xiàn)為imp,如果該方法不存在添加該方法IMP class_replaceMethod(Class cls, SEL name, IMP imp,const char *types)
Example:利用runtime懶人實現(xiàn)iOS 防止按鈕連續(xù)點擊

20.判斷類中是否實現(xiàn)了某個方法的 BOOL class_respondsToSelector(Class cls, SEL sel)與oc中NSObject.h的方法一樣:- (BOOL)respondsToSelector:(SEL)aSelector- (BOOL)instancesRespondToSelector:(SEL)aSelector在使用代理時經(jīng)常需要用到這個判斷

21.獲取類的所有方法列表-Method *class_copyMethodList(Class cls, unsigned int *outCount)——包括獲取到了私有方法;outCount是返回的一個值,包含返回array列表數(shù)組的長度個數(shù),如果outCount是NULL,就不會返回length;
Example:
例如:獲取UILabel類的所有方法列表
u_int count; Method *methods = class_copyMethodList([UILabel class], &count); for (int i =0; i<count; i++) {
SEL name1 = method_getName(methods[i]);

    const char *selName= sel_getName(name1);
    NSString *strName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding];        NSLog(@"%@",strName);

        這2句等同于NSStringFromSelector(name1);
}

free(methods);
NSLog(@"%u",count);//171個方法;包括很多私有方法;

22.判斷類是否實現(xiàn)指定的協(xié)議- BOOL class_conformsToProtocol(Class cls, Protocol *protocol)
與NSObject類中的方法類似:- (BOOL)conformsToProtocol:(Protocol*)aProtocol不常用吧;
返回類實現(xiàn)的協(xié)議列表Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount)
Example:

 BOOL protocol = class_conformsToProtocol(self.class, @protocol(XXXDelegate));
//oc中:
 BOOL protocol = [self conformsToProtocol:@protocol(XXXDelegate)];
//class_copyProtocolList 
 unsigned int outCount=0;
   Protocol * __unsafe_unretained *protocols = class_copyProtocolList(objc_getClass("Class"), &outCount);
    for (int i =0; i<outCount; i++)    {      
    Protocol *myProtocol = protocols[i];       
    const char *protocolName = protocol_getName(myProtocol);  
    NSString *str = [NSString stringWithUTF8String:protocolName];     
   NSLog(@"%@",str);
    }

23.給class類添加一個新的實例變量,再判斷是否成功
/*cls:動態(tài)創(chuàng)建的類; name:實例變量名; size:實例變量類型字節(jié)數(shù); alignment:對齊方法,一般是0; */ BOOL class_addIvar(Class cls, const char *name, size_t size,uint8_t alignment, const char *types)
[注意]只能在動態(tài)創(chuàng)建類objc_allocateClassPair之后,注冊類objc_registerClassPair之前添加實例變量
給class動態(tài)添加一個屬性:class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount)
24.動態(tài)添加類.
1* 創(chuàng)建一個新類和元類:* Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
superclass:父類;
name:類名;
extraBytes:變量字節(jié)數(shù),一般是0;
跟objc_registerClassPair是一對,搭配使用;
2 *在應用中注冊由objc_allocateClassPair創(chuàng)建的類; *void objc_registerClassPair(Class cls)注冊后,才能使用這個類;
3 * 銷毀一個類及其相關(guān)聯(lián)的類 *void objc_disposeClassPair ( Class cls ); //在運行中還存在或存在子類實例,就不能夠調(diào)用這個
Example:

Class Test= objc_allocateClassPair([NSObject class], "Test", 0);
//為類添加變量
class_addIvar(Test, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
//為類添加方法
//IMP 是函數(shù)指針
// typedef id (*IMP)(id, SEL, ...);
IMP i = imp_implementationWithBlock(^(id this,id some){
  NSLog(@"%@",some);
  return @111;
});
//注冊方法名為 test: 的方法
SEL s = sel_registerName("test:");
class_addMethod(Test, s, i, "i@:");
//結(jié)束類的定義
objc_registerClassPair(Test);

六 消息轉(zhuǎn)發(fā)

我們程序崩潰的時候看到這樣的提示:**** unrecognized selector sent to instance 表明你曾向某個對象發(fā)送了一條無法解讀的消息。當一個對象收到無法解讀的消息后會如何處理,也就是說對象無法響應選擇子(方法),這時就要進入到消息轉(zhuǎn)發(fā)機制的流程。
1.查找接收者所屬的類,看其是否能動態(tài)添加方法,以處理這個“未知的方法”。(動態(tài)方法解析) +(BOOL) resolveInstanceMethod:(SEL)selector
2 .運行期系統(tǒng)把消息轉(zhuǎn)給其他接收者處理。(備援接收者)
-(id)forwardingTargetForSelector:(SEL)selector
3* .經(jīng)過上述兩步后,如果還是沒有辦法處理選擇子,就啟動完成的消息轉(zhuǎn)發(fā)。創(chuàng)建NSInvocation對象,把與尚未處理的那條消息有關(guān)的全部細節(jié)都封于其中。此對象包含選擇子、目標target及參數(shù)。在觸發(fā)NSInvocation對象時,消息派發(fā)系統(tǒng)會把消息指派給目標對象。
-(void)forwardInvocation:(NSInvocation *)invocation

7.Objective-C Reflection(Objective-C 反射機制)

JSONModel中的實現(xiàn):


JSONModel方法實現(xiàn)順序
對象屬性的獲取則主要在最后一個inspectProperties方法
-(void)__inspectProperties
{
    //JMLog(@"Inspect class: %@", [self class]);
    
    NSMutableDictionary* propertyIndex = [NSMutableDictionary dictionary];
    
    //temp variables for the loops
    Class class = [self class];
    NSScanner* scanner = nil;
    NSString* propertyType = nil;
    
    // inspect inherited properties up to the JSONModel class
    while (class != [JSONModel class]) {
        //JMLog(@"inspecting: %@", NSStringFromClass(class));
        
        unsigned int propertyCount;
        objc_property_t *properties = class_copyPropertyList(class, &propertyCount);
        
        //loop over the class properties
        for (unsigned int i = 0; i < propertyCount; i++) {

            JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

            //get property name
            objc_property_t property = properties[i];
            const char *propertyName = property_getName(property);
            p.name = [NSString stringWithUTF8String:propertyName];
            
            //JMLog(@"property: %@", p.name);
            
            //get property attributes
            const char *attrs = property_getAttributes(property);
            NSString* propertyAttributes = [NSString stringWithUTF8String:attrs];
            
            if ([propertyAttributes hasPrefix:@"Tc,"]) {
                //mask BOOLs as structs so they can have custom convertors
                p.structName = @"BOOL";
            }
            
            scanner = [NSScanner scannerWithString: propertyAttributes];
            
            //JMLog(@"attr: %@", [NSString stringWithCString:attrs encoding:NSUTF8StringEncoding]);
            [scanner scanUpToString:@"T" intoString: nil];
            [scanner scanString:@"T" intoString:nil];

···

//finally store the property index in the static property index
objc_setAssociatedObject(self.class,
                         &kClassPropertiesKey,
                         [propertyIndex copy],
                         OBJC_ASSOCIATION_RETAIN // This is atomic
                         );

在這邊可以看到基本步驟如下

通過調(diào)用自身的class方法獲取當前類的元數(shù)據(jù)信息
通過runtime的 class_copyPropertyList 方法取得當前類的屬性列表,以指針數(shù)組的形式返回
遍歷指針數(shù)組,通過property_getName獲取屬性名,property_getAttributes獲取屬性類型
使用NSScanner來掃描屬性類型字符串,將類似如下的形式"T@"NSNumber",&,N,V_id",處理成NSNumber,逐個屬性循環(huán)處理
將所有處理好的數(shù)據(jù)放入propertyIndex這個字典中
通過objc_setAssociatedObject將這些數(shù)據(jù)關(guān)聯(lián)到kClassPropertiesKey
使用時在properties方法中這樣取出屬性數(shù)據(jù):

//returns a list of the model's properties
-(NSArray*)__properties__
{
    //fetch the associated object
    NSDictionary* classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
    if (classProperties) return [classProperties allValues];

    //if here, the class needs to inspect itself
    [self __setup__];
    
    //return the property list
    classProperties = objc_getAssociatedObject(self.class, &kClassPropertiesKey);
    return [classProperties allValues];
}

以上就是JSONModel中使用反射機制實現(xiàn)的類屬性獲取過程,相比常見的逐個取值賦值的方式,這種方法在代碼上的確簡潔優(yōu)雅了很多

完結(jié)。 記錄一下 學習runtime的過程吧,也希望大家有所收獲吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,768評論 0 9
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,231評論 0 7
  • Objective-C語言是一門動態(tài)語言,他將很多靜態(tài)語言在編譯和鏈接時期做的事情放到了運行時來處理。這種動態(tài)語言...
    tigger丨閱讀 1,431評論 0 8
  • 原文出處:南峰子的技術(shù)博客 Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了...
    _燴面_閱讀 1,252評論 1 5
  • 七夕的清晨,就著漫天的雨,讀《鵲橋仙》。 鵲橋仙 宋.楊無...
    熟年與君共閱讀 551評論 0 1