接下來的幾篇文章會講解到以下內容:
1.消息機制
2.獲取類的屬性和方法列表
3.方法交換(方法欺騙)
4.動態(tài)方法決議(或攔截調用)
5.關聯(lián)屬性(associative)
一.runtime簡單介紹.
????????? Objective-C語言是一門動態(tài)語言扮念,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理碧库。這種動態(tài)語言的優(yōu)勢在于:我們寫代碼時能夠更具靈活性巧勤,如我們可以把消息轉發(fā)給我們想要的對象,或者隨意交換一個方法的實現(xiàn)等颅悉。??
????????? 這種特性意味著Objective-C不僅需要一個編譯器迁匠,還需要一個運行時系統(tǒng)來執(zhí)行編譯的代碼剩瓶。對于Objective-C來說城丧,這個運行時系統(tǒng)就像一個操作系統(tǒng)一樣:它讓所有的工作可以正常的運行。這個運行時系統(tǒng)即Objc Runtime亡哄。Objc Runtime其實是一個Runtime庫,它基本上是用C和匯編寫的愿卸,這個庫使得C語言有了面向對象的能力。
Runtime庫主要做下面幾件事:
??????????? 封裝:在這個庫中趴荸,對象可以用C語言中的結構體表示宦焦,而方法可以用C函數(shù)來實現(xiàn)赊舶,另外再加上了一些額外的特性赶诊。這些結構體和函數(shù)被runtime函數(shù)封裝后园骆,我們就可以在程序運行時創(chuàng)建,檢查锌唾,修改類、對象和它們的方法了晌涕。
??????????? 找出方法的最終執(zhí)行代碼:當程序執(zhí)行[object doSomething]時,會向消息接收者(object)發(fā)送一條消息(doSomething)余黎,runtime會根據(jù)消息接收者是否能響應該消息而做出不同的反應惧财。這將在后面詳細介紹扭仁。
Objective-C
runtime目前有兩個版本:Modern runtime和Legacy runtime厅翔。Modern Runtime 覆蓋了64位的Mac
OS X Apps,還有 iOS Apps刀闷,Legacy Runtime 是早期用來給32位 Mac OS X Apps
用的,也就是可以不用管就是了
? ? ? OC中一切都被設計成了對象顽分,我們都知道一個類被初始化成一個實例筒扒,這個實例是一個對象。實際上一個類本質上也是一個對象悬秉,在runtime中用結構體表示冰蘑。
<objc/runtime.h>框架中相關的定義:
typedef struct objc_method *Method;? //描述類中的一個方法
typedef struct objc_ivar *Ivar; //實例變量
typedef struct objc_category *Category; //類別Category
typedef struct objc_property *objc_property_t; //類中聲明的屬性
類在runtime中的表示:
struct objc_class {
Class isa; //指針,顧名思義祠肥,表示是一個什么仇箱,實例的isa指向類對象,類對象的isa指向元類
#if !__OBJC2__
Class super_class;//指向父類
const char *name;//類名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars//成員變量列表
struct objc_method_list **methodLists;//方法列表
struct objc_cache *cache;//緩存, 一種優(yōu)化剂桥,調用過的方法存入緩存列表,下次調用先找緩存
struct objc_protocol_list *protocols//協(xié)議列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
二.簡單運用.(待補充)
1.消息機制
例:自定義Person類美尸,并聲明實現(xiàn)方法- (void)eat, 我們在控制器中調用oc代碼如下:
Person *p1 = [[Person alloc]init];
//1.1 消息發(fā)送
[p1 eat];
oc代碼在Runtime庫中轉化成底層c代碼如下:
Class PersonClass = objc_getClass("Person");
//注意Class實際上也是對象斟薇,所以同樣能夠接受消息,向Class發(fā)送alloc消息
Person *p1 = objc_msgSend(PersonClass, sel_registerName("alloc"));
//發(fā)送init消息給Person實例p1
p1 = objc_msgSend(p1, sel_registerName("init"));
//發(fā)送eat消息給p1胯陋,即調用eat方法
objc_msgSend(p1, sel_registerName("eat"));
//合并以上消息傳遞過程
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("eat"));
如何證明?(如何能看到編譯后的文件寿弱?)
1). 打開終端按灶。cd 到工程目錄下;
cd /Users/qinlun/Desktop/runtime\ -\ 02\ 消息發(fā)送的副本/runtime-消息發(fā)送
2). 執(zhí)行 ls 查看工程目錄下的文件,找到創(chuàng)建person對象的控制器文件ViewController.m;
3). 執(zhí)行rewriteoc ViewController.m(或者clang -rewrite-objc ViewController.m)等一會就會在工程目錄得到ViewConreoller.cpp文件(該文件即為編譯過的文件)鸯旁;
4.)用Xcode打開此.cpp文件大概在57077行可以找到如下代碼;
// @interface ViewController ()
/* @end */
// @implementation ViewController
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
Person *p1 = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p1, sel_registerName("eat"));
注意:在執(zhí)行rewriteoc時應該會報錯UIKit.h file not found, 處理方法:
1.進入終端艇挨,鍵入命令vim ~/.bash_profile韭赘;
2.在vim界面輸入i進入編輯編輯狀態(tài)并且鍵入:alias rewriteoc='clang -x objective-c -rewrite-objc -isysroot/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk'泉瞻;
3.鍵入完畢,點esc退出編輯狀態(tài)袖牙,再鍵入:wq退出vim并保存,再執(zhí)行source ~/.bash_profile司忱;
4.再執(zhí)行rewriteoc xxx.m即可成功畴蹭;
解決辦法參見文章( http://blog.csdn.net/majiakun1/article/details/52842010)解決
2.獲取類的屬性和方法列表
例:自定義類Person
Person.h文件
@interface Person : NSObject
@property (nonatomic, strong)NSString *name;
@property (nonatomic, assign) int age;
- (void)sayHello;
@end
Person.m文件
#import "Person.h"
@interface Person ()
//私有屬性
@property (nonatomic, strong)NSString *address; //地址
@end
@implementation Person
- (instancetype)init{
if (self = [super init]) {
? ? ? _address = @"北戴河";
? ? ? ? self.name = @"Beijing";
? ? ? ? self.age = 5000;
}
return self;
}
- (NSString *)description {
? ? ? ? return [NSString stringWithFormat:@"address:%@, age:%f, name: %@", self.address,self.age ,self.name];
}
- (void)sayHello{
? ? ? ? NSLog(@"-----說:hello--%@", self.address);
}
//內部私有方法
- (void)interface{
? ? ? ? ? NSLog(@"我是: %@", self.name);
}
@end
ViewController.m中操作如下:
1).獲取成員變量(包括私有)
Person *p = [[Person alloc]init];
NSLog(@"--更改前:%@--",[oneperson description]);
unsigned int count = 0;
//這是個指針,地址為所有成員變量的數(shù)組集合桨踪,函數(shù)第二個參數(shù)傳遞的是count的地址芹啥,函數(shù)執(zhí)行完畢后會將成員變量個數(shù)存到改地址下
Ivar *members = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; i++) {
? ? //循環(huán)獲取每一個成員變量
? ? ? Ivar var = members[i];
? ? ? //獲取對象的成員變量名稱
? ? ? const char *memberName = ivar_getName(var);
? ? ? //獲取對象的成員變量類型
? ? ? const char *memberType = ivar_getTypeEncoding(var);
? ? ? NSLog(@"--members: %p; memberName: %s; type: %s",members, memberName, memberType);
}
日志輸入:
發(fā)現(xiàn)在Person.m中聲明的屬性address也拿到了墓怀。
2).修改屬性值
Ivar address = members[2];
object_setIvar(p1, address, @"圓明園");
NSLog(@"--更改后:%@--",[oneperson description]);
日志輸入:
可以看到對象p1初始化地址北戴河被修改為了圓明園
3).獲取方法(包括私有)
unsigned int count = 0;
Method *memberFuncs = class_copyMethodList([Person class], &count);
for (int i = 0; i < count; i++) {
? ? //獲取對象的方法名
? ? SEL address= method_getName(memberFuncs[i]);
? ? NSString *methodName = [NSString stringWithCString:sel_getName(address)? ? encoding:NSUTF8StringEncoding];
? ? //獲取對象方法的返回值
? ? char *backParamType =? method_copyReturnType(memberFuncs[i]);
? ? NSLog(@"對象的方法有:---%d----%@----%s",i, methodName, backParamType);
}
日志輸入:
3.方法交換(方法欺騙)
Objective-C 提供了一下API用于動態(tài)替換類方法或者實例方法的實現(xiàn):
class_replaceMethod 替換類方法的定義傀履;
method_exchangeImplementations 交換兩個方法的實現(xiàn)(具體使用案例如下);
method_setImplementation 設置一個方法的實現(xiàn)钓账;
注:class_replaceMethod 試圖替換一個不存在的方法時候,會調用class_addMethod為該類增加一個新方法
示例:開發(fā)中經(jīng)常運到數(shù)組數(shù)據(jù)個數(shù)不確定的情況服协,一旦數(shù)組越界就會出現(xiàn)崩潰啦粹,對此問題可以采取給系統(tǒng)的NSArray原生的方法- (ObjectType)objectAtIndex:(NSUInteger)index替換掉;
#import "NSArray+LXZArray.h"
/運行時相關的文件
#import <objc/runtime.h>
@implementation NSArray (LXZArray)
//load方法在此文件加載進內存后就會調用
+ (void)load{
? ? [super load];
? ? //獲取系統(tǒng)自動的方法
? ? Method fromeMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"),? ? @selector(objectAtIndex:));
? ? ? //獲取用來替換的方法
? ? ? Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(lxz_objectAtIndex:));
? ? ? //方法交換
? ? ? method_exchangeImplementations(fromeMethod, toMethod);
}
- (id)lxz_objectAtIndex:(NSInteger)index {
? ? ? if (self.count - 1 < index) {
? ? ? ? ? ? @try {
? ? ? ? ? ? ? ? ? return [self lxz_objectAtIndex:index];
? ? ? ? ? } @catch (NSException *exception) {
? ? ? ? ? // 在崩潰后會打印崩潰信息跳纳,方便我們調試贪嫂。
? ? ? ? ? NSLog(@"---崩潰------- %s Crash Because Method %s? ----------\n", class_getName(self.class),? __func__);
? ? ? ? ? NSLog(@"---崩潰---%@", [exception callStackSymbols]);
? ? ? ? ? return nil;
? ? ? ? } @finally {}
? }else {
? ? ? ? ? return [self lxz_objectAtIndex:index];}
}
疑問:
class_getInstanceMethod(objc_getClass("__NSArrayI"),? ? @selector(objectAtIndex:))
為什么寫objc_getClass("__NSArrayI")而不是objc_getClass("NSArray")?
解答:
NSArray是基于C底層CFArray/CFArrayRef實現(xiàn)的,NSArray 可以看做是一個 CFArrayRef 的 Wrapper類.
NSArray的真正類型是__NSArrayI(Immutable)
NSMutableArray的真正類型是__NSArrayM(_internal)
這里涉及到類簇概念《诽粒可以參考【iOS】類簇(class cluster) http://www.reibang.com/p/c60d9ffcde4b
未完待續(xù)餐曹。。台猴。
參考:
http://www.reibang.com/p/99af00237cb8;
http://www.cocoachina.com/ios/20150901/13173.html曹步;