最近照著網上博客學習runtime,把一些重點記錄下來。
通過傳一個類的實例對象,可獲得這個類的屬性列表(Property),方法列表(實例方法不是類方法,怎樣獲得類方法列表還在學習中),成員變量列表(通常以_開頭,類擴展(匿名類別)中的成員變量也能獲取在內),協議列表(是這個類(包括類擴展也就是匿名類別)所遵守的協議,并不是寫在這個類里面的協議)。
在控制器中新建屬性,方法,成員變量,協議四個按鈕,點擊按鈕顯示相應列表到textView上面。使用runtime記得先導入頭文件#import "objc/runtime.h"
新建一個學生類Student.h
#import <Foundation/Foundation.h>
@protocol AbideByLawDelegate <NSObject>
-(void)loveCountry;
-(void)protectPublicProperty;
-(void)readyToHelpOthersForCause:(NSString *)cause;
@end
@protocol PreservePublicMoralDelegate <NSObject>
-(void)filialToOurParent;
-(void)solidarity;
-(void)mustGoOn;
@end
@interface Student : NSObject<AbideByLawDelegate,PreservePublicMoralDelegate,NSCopying,NSCoding>
{
NSString *_joggy;
NSUInteger _workTime;
BOOL _possessRoom;
}
@property (nonatomic, copy) NSString *cnName; //姓名
@property (nonatomic, copy) NSString *cardType; //證件類型
@property (nonatomic, copy) NSString *idNo; //身份證號
@property (nonatomic, copy) NSString *siginDate; //發證日期
@property (nonatomic, copy) NSString *deadlineDate; //后臺返回的截止期限
@property (nonatomic, copy) NSString *period; //有效期限(是一個區間)
@property (nonatomic, copy) NSString *sex; //性別
@property (nonatomic, copy) NSString *birthday; //出生
@property (nonatomic, copy) NSString *address; //住址
@property (nonatomic, copy) NSString *origan; //簽發機關
@property (nonatomic, copy) NSString *nation;
@property (nonatomic, assign)NSUInteger *age;
-(void)eatUp;
-(void)sleepUp;
-(void)playBall:(NSString *)ball;
-(void)writeCode:(NSString *)bank;
+(void)presentForPerson;
+(void)rankWithName:(NSString *)name;
@end
- 獲取屬性列表
//獲取屬性列表
- (IBAction)getPropertyList:(UIButton *)sender {
objc_property_t *propertyList = class_copyPropertyList([self.stu class], &count);
for (unsigned int i = 0; i<count; i++) {
const char *propertyname = property_getName(propertyList[i]);
NSLog(@"property----=""%@",[NSString stringWithUTF8String:propertyname]);
[self.listArray addObject:[NSString stringWithUTF8String:propertyname]];
self.num++;
}
NSMutableString *listInfostring = [NSMutableString stringWithString:@""];
for (int i= 0 ; i < self.num; i++) {
[listInfostring appendString:self.listArray[i]];
[listInfostring appendString:@"\n"];
}
self.listInfoTextView.text = listInfostring;
}
- 獲取方法列表
//獲取方法列表
- (IBAction)getMethodList:(UIButton *)sender {
Method *methodList = class_copyMethodList([self.stu class], &count);
for (unsigned int i = 0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method----=""%@",NSStringFromSelector(method_getName(method)));
[self.listArray addObject:NSStringFromSelector(method_getName(method))];
self.num ++ ;
}
NSMutableString *listInfostring = [NSMutableString stringWithString:@""];
for (int i= 0 ; i < self.num; i++) {
[listInfostring appendString:self.listArray[i]];
[listInfostring appendString:@"\n"];
}
self.listInfoTextView.text = listInfostring;
}
- 獲取成員變量列表
//獲取成員變量列表
- (IBAction)getIvarList:(UIButton *)sender {
Ivar *ivarList = class_copyIvarList([self.stu class], &count);
for (unsigned int i = 0; i<count; i++) {
Ivar ivar = ivarList[i];
const char *ivarname = ivar_getName(ivar);
NSLog(@"ivar----="">%@",[NSString stringWithUTF8String:ivarname]);
[self.listArray addObject:[NSString stringWithUTF8String:ivarname]];
self.num ++;
}
NSMutableString *listInfostring = [NSMutableString stringWithString:@""];
for (int i= 0 ; i < self.num; i++) {
[listInfostring appendString:self.listArray[i]];
[listInfostring appendString:@"\n"];
}
self.listInfoTextView.text = listInfostring;
}
- 獲取協議列表
//獲取協議列表
- (IBAction)getProtocolList:(UIButton *)sender {
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self.stu class], &count);
for (unsigned int i = 0; i<count; i++) {
Protocol *protocol = protocolList[i];
const char *protocolname = protocol_getName(protocol);
NSLog(@"protocol----="">%@",[NSString stringWithUTF8String:protocolname]);
[self.listArray addObject:[NSString stringWithUTF8String:protocolname]];
self.num ++;
}
NSMutableString *listInfostring = [NSMutableString stringWithString:@""];
for (int i= 0 ; i < self.num; i++) {
[listInfostring appendString:self.listArray[i]];
[listInfostring appendString:@"\n"];
}
self.listInfoTextView.text = listInfostring;
}
如果用實例對象調用實例方法,會到實例的isa指針指向的對象(也就是類對象)操作。
如果調用的是類方法,就會到類對象的isa指針指向的對象(也就是元類對象)中操作。
1.首先,在相應操作的對象中的緩存方法列表中找調用的方法,如果找到,轉向相應實現并執行。
2.如果沒找到,在相應操作的對象中的方法列表中找調用的方法,如果找到,轉向相應實現執行
3.如果沒找到,去父類指針所指向的對象中執行1,2.
4.以此類推,如果一直到根類還沒找到,轉向攔截調用。
5.如果沒有重寫攔截調用的方法,程序報錯。
以上的過程給我帶來的啟發:
A.重寫父類的方法,并沒有覆蓋掉父類的方法,只是在當前類對象中找到了這個方法后就不會再去父類中找了。
B.如果想調用已經重寫過的方法的父類的實現,只需使用super這個編譯器標識,它會在運行時跳過在當前的類對象中尋找方法的過程。
動態添加方法
攔截調用:在方法調用中說到了,如果沒有找到方法就會轉向攔截調用。那么什么是攔截調用呢。攔截調用就是,在找不到調用的方法程序崩潰之前,你有機會通過重寫NSObject的四個方法來處理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后兩個方法需要轉發到其他的類處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
- 第一個方法是當你調用一個不存在的類方法的時候,會調用這個方法,默認返回NO,你可以加上自己的處理然后返回YES。
- 第二個方法和第一個方法相似,只不過處理的是實例方法。
- 第三個方法是將你調用的不存在的方法重定向到一個其他聲明了這個方法的類,只需要你返回一個有這個方法的target。
- 第四個方法是將你調用的不存在的方法打包成NSInvocation傳給你。做完你自己的處理后,調用invokeWithTarget:方法讓某個target觸發這個方法。
攔截調用之后有兩種方式,動態添加方法。一種是C語言方式,一種是OC方式。
采用C語言的方式
//在控制器中調用并不存在的實例方法,只能像下面這樣隱式調用,直接調用會報錯[self.stu dadoudou];
[self.stu performSelector:@selector(dadoudou) withObject:@"test"];
//在Student.m中
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"dadoudou"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}
void runAddMethod(id self,SEL _cmd,NSString *string)
{
NSLog(@"add C IMP:%@",string);
}
采用OC方式
[self.stu performSelector:@selector(dadoudou) withObject:@"test"];
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if ([NSStringFromSelector(sel) isEqualToString:@"dadoudou"]) {
class_addMethod(self, sel, [self instanceMethodForSelector:@selector(jump)], "v@:*");
}
return YES;
}
-(void)jump
{
NSLog(@"---->%@",NSStringFromSelector(_cmd));
}
其中class_addMethod的四個參數分別是:
Class cls 給哪個類添加方法,本例中是self
SEL name 添加的方法,本例中是重寫的攔截調用傳進來的selector。
IMP imp 方法的實現,C方法的方法實現可以直接獲得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;獲得方法的實現。
"v@:*"方法的簽名,代表有一個參數的方法。
關聯對象
現在你準備用一個系統的類,但是系統的類并不能滿足你的需求,你需要額外添加一個屬性。這種情況的一般解決辦法就是繼承。但是,只增加一個屬性,就去繼承一個類,總是覺得太麻煩類。這個時候,runtime的關聯屬性就發揮它的作用了。
//首先定義一個全局變量,用它的地址作為關聯對象的key
static char associateObjectKey;
NSString *testString = @"111";
//設置關聯對象
objc_setAssociatedObject(testString, &associateObjectKey, @"添加字符串屬性", OBJC_ASSOCIATION_RETAIN);
NSString *string = objc_getAssociatedObject(testString, &associateObjectKey);
NSLog(@"AssociateObject = %@",string);
objc_setAssociatedObject的四個參數:
id object給誰設置關聯對象。
const void *key關聯對象唯一的key,獲取時會用到。
id value關聯對象。
objc_AssociationPolicy關聯策略,有以下幾種策略:
enum {
OBJC_ASSOCIATION_ASSIGN = 0,
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
OBJC_ASSOCIATION_RETAIN = 01401,
OBJC_ASSOCIATION_COPY = 01403
};
objc_getAssociatedObject的兩個參數。
id object獲取誰的關聯對象。
const void *key根據這個唯一的key獲取關聯對象。
以把添加和獲取關聯對象的方法寫在你需要用到這個功能的類的類別中,方便使用。新建一個NSString+Feature.h的類別;
#import "NSString+Feature.h"
#import "objc/runtime.h"
@implementation NSString (Feature)
//添加關聯對象
-(void)addAssociatedObject:(id)object
{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN);
}
//獲取關聯對象
-(id)getAssociatedObject
{
return objc_getAssociatedObject(self,_cmd);
}
@end
NSString *str0 = @"222";
[str0 addAssociatedObject:@"333"];
NSLog(@"1111111%@",str0);
str0 = [str0 getAssociatedObject];
NSLog(@"2222222%@",str0);
方法交換
方法交換,顧名思義,就是將兩個方法的實現交換。例如,將A方法和B方法交換,調用A方法的時候,就會執行B方法中的代碼,反之亦然。
#import "UIViewController+swizzling.h"
#import @implementation UIViewController (swizzling)
//load方法會在類第一次加載的時候被調用
//調用的時間比較靠前,適合在這個方法里做方法交換
+ (void)load{
//方法交換應該被保證,在程序中只會執行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//獲得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己實現的將要被交換的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
//兩個方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
//首先動態添加方法,實現是被交換的方法,返回值表示添加成功還是失敗
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,說明類中不存在這個方法的實現
//將被交換方法的實現替換到這個并不存在的實現
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否則,交換兩個方法的實現
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
- (void)swiz_viewWillAppear:(BOOL)animated{
//這時候調用自己,看起來像是死循環
//但是其實自己的實現已經被替換了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}
在一個自己定義的viewController中重寫viewWillAppear
//替換系統的方法
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
先打印swizzle,后打印viewWillAppear。