Runtime

能夠新增長一個類Class(期間能夠爲該類增長屬性,成員變量,方法,設置其父類,所遵循的協議等)數組

能夠取到類Class的名字,大小,指定成員變量的結構體指針(而後用一系列操做成員變量結構體的方法從該結構體獲取信息),指定屬性的結構體指針(而後用一系列操做屬性結構體的方法從該結構體獲取信息),整個成員變量列表,整個屬性列表,替換屬性,指定實例方法的結構體指針(而後用一系列操做方法結構體的方法從該結構體獲取信息),指定類方法的結構體指針(而後用一系列操做方法結構體的方法從該結構體獲取信息),全部方法的數組,替換方法的實現,方法的實現(而後能夠調用該實現),是否響應指定的選擇子,是否遵循某協議,增長遵循協議,所遵循的協議列表(而後用一系列操做協議列表數組裏面的協議結構體指針元素的方法獲取結構體信息),元類,父類,是不是元類,改變所屬類緩存

 

類與對象基礎數據結構:
Class:
OC類由Class類型表示,Class類型是一個指向objc_class結構體的指針,定義以下:
typedef struct objc_class* Class;//struct objc_class* == Class
objc_class結構體定義以下:
struct objc_class{
Class isa; //指向元類(metaClass)
#if !__OBJC2__
Class super_class; //指向父類(若是當前該類已經是根類,則爲NULL)
const char *name; //類名
long version; //類的版本信息,默認0
long info; //類信息,供運行期使用的一些位標識
long instance_size; //該類的實例變量大小
struct objc_ivar_list *ivars; //該類的成員變量鏈表
struct objc_method_list **methodLists; //方法定義的鏈表
struct objc_cache *cache; //方法緩存(緩存最近使用的方法,每次調用一個方法,被緩存到cache列表中,下次調用runtime先從cache查找,沒有才根據isa去methodLists中查找)
struct objc_protocol_list *protocols; //協議鏈表
#endif
}數據結構

objc_object與id:
objc_object:表示一個類的實例的結構體,定義以下:
struct objc_object{
Class isa;
}
typedef struct objc_object* id;//struct objc_object* == id
id類型是一個指向objc_object結構體的指針,能夠轉換爲任何一種對象,由於任何一種對象其實就是一個objc_object結構體,id相似C中的void *
向一個對象發送消息時,根據該實例對象的isa指針能找到這個實例對象所屬的類,在類的方法列表以及父類的方法列表中尋找對應的selector指向的方法,找到便運行該方法框架

objc_cache:
objc_cache結構體定義以下:
struct objc_cache{
unsigned int mask; //指定分配的緩存bucket的總數
unsigned int occupied; //指定實際佔用的緩存bucket的總數
Method buckets[1]; //指向Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素
}
元類:
OC中類也是一個對象,既然是一個對象就是objc_object結構體,那麼該結構體中的isa指針所指的就是meta-class(元類)
meta-class是一個類對象的類,存儲一個類的全部類方法
向對象發消息時,會在這對象所屬的類的方法列表中查找方法;
向類發消息時,會在這個類的元類的方法列表中查找
元類的isa指向基類的元類,即:
任何NSObject繼承體系下的meta-class都使用NSObject的meta-class做爲本身的所屬類,而基類的meta-class的isa指針是指向它本身。dom

示範例子://用代碼建立一個類,並給類添加成員方法且實現成員方法,並實例化對象調用成員方法
#import <objc/runtime.h>
void ex_registerClassPair();
int main(int argc, const char * argv[]){
@autoreleasepool {
ex_registerClassPair();
}
return 0;
}
void TestMetaClass(id self, SEL _cmd) {
NSLog(@"This objcet is %p", self);
NSLog(@"Class is %@, super class is %@", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 0; i < 4; i++) {
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = objc_getClass((__bridge void *)currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", objc_getClass((__bridge void *)[NSObject class]));
}
/*
經過對Class變量使用objc_getClass()獲取objc_class結構體中的isa(元類的objc_class結構體指針),
isa也是一個Class變量,再經過它使用objc_getClass()一直獲取到根元類
在一個類對象調用class方法是沒法獲取meta-class,它只是返回類而已
*/
void ex_registerClassPair() {
Class newClass = objc_allocateClassPair([NSError class], "TestClass", 0);//建立一個類實例對象叫newClass,是繼承NSError的TestClass類型
class_addMethod(newClass, @selector(testMetaClass), (IMP)TestMetaClass, "v@:");//往newClass對象添加成員方法testMetaClass,而且該成員方法的實現函數是自定義void TestMetaClass(id self, SEL _cmd)函數
objc_registerClassPair(newClass);//註冊newClass實例對象
id instance = [[newClass alloc] initWithDomain:@"some domain" code:0 userInfo:nil];//給該實例對象分配內存並把內存頭賦給instance變量
[instance performSelector:@selector(testMetaClass)];//調用變量的testMetaClass成員方法
}函數

類與對象操做函數
類的操做方法大部分前綴爲class,這些方法主要就是針對objc_class這個結構體中的各個字段
對象的操做方法大部分前綴爲objc或object_
類相關操做函數:
0.由類名獲取該類型Class對象:
Class someClass = NSClassFromString(@"SomeClassName");
id obj = [[someClass alloc] init];
1.獲取類名:
const char * class_getName(Class cls);
2.獲取類的父類:
Class class_getSuperclass(Class cls);
3.判斷給定的Class是不是一個元類:
BOOL class_isMetaClass(Class cls);
4.實例變量大小:
size_t class_getInstanceSize(Class cls);
5.成員變量及屬性:
成員變量包括.h與.m中@interface MyClass()..@end之間聲明的@property與非@property變量
屬性只包括.h與.m中@property變量
全部成員變量,屬性的信息放在objc_class結構體的鏈表ivars中
ivars是一個數組,元素是指向Ivar(變量信息objc_ivar結構體)的指針
a.成員變量操做函數:
獲取類中指定名稱實例成員變量的信息:
Ivar class_getInstanceVariable(Class cls,const char *name);
獲取類成員變量的信息:
Ivar class_getClassVariable(Class cls,const char *name);
添加成員變量:
BOOL class_addIvar(Class cls,const char *name,size_t size,uint8_t alignment,const char *types);
/*
該函數只能在objc_allocateClassPair函數與objc_registerClassPair函數之間調用
另外添加成員變量的類也不能是元類
若是變量的類型是指針類型,則傳遞log2(sizeof(pointer_type))
*/
獲取整個成員變量列表:
Ivar * class_copyIvarList(Class cls,unsigned int *outCount);
/*
返回一個指向成員變量信息的數組
每一個元素指向該成員變量信息的objc_ivar結構體,也就是每一個元素是Ivar
返回的數組不包含父類中聲明的變量
outCount指針返回數組大小
返回的數組必須使用free()釋放
*/
b.屬性操做函數:
獲取指定屬性:
objc_property_t class_getProperty(Class cls,const char *name);
獲取屬性列表:
objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount);
爲類添加屬性:
BOOL class_addProperty(Class cls,const char *name,const objc_property_attribute_t *attributes,unsigned int attributeCount);
替換類的屬性:
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount);
6.方法:
添加方法:
BOOL class_addMethod(Class cls,SEL name,IMP imp,const char *types);
/*
會覆蓋父類的方法實現,但不會取代本類中已存在的實現
若是奔雷中包含一個同名的實現,函數返回NO
全部IMP所指向的具體實現函數至少包含兩個參數:
_self
_cmd
如:
void myMethodIMP(id self,SEL _cmd){
//implementation...
}
參數types是一個描述傳遞給方法的參數類型的字符數組(涉及到類型編碼)
*/
獲取實例方法:
Method class_getInstanceMethod(Class cls,SEL name);
/*
會搜索父類的實現
*/
獲取類方法:
Method class_getClassMethod(Class cls,SEL name);
/*
會搜索父類的實現
*/
獲取全部實例方法的數組:
Method * class_copyMethodList(Class cls,unsigned int *outCount);
/*
只搜索本類的實現
若須要獲取本類中全部類方法:
使用class_copyMethodList(object_getClass(cls), &count);
一個類的實例方法是定義在元類裏面
須要使用free()釋放所獲取的數組
*/
替代方法的實現:
IMP class_replaceMethod(Class cls,SEL name,IMP imp,const char *types);
/*
兩種狀況:
1.類中不存在name指定的方法,會添加該方法
2.類中存在name指定的方法,則相似於method_setImplementation同樣替代原方法的實現
*/
返回方法的具體實現:
IMP class_getMethodImpLementation(Class cls,SEL name);
/*
該函數在向類實例發送消息時會被調用,返回一個指向方法實現函數的指針
速度比method_getImplementation(class_getInstanceMethod(cls, name));函數快
當類實例沒法響應selector,返回的函數指針將是運行時消息轉發機制的一部分
*/
IMP class_getMethodImpLementation_stret(Class cls,SEL name);
類實例是否響應指定的selector
BOOL class_respondsToSelector(Class cls,SEL sel);
/*
相似NSObject類的respondsToSelector:或instancesRespondToSelector:方法
*/
7.協議:
添加協議:
BOOL class_addProtocol(Class cls,Protocol *protocol);
返回類是否實現指定的協議
BOOL class_conformsToProtocol(Class cls,Protocol *protocol);
/*
等價NSObject類的conformsToProtocol:方法
*/
返回類實現的協議列表:
Protocol *class_copyProtocolList(Class cls,unsigned int *outCount);
/*
返回的數組須要使用free()釋放;
*/
8.版本:
獲取版本號:
int class_getVersion(Class cls);
設置版本號:

示範例子:
MyClass *myClass=[[MyClass alloc]init];
unsigned int outCount=0;
Class cls=myClass.class;
//類名:
NSLog(@"class name:%s",class_getName(cls));
NSLog(@"==========================================================");
//父類:
NSLog(@"super class name:%s",class_getName(class_getSuperclass(cls)));
NSLog(@"==========================================================");
//是不是元類:
NSLog(@"MyClass is %@ a meta-class",(class_isMetaClass(cls)?@"yes":@"not"));
NSLog(@"==========================================================");
Class meta_class=objc_getMetaClass(class_getName(cls));
NSLog(@"%s`s meta-class is %s",class_getName(cls),class_getName(meta_class));
NSLog(@"==========================================================");
//變量實例大小
NSLog(@"instance size :%zu",class_getInstanceSize(cls));
NSLog(@"==========================================================");
//成員變量:
Ivar *ivars=class_copyIvarList(cls, &outCount);
for (int i=0; i<outCount; i++) {
Ivar ivar=ivars[i];
NSLog(@"instance variable`s name :%s at index:%d",ivar_getName(ivar),i);
}
free(ivars);

Ivar string=class_getInstanceVariable(cls, "_string");
if (string != NULL) {
NSLog(@"instance variable %s",ivar_getName(string));
}
NSLog(@"==========================================================");
//屬性操做
objc_property_t *properties=class_copyPropertyList(cls, &outCount);
for (int i=0; i<outCount; i++) {
objc_property_t property=properties[i];
NSLog(@"property`s name:%s",property_getName(property));
}
free(properties);

objc_property_t array=class_getProperty(cls, "array");
if (array != NULL) {
NSLog(@"property %s",property_getName(array));
}
NSLog(@"==========================================================");
//方法操做:
Method *methods=class_copyMethodList(cls, &outCount);
for (int i=0; i<outCount; i++) {
Method method=methods[i];
NSLog(@"method`s signature:%s",method_getName(method));
}
free(methods);

Method method1=class_getInstanceMethod(cls, @selector(method1));//@selector(method1)的參數須要cls中存在的方法名
if (method1 != NULL) {
NSLog(@"method %s",method_getName(method1));
}

Method classMethod=class_getClassMethod(cls, @selector(classMethod1));
if (classMethod !=NULL) {
NSLog(@"class method : %s", method_getName(classMethod));
}
NSLog(@"MyClass is%@ responsd to selector: method3WithArg1:arg2:", class_respondsToSelector(cls, @selector(method3WithArg1:arg2:)) ? @"yes" : @" not");
IMP imp=class_getMethodImplementation(cls, @selector(method1));
imp(cls,NULL);
NSLog(@"==========================================================");
//協議
Protocol * __unsafe_unretained *protocols=class_copyProtocolList(cls, &outCount);
Protocol *protocol;
for (int i=0; i<outCount; i++) {
protocol=protocols[i];
NSLog(@"protocol name:%s",protocol_getName(protocol));
}
NSLog(@"MyClass is%@ responsed to protocol %s", class_conformsToProtocol(cls, protocol) ? @"yes" : @" not", protocol_getName(protocol));
NSLog(@"==========================================================");性能

動態建立類和對象:
動態建立類:
建立一個新類和元類
Class objc_allocateClassPair(Class superclass,const char *name,size_t extraBytes);
/*
若建立一個根類,superclass指定爲Nil
extraBytes一般指定爲0
*/
在應用中註冊由objc_allocateClassPair建立的類
void objc_registerClassPair(Class cls);
銷燬一個類及其相關聯的類
void objc_disposeClassPair(Class cls);
/**
若是程序運行中還存在類或其子類的實例,則不能調用針對類調用該方法。
/
/*
建立新類調用objc_allocateClassPair後,
使用如class_addMethod,class_addIvar等函數爲新類添加方法(實例方法和實例變量應該添加到類自身上,而類方法應該添加到類的元類上),實例變量和屬性等
最後調用objc_registerClassPair函數註冊類後,這個新類就能夠在程序中使用了。
*/
示範例子:
#import <objc/runtime.h>
#import "MyClass.h"
void imp_submethod1();
int main(int argc, const char * argv[]){
@autoreleasepool {
MyClass *myClass=[[MyClass alloc]init];
Class cls=objc_allocateClassPair(myClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");
objc_property_attribute_t type={"T","@\"NSString\""};
objc_property_attribute_t ownership={"C",""};
objc_property_attribute_t backingivar={"V","_ivar1"};
objc_property_attribute_t attrs[]={type,ownership,backingivar};
class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);

id instance=[[cls alloc]init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];

}
return 0;
}
void imp_submethod1(){
NSLog(@" run sub method 1");
}
動態建立對象:
建立類實例:
id class_createInstance(Class cls,size_t extraBytes);
/*
ARC下沒法使用
*/
在指定位置穿件類實例:
id objc_constructInstance(Class cls,void *bytes);
銷燬類實例
void * objc_destructInstance(id obj);
/*
銷燬一個類的實例,但不會釋放並移除任何與其相關的引用
*/
示範例子:
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);


實例操做函數:
1.針對整個對象進行操做的函數,包含:
返回指定對象的一份拷貝:
id object_copy(id obj,size_t size);
釋放指定對象佔用的內存:
id object_dispose(id obj);
示範例子:
/*
經過拷貝一個父類實例而且指定新實例大小爲子類實例大小(子類實例比父類實例佔用更多內存)獲得一個新父類實例(但分配到的內存大小不是父類實例而是子類實例的大小)
對新父類實例轉換爲子類類型的實例
最後須要手動釋放
*/
NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);
2.針對對象的實例變量進行操做的函數,包含:
修改類實例的實例變量的值:
Ivar object_setInstanceVariable(id obj,const char *name,void *value);
獲取對象實例變量的值:
Ivar object_getInstanceVariable(id obj,const char *name,void **outValue);
返回指定給定對象分配的任何額外字節的指針
void *object_getIndexedIvars(id obj);
返回對象中實例變量的值
id object_getIvar(id obj,Ivar ivar);
設置對象中實例變量的值
void object_setIvar(id obj,Ivar ivar,id value);
/*
若是實例變量的Ivar已經知道,
那麼調用object_getIvar會比object_getInstanceVariable函數快,
相同狀況下,object_setIvar也比object_setInstanceVariable快
*/
3.針對對象的類進行操做的函數,包含:
返回給定對象的類名
const char * object_getClassName(id obj);
返回對象的類
Class object_getClass(id obj);
設置對象的類
Class object_setClass(id obj,Class cls);
獲取類定義:
獲取已註冊的類定義的列表:
int objc_getClassList(Class *buffer,int bufferCount);
示範例子:
int numClasses;
Class * classes = NULL;
numClasses = objc_getClassList(NULL, 0);
if (numClasses > 0) {
classes = malloc(sizeof(Class) * numClasses);
numClasses = objc_getClassList(classes, numClasses);
NSLog(@"number of classes: %d", numClasses);
for (int i = 0; i < numClasses; i++) {
Class cls = classes[i];
NSLog(@"class name: %s", class_getName(cls));
}
free(classes);
}
建立並返回一個指向全部已註冊類的指針列表:
Class *objc_copyClassList(unsigned int *outCount);
返回指定類的定義:
Class objc_lookUpClass(const char *name);
/*
若是類在運行時未註冊,則objc_lookUpClass會返回nil
*/
Class objc_getClass(const char *name);
/*
若是類在運行時未註冊,會調用類處理回調,並再次確認類是否註冊,若是確認未註冊,再返回nil
*/
Class objc_getRequiredClass(const char *name);
/*
與objc_getClass相同,只不過若是沒有找到類,則會殺死進程。
*/
返回指定類的元類:
Class objc_getMetaClass(const char *name);
/*
若是指定的類沒有註冊,則該函數會調用類處理回調,並再次確認類是否註冊,若是確認未註冊,再返回nil
*/測試

編碼類型:
方法的返回值類型與參數類型編碼爲一個字符串,並將其與方法的selector關聯一塊兒
經過使用@encode(類型)獲得該類型的編碼類型
使用範例:
float a[] = {1.0, 2.0, 3.0};
NSLog(@"array encoding type: %s", @encode(typeof(a)));
/*輸出*/
2014-10-28 11:44:54.731 RuntimeTest[942:50791] array encoding type: [3f]
具體各種型編碼下面表格有。ui

成員變量,屬性:
基礎數據類型:
Ivar:
表示實例變量的類型,一個指向objc_ivar結構體的指針,定義以下:
typedef struct objc_ivar *Ivar;
struct objc_ivar{
char *ivar_name; //變量名
char *ivar_type; //變量類型
int ivar_offset; //基地址偏移字節
}
objc_property_t:
表示Objective-C聲明的屬性的類型,指向objc_property結構體的指針,定義以下:
typedef struct objc_property *objc_property_t;
objc_property_attribute_t:
定義了屬性的特性,是一個結構體,定義以下:
typedef struct{
const char *name; //特性名
const char *value; //特性值
}
關聯對象:
相似於成員變量,不過實在運行時添加的
能夠把關聯對象想象成一個Objective-C對象如(NSString),該對象經過
給定的key鏈接到類的一個實例上
key是一個void指針(const void *)
還須要指定一個內存管理策略,策略有:
OBJC_ASSOCIATION_ASSIGN
宿主對象被釋放時,關聯對象不會被釋放
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
宿主釋放時,關聯對象會被釋放
OBJC_ASSOCIATION_COPY
宿主釋放時,關聯對象會被釋放(若關聯對象是block則須要使用該內存策略)
給對象添加關聯對象以下:
static char myKey;
objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN);
/*
self對象將獲取一個新的關聯對象anObject
若是咱們使用同一個key關聯另一個對象時,會自動釋放以前關聯的對象,新對象會使用舊的關聯對象的地址
*/
獲取對象的關聯對象:
id anObject=objc_getAssociatedobject(self,&myKey);
移除對象的關聯對象:
經過使用objc_removeAssociatedObjects函數

經過使用objc_setAssociatedObject函數this

使用範例:
/*
動態將一個Tap手勢操做鏈接到任何UIView中,
根據束腰指定點擊後的實際操做
能夠將一個手勢對象及操做的block對象關聯到UIView對象中
1.先建立一個手勢識別對象
2.將它和block作關聯對象
*/
- (void)setTapActionWithBlock:(void (^)(void))block{
UITapGestureRecognizer *gesture = objc_getAssociatedObject(self, &kDTActionHandlerTapGestureKey);
if (!gesture){
gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(__handleActionForTapGesture:)];
[self addGestureRecognizer:gesture];
objc_setAssociatedObject(self, &kDTActionHandlerTapGestureKey, gesture, OBJC_ASSOCIATION_RETAIN);
}
objc_setAssociatedObject(self, &kDTActionHandlerTapBlockKey, block, OBJC_ASSOCIATION_COPY);
}
- (void)__handleActionForTapGesture:(UITapGestureRecognizer *)gesture{
if (gesture.state == UIGestureRecognizerStateRecognized){
void(^action)(void) = objc_getAssociatedObject(self, &kDTActionHandlerTapBlockKey);
if (action){
action();
}
}
}
成員變量的操做方法:
獲取成員變量名:
const char *ivar_getName(Ivar v);
獲取成員變量類型編碼:
const char *ivar_getTypeEncoding(Ivar v);
獲取成員變量的偏移量:
ptrdiff_t ivar_getOffset(Ivar v);
/*
對於類型id或其它對象類型的實例變量,能夠調用object_getIvar和object_setIvar來直接訪問成員變量,而不使用偏移量。
*/
關聯對象的操做方法:
設置關聯對象:
void objc_setAssociatedObject(id object,const void *key,id value,objc_AssociationPolicy policy);
獲取關聯對象:
id objc_getAssociatedObject(id object,const void *key);
移除關聯對象:
void objc_removeAssociatedObjects(id object);
屬性的操做方法:
獲取屬性名:
const char *property_getName(objc_property_t property);
獲取屬性特性描述字符串:
const char *property_getAttributes(objc_property_t property);
獲取屬性中指定的特性:
char *property_copyAttributeValue(objc_property_t property,const char *attributeName);
/*
使用後須要使用free()釋放
*/
獲取屬性的特性列表:
objc_property_attribute_t * property_copyAttributeList(objc_property_t property, unsigned int *outCount);
/*
使用後須要使用free()釋放
*/
示範例子:
/*
相同的信息使用不一樣的字段表示,接收數據時,能夠將數據保存到相同的對象中
*/
//有以下兩個數據:
@{@"name1":"張三",@"status1":@"start"}
@{@"name2":"張三",@"status2":@"end"}

//一般須要思想連個方法分別作轉換,但使用Runtime,能夠只實現一個轉換方法,須要先定義一個映射字典(全局變量)
static NSMutableDictionary *map=nil;
@implementation MyObject
+(void)load{
map = [NSMutableDictionary dictionary];
map[@"name1"] =@"name";
map[@""status1"]=@"status";
map[@"name2"] =@"name";
map[@"status2"] =@"status";
}
@end
//轉換方法以下:
- (void)setDataWithDic:(NSDictionary *)dic{
[dic enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
NSString *propertyKey=[self propertyForKey:key];
if(propertyKey){
objc_property_t property = class_getProperty([self class], [propertyKey UTF8String]);
NSString *attributeString = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
...
[self setValue:obj forKey:propertyKey];
}
}];

}

方法與消息:
基礎數據類型:
SEL:
選擇器,表示一個方法的selector的指針,定義以下:
typedef struct objc_selector *SEL;
selector:
用於表示運行時方法的名字
依據每一個方法的名字,參數序列而生成一個惟一的整型標識(Int類型的地址),也就是SEL
只要方法名相同,那麼生成的SEL都同樣,所以同一個類及類的繼承體系中即便參數類型不一樣,也不能存在兩個同名的方法,由於參數類型不參與生成方法標識SEL
SEL sel1=@selector(method1);
NSLog(@"sel1:%p",sel1);
/*
輸出:
2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72
*/
能夠經過下面三種方法獲取SEL:
1.sel_registerName函數
2.@selector()函數
3.NSSelectorFromString()方法
IMP:
一個函數指針,指向方法實現的首地址,定義以下:
id(*IMP)(id,SEL,...)
/*
第一個參數指向self的指針(實例方法,則是類實例的內存地址;類方法,則是指向元類的指針)
第二個參數是方法選擇器selector
接下來是方法的實際參數列表
*/
經過SEL,能夠得到它對應的IMP,因爲IMP是實現方法的首地址,所以得到了執行這個方法代碼的入口點,就能夠像調普通C函數同樣使用該函數指針
經過得到IMP,就能夠跳過Runtime消息傳遞機制,直接執行IMP指向的函數實現,比向對象發送消息更高效
Method:
用於表示類定義中的方法的類型,定義以下:
typedef struct objc_method *Method;
struct objc_method{
SEL method_name; //方法名
char *method_types; //
IMP method_imp; //方法實現
}
方法操做相關函數:
調用指定方法的實現:
id method_invoke(id receiver,Method m,...);
調用返回一個數據結構的方法的實現:
void method_invoke_stret(id receiver,method m,...);
獲取方法名:
SEL method_getName(Method m);
/*
若是向獲取方法名的C字符串,可使用sel_getName(method_getName(method));
*/
返回方法的實現:
IMP method_getImpLementation(Method m);
獲取描述方法參數和返回值類型的字符串
const char *method_getTypeEncoding(Method m);
獲取方法的返回值類型的字符串
char *method_copyReturnType(Method m);
獲取方法的指定位置參數的類型字符串
char *method_copyArgumentType(Method m,unsigned int index);
經過引用返回方法的返回值類型字符串
void method_getReturnType(Method m,char *dst,size_t dst_len);
/*
返回類型字符串會被拷貝到dst中
*/
返回方法的參數的個數
unsigned int method_getNumberOfArguments(Method m);
經過引用返回方法指定位置參數的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
返回指定方法的方法描述結構體
struct objc_method_description * method_getDescription ( Method m );
設置方法的實現
IMP method_setImplementation ( Method m, IMP imp );
交換兩個方法的實現
void method_exchangeImplementations ( Method m1, Method m2 );

選擇器相關操做函數:
返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );
在Objective-C Runtime系統中註冊一個方法,將方法名映射到一個選擇器,並返回這個選擇器
SEL sel_registerName ( const char *str );
/*
將一個方法添加到類定義時,咱們必須在Objective-C Runtime系統中註冊一個方法名以獲取方法的選擇器。
*/
在Objective-C Runtime系統中註冊一個方法
SEL sel_getUid ( const char *str );
比較兩個選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );

方法調用流程:
編譯器——》[receiver message]——》objc_msgSend(消息接受者receiver,方法名selector)/objc_msgSend(receiver, selector, arg1, arg2, ...)
——》完成動態綁定
1.根據接收者的類找到selector對應的方法實現:receiver-->isa-->Class/superClass/rootClass-->objc_method_list-->IMP/沒有IMP就走消息轉發流程
2.objc_msgSend()調用方法實現同時把接收者對象與方法所需參數傳入:調用IMP
3.將實方法實現的返回值做爲本身的返回值:得到IMP返回值並返回
objc_msgSend()的隱藏參數:
1.消息接收對象(經過self來引用)
2.方法的selector(經過_cmd來引用)
示範例子:
- strange{
id target = getTheReceiver();
SEL method = getTheMethod();
if ( target == self || method == _cmd ){
return nil;
}
return [target performSelector:method];
}
獲取方法地址:
methodForSelector:方法能夠獲取方法的指針
須要將該方法返回的指針轉換爲什麼時的函數類型,函數參數和返回值都需匹配上
示範例子:
/*
當頻繁調用一個特定的方法時,經過獲取方法實現的地址直接調用它比動態綁定方式性能更高
*/
void (*setter)(id, SEL, BOOL);
int i;
setter=(void(*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];//獲取到的方法的指針作類型轉換爲本身定義的一個方法指針類型並賦值
for (i = 0 ; i < 1000 ; i++){
setter(targetList[i], @selector(setFilled:), YES);
}
/*
函數指針的前兩個參數必須是id和SEL
這種方式只適合於在相似於for循環這種狀況下頻繁調用同一方法
*/

消息轉發:
默認狀況下
若是是以[object message]的方式調用方法,若是object沒法響應message消息時,編譯器會提示報錯,沒法編譯;
但若是是以perform…的形式來調用,則須要等到運行時才能肯定object是否能接收message消息,不能則程序崩潰。
respondsToSelector:函數能夠判斷一個對象是否能接收某個消息:
if ([self respondsToSelector:@selector(method)]) {
[self performSelector:@selector(method)];
}
運行時,對象沒法處理某一消息時,會啓動消息轉發(message forwarding)機制
默認對象接收到未知消息,會致使程序崩潰
消息轉發機制分3個步驟:
1.動態方法解析:
收到未知消息時,調用所屬類的類方法+resolveInstanceMethod:或者+resolveClassMethod:
在方法中,咱們能夠爲該未知消息新增一個處理方法
前提需實現該處理方法,經過class_addMethod函數動態添加到類裏
示範例子:
void functionForMethod1(id self, SEL _cmd) {
NSLog(@"%@, %p", self, _cmd);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString isEqualToString:@"method1"]) {
class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:");
}
return [super resolveInstanceMethod:sel];
}
2.備用接收者
在1中沒法處理消息,Runtime會繼續調用如下函數進行消息轉發:
- (id)forwardingTargetForSelector:(SEL)aSelector;//把aSelector交由該函數的返回值參數處理
使用範例:
@interface SUTRuntimeMethodHelper : NSObject
- (void)method2;
@end
@implementation SUTRuntimeMethodHelper
- (void)method2 {
NSLog(@"%@, %p", self, _cmd);
}
@end

@interface SUTRuntimeMethod () {
SUTRuntimeMethodHelper *_helper;
}
@end
@implementation SUTRuntimeMethod
+ (instancetype)object {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (self != nil) {
_helper = [[SUTRuntimeMethodHelper alloc] init];
}
return self;
}
- (void)test {
[self performSelector:@selector(method2)];
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector");
NSString *selectorString = NSStringFromSelector(aSelector);
// 將消息轉發給_helper來處理
if ([selectorString isEqualToString:@"method2"]) {
return _helper;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
3.完整轉發
在2中仍沒法處理消息,則啓用完整消息轉發機制,會調用如下方法:
- (void)forwardInvocation:(NSInvocation *)anInvocation
NSInvocation對象包含未處理的消息有關的所有細節,如:selector,target和參數
能夠在該方法選擇將消息轉發給其餘對象
forwardInvocation:方法的實現有兩個任務:
1.定位能夠響應封裝在anInvocation中的消息的對象
2.使用anInvocation做爲參數,將消息發送到選中的對象。anInvocation將會保留調用結果,運行時系統會提取這一結果並將其發送到消息的原始發送者
咱們能夠對消息的內容進行修改,好比追回一個參數等,而後再去觸發消息
還有,必須重寫如下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息轉發機制使用從這個方法中獲取的信息來建立NSInvocation對象,爲給定的selector提供一個合適的方法簽名。
使用範例:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) {
signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:_helper];
}
}

消息轉發與多重繼承:
經過上面2.3.步,表面上是該對象在處理消息,能夠模擬多重繼承的某些特性
不過二者有一個重要區別:
多重繼承將不一樣的功能集成到一個對象中,它會讓對象變得過大,涉及的東西過多;
而消息轉發將功能分解到獨立的小的對象中,並經過某種方式將這些對象鏈接起來,並作相應的消息轉發。

Method Swizzling:
Method Swizzling是改變一個selector的實際實現的技術。
經過這一技術,咱們能夠在運行時經過修改類的分發表中selector對應的函數,來修改方法的實現。
示範例子:
#import <objc/runtime.h>
@implementation UIViewController (Tracking)//建分類
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
// When swizzling a class method, use the following:
// Class class = object_getClass((id)self);

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod =class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}

#pragma mark - Method Swizzling

- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];//實際運行時該調用被從新指定到UIViewController類的-viewWillAppear:中
NSLog(@"viewWillAppear: %@", self);
}
@end
/*
經過method swizzling修改了UIViewController的@selector(viewWillAppear:)對應的函數指針,使其實現指向了咱們自定義的xxx_viewWillAppear的實現
*/
Swizzling應該老是在+load中執行
Swizzling應該老是在dispatch_once中執行
使用注意事項:
1.老是調用方法的原始實現(除非有更好的理由不這麼作)而不使用Swizzling
2.避免衝突:給自定義的分類方法加前綴,從而使其與所依賴的代碼庫不會存在命名衝突。

協議與分類:
基礎數據類型:
Category:
一個指向分類的結構體的指針,定義以下:
typedef struct objc_category *Category;
struct objc_category {
char *category_name ; // 分類名
char *class_name ; // 分類所屬的類名
struct objc_method_list *instance_methods ; // 實例方法列表
struct objc_method_list *class_methods ; // 類方法列表
struct objc_protocol_list *protocols ; // 分類所實現的協議列表
}
操做函數:
分類中的信息都包含在objc_class中,咱們能夠經過針對objc_class的操做函數來獲取分類的信息
示範例子:
@interface RuntimeCategoryClass : NSObject
- (void)method1;
@end
@interface RuntimeCategoryClass (Category)
- (void)method2;
@end
@implementation RuntimeCategoryClass
- (void)method1 {
}
@end
@implementation RuntimeCategoryClass (Category)
- (void)method2 {
}
@end
#pragma mark -
NSLog(@"測試objc_class中的方法列表是否包含分類中的方法");
unsigned int outCount = 0;
Method *methodList = class_copyMethodList(RuntimeCategoryClass.class, &outCount);
for (int i = 0; i < outCount; i++) {
Method method = methodList[i];
const char *name = sel_getName(method_getName(method));
NSLog(@"RuntimeCategoryClass's method: %s", name);
if (strcmp(name, sel_getName(@selector(method2)))) {
NSLog(@"分類方法method2在objc_class的方法列表中");
}
}
/*
輸出結果:
2014-11-08 10:36:39.213 [561:151847] 測試objc_class中的方法列表是否包含分類中的方法
2014-11-08 10:36:39.215 [561:151847] RuntimeCategoryClass's method: method2
2014-11-08 10:36:39.215 [561:151847] RuntimeCategoryClass's method: method1
2014-11-08 10:36:39.215 [561:151847] 分類方法method2在objc_class的方法列表中
*/
Protocol:
定義以下:
typedef struct objc_object Protocol;
操做函數:
返回指定的協議:
Protocol * objc_getProtocol ( const char *name );
/*
若是僅僅是聲明瞭一個協議,而未在任何類中實現這個協議,則該函數返回的是nil
*/
獲取運行時所知道的全部協議的數組:
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
/*
獲取到的數組須要使用free()來釋放
*/
建立新的協議實例:
Protocol * objc_allocateProtocol ( const char *name );
在運行時中註冊新建立的協議
void objc_registerProtocol ( Protocol *proto );
/*
建立一個新的協議後,必須調用該函數以在運行時中註冊新的協議。協議註冊後即可以使用,但不能再作修改,即註冊完後不能再向協議添加方法或協議
*/
爲協議添加方法:
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
添加一個已註冊的協議到協議中:
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
爲協議添加屬性:
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
返回協議名:
const char * protocol_getName ( Protocol *p );
測試兩個協議是否相等:
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
獲取協議中指定條件的方法的方法描述數組
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
獲取協議中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
獲取協議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
獲取協議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
獲取協議採用的協議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
查看協議是否採用了另外一個協議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
庫相關操做:
獲取全部加載的Objective-C框架和動態庫的名稱
const char ** objc_copyImageNames ( unsigned int *outCount );
獲取指定類所在動態庫
const char * class_getImageName ( Class cls );
獲取指定庫或框架中全部類的類名
const char ** objc_copyClassNamesForImage ( const char *image, unsigned int *outCount );
示範例子:
NSLog(@"獲取指定類所在動態庫");
NSLog(@"UIView's Framework: %s", class_getImageName(NSClassFromString(@"UIView")));
NSLog(@"獲取指定庫或框架中全部類的類名");
const char ** classes = objc_copyClassNamesForImage(class_getImageName(NSClassFromString(@"UIView")), &outCount);
for (int i = 0; i < outCount; i++) {
NSLog(@"class name: %s", classes[i]);
}

塊操做:
建立一個指針函數的指針,該函數調用時會調用特定的block
IMP imp_implementationWithBlock ( id block );
/*
參數block的簽名必須是method_return_type ^(id self, method_args …)形式的。該方法能讓咱們使用block做爲IMP
*/
示範例子:
IMP imp = imp_implementationWithBlock(^(id obj, NSString *str) {
NSLog(@"%@", str);
});
class_addMethod(MyRuntimeBlock.class, @selector(testBlock:), imp, "v@:@");
MyRuntimeBlock *runtime = [[MyRuntimeBlock alloc] init];
[runtime performSelector:@selector(testBlock:) withObject:@"hello world!"];
/*
輸出結果:
2014-11-09 14:03:19.779 [1172:395446] hello world!
*/
返回與IMP(使用imp_implementationWithBlock建立的)相關的block
id imp_getBlock ( IMP anImp );
解除block與IMP(使用imp_implementationWithBlock建立的)的關聯關係,並釋放block的拷貝
BOOL imp_removeBlock ( IMP anImp );

弱引用操做:
加載弱引用指針引用的對象並返回
id objc_loadWeak ( id *location );
/*
加載一個弱指針引用的對象,並在對其作retain和autoreleasing操做後返回它,對象就能夠在調用者使用它時保持足夠長的生命週期
*/
存儲__weak變量的新值
id objc_storeWeak ( id *location, id obj );
/*
該函數的典型用法是用於__weak變量作爲賦值對象時
*/
拾遺:
類中經過self與super調用方法,實際上指向的都是相同的消息接收者
[super viewDidLoad]; -runtime生成-> struct objc_super { id receiver; Class superClass; };/*receiver就是self*/ -runtime轉換-> id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );/*在super中的superClass的方法列表查找viewDidLoad*/ --> 找到後 -->objc_msgSend(objc_super->receiver, @selector(viewDidLoad));/*objc_super->receiver就是self自己*/








 

 

 

isa指針:
指向對象的Class對象(保存有對象的方法列表)
一個類全部對象的Class對象都是同一個(保證內存中每個類型有惟一的類型描述)
Class對象也有一個isa指向該Class對象上一級父類的Class對象
調用myObject對象中myAction()方法時:
從myObject中isa尋到myObject的Class對象,而後在這個Class對象中尋找myAction()方法,找到調用,
沒有找到就從該Class對象中isa尋到父類的Class對象中查找,直到根父類的Class對象中也找不到就拋出異常
即:myObject--isa-->myObject的Class對象-->尋找方法-->有-->調用
-->沒有--Class對象的isa-->myObject的Class對象的父類的Class對象-->尋找方法-->有-->調用
-->沒有-->繼續往父類的Class對象尋找...
SEL(方法選擇器)與IMP:
SEL:
OC編譯時依據每個定義的方法的名字與參數序列,生成的一個惟一整數標識
獲取方法:
1.(SEL) @selector(方法的名字)
2.(SEL) NSSelectorFromString(方法的名字的字符串)
IMP:
函數指針
表示指向實現方法的指針
使用IMP直接定位到方法體,不像[myObject myAction];經過對象的isa尋找SEL後才定位到IMP才能調用
從而直接使用IMP更高效
objc_msgSend函數:
編譯時[object method]轉換爲objc_msgSend(id receiver,SEL selector,參數...)的函數調用
objc_msgSend的調用過程:
1.經過receiver,找到isa指針,經過isa指針找到Class對象使用第二個參數selector查找方法
2.若是沒有找到,使用當前Class對象中新的isa指針到上一級父類的Class對象中查找
3.找到方法後,依據receiver中的self指針找到當前的對象,調用當前對象的具體實現方法(IMP指針函數),而後傳遞參數,調用實現方法
4.如一直找到NSObject的Class對象,也沒有該方法,就報告不能識別發送消息的錯誤
動態方法解析:
在類.m中重寫+(BOOL) resolveInstanceMethod:(SEL)sel方法,指定動態實現的方法或函數
+(BOOL)resolveInstanceMethod:(SEL)sel{
NSString *methodName=NSStringFromSelector(sel);
//看看是否是咱們要動態實現的方法名稱
if ([methodName isEqualToString:@"testAction"]){
class_addMethod([self class], sel, (IMP)myAddMethod,"v@:c");
result=YES;
}
return NO;
}
/*
class_addMethod([self class], sel, (IMP)myAddMethod,"v@:c");
第三個參數:
指定該SEL動態方法sel的IMP函數體(函數指針),這裏爲自定義myAddMethod函數
第四個參數:
反應出SEL動態方法sel映射的IMP自定義函數的函數體的函數式(返回類型,參數列表類型)
其中該映射函數也就是動態方法的實現函數參數中
第一個必須是id類型
第二個必須爲SEL類型
第三個開始,能夠按照須要參數定義
"v@:c"
第一個字符是IMP函數的返回類型編碼
第二個字符是IMP函數的第一個參數類型編碼
第三個字符是IMP函數的第二個參數類型編碼
第四個日後是IMP函數接下來所需參數類型編碼
動態方法的實現的前兩個參數必須是id、SEL,因此
第四個參數中的字符串的第2、三個字符必定是@:
*/
void myAddMethod(id self,SEL _cmd,char w){
printf("Method-%s\n",[NSStringFromSelector(_cmd) cStringUsingEncoding:NSUTF8StringEncoding]);
printf("%c\n",w);
}
在使用該類對象時能夠經過一下代碼調用動態方法:
SEL testAction_sel=NSSelectorFromString(@"testAction");
IMP imp=[myObject methodForSelector:testAction_sel];
imp(myObject,testAction_sel,'s');

消息轉發:
沒有實現對象所調用方法會報告錯誤的緣由:
最終都沒有找到對象調用的方法,
系統會NSObject的調用-(void) forwardInvocation: (NSInvocation*)invocation 方法
該方法若是沒有被重寫默認就是拋出錯誤異常
若重寫了-(void) forwardInvocation: (NSInvocation*)invocation 方法
就能夠改變處理方式,好比能夠實現消息轉發到別的類處理
invocation參數對象包含:
receiver對象
selector對象
也就是包含了向一個對象發送消息的全部元素:對象,方法名,參數序列

 

 

 

 

 

 

 

 

*****************************************************************************************************************************************************************************************************
@編譯器指令
一個給定類型編碼爲一種內部表示的字符串
編碼 意義
c ---> A char
i ---> An int
s ---> A short
l ---> A longl 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)
嘗試打印值:
NSLog(@"int : %s", @encode(int));
NSLog(@"float : %s", @encode(float));
NSLog(@"float * : %s", @encode(float*));
NSLog(@"char : %s", @encode(char));
NSLog(@"char * : %s", @encode(char *));
NSLog(@"BOOL : %s", @encode(BOOL));
NSLog(@"void : %s", @encode(void));
NSLog(@"void * : %s", @encode(void *));
NSLog(@"NSObject * : %s", @encode(NSObject *));
NSLog(@"NSObject : %s", @encode(NSObject));
NSLog(@"[NSObject] : %s", @encode(typeof([NSObject class])));
NSLog(@"NSError ** : %s", @encode(typeof(NSError **)));

int intArray[5] = {1, 2, 3, 4, 5};
NSLog(@"int[] : %s", @encode(typeof(intArray)));

float floatArray[3] = {0.1f, 0.2f, 0.3f};
NSLog(@"float[] : %s", @encode(typeof(floatArray)));

typedef struct _struct { short a; long long b; unsigned long long c; } Struct; NSLog(@"struct : %s", @encode(typeof(Struct)));

相關文章
相關標籤/搜索