Runtime是什么?見(jiàn)名知意,其概念無(wú)非就是“因?yàn)?code>Objective-C是一門動(dòng)態(tài)語(yǔ)言,所以它需要一個(gè)運(yùn)行時(shí)系統(tǒng)……這就是Runtime系統(tǒng)”云云。對(duì)博主這種菜鳥(niǎo)而言,Runtime在實(shí)際開(kāi)發(fā)中,其實(shí)就是一組C語(yǔ)言的函數(shù)。大家可以簡(jiǎn)單的認(rèn)為就是幾個(gè)C語(yǔ)言的api。 只是我們可以用這些方法干些蘋果爸爸不讓我們干的事情。
一: 打開(kāi)xcode對(duì)runtime的限制
二: runtime簡(jiǎn)介
三: runtime發(fā)送消息功能實(shí)現(xiàn)
四: runtime交換方法
五: runtime動(dòng)態(tài)添加方法
六: runtime分類增加屬性
參考資料
一: 打開(kāi)xcode對(duì)runtime的限制
但是從Xcode6之后,蘋果不推薦我們使用runtime,所以就取消了參數(shù)提示功能。估計(jì)是不想讓我們過(guò)多的了解底層。
Xcode6之后我們運(yùn)用objc_msgSend函數(shù)是這樣的:
從圖中很明顯可以看出來(lái)是沒(méi)有參數(shù)提示的。
我們可以通過(guò)對(duì)Xcode進(jìn)行配置進(jìn)行更改。更改如圖:

操作完成后再敲原來(lái)的函數(shù)就有參數(shù)提示功能了。
二: runtime簡(jiǎn)介
- 1: RunTime簡(jiǎn)稱運(yùn)行時(shí)。OC就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制。
- 2: 對(duì)于C語(yǔ)言,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)。對(duì)于OC的函數(shù),屬于動(dòng)態(tài)調(diào)用過(guò)程,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù),只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
- 3: 在編譯階段,OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn),只要聲明過(guò)就不會(huì)報(bào)錯(cuò)。在編譯階段,C語(yǔ)言調(diào)用未實(shí)現(xiàn)的函數(shù)就會(huì)報(bào)錯(cuò)。
三: 發(fā)送消息(objc_msgSend)
- 方法調(diào)用的本質(zhì),就是讓對(duì)象發(fā)送消息。
-
objc_msgSend,只有對(duì)象才能發(fā)送消息,因此以objc開(kāi)頭. - 使用消息機(jī)制前提,必須導(dǎo)入
#import <objc/message.h>
消息機(jī)制簡(jiǎn)單使用
XZPerson.h
#import <Foundation/Foundation.h>
@interface XZPerson : NSObject
// 對(duì)象方法
- (void)instanceMethodEat:(NSString *)foodName;
// 類方法
+ (void)classMethodEat:(NSString *)foodName;
@end
XZPerson.m
#import "XZPerson.h"
@implementation XZPerson
// 對(duì)象方法
- (void)instanceMethodEat:(NSString *)foodName{
NSLog(@"instanceMethodEat...%@", foodName);
}
+ (void)classMethodEat:(NSString *)foodName{
NSLog(@"classMethodEat...%@", foodName);
}
@end
ViewController.m
#import "ViewController.h"
#import "XZPerson.h"
#import <objc/message.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
XZPerson *p = [[XZPerson alloc] init];
[p instanceMethodEat:@"對(duì)象-麻辣魚"];
// objc_msgSend 對(duì)象方法的調(diào)用
objc_msgSend(p, @selector(instanceMethodEat:), @"對(duì)象-西瓜皮");
// objc_msgSend 類方法的調(diào)用
objc_msgSend([XZPerson class], @selector(classMethodEat:), @"類-麻婆豆腐");
}
@end
結(jié)果
XZRuntime[14251:707049] instanceMethodEat...對(duì)象-麻辣魚
XZRuntime[14251:707049] instanceMethodEat...對(duì)象-西瓜皮
XZRuntime[14251:707049] classMethodEat...類-麻婆豆腐
消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)

四: runtime交換方法
開(kāi)發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能。
方式一:繼承系統(tǒng)的類,重寫方法.
方式二:使用runtime,交換方法.
需求是: [UIImage imageNamed:@""]這個(gè)方法很常見(jiàn), 但是會(huì)遇到一種尷尬的問(wèn)題, 1:沒(méi)有輸入圖片的名字 2: 素材不合法 導(dǎo)致UIImageView不可以正常展示
4.1: 使用分類的方法寫一個(gè)名字為+(UIImage *)ImageNamed:(NSString *)name方法, 覆蓋原有的方法 (不可以實(shí)現(xiàn))
答: // Category is implementing a method which will also be implemented by its primary class
蘋果爸爸不允許分類重寫蘋果庫(kù)提供的方法
4.2: 使用分類的方法寫一個(gè)名字為+(UIImage *)changeImageName:(NSString *)name方法, 在需要的地方調(diào)用 (不可以實(shí)現(xiàn), 麻煩)
UIImage+Extension.h
#import <UIKit/UIKit.h>
@interface UIImage (Extension)
+ (UIImage *)changeImageName:(NSString *)name;
@end
UIImage+Extension.m
#import "UIImage+Extension.h"
#import <objc/message.h>
@implementation UIImage (Extension)
+ (UIImage *)changeImageName:(NSString *)name{
assert(name.length != 0);
UIImage *img = [self changeImageName:name];
assert(img != nil);
return img;
}
@end
可以實(shí)現(xiàn)但是相對(duì)調(diào)用比較復(fù)雜 需要每次引入UIImage+Extension.h, 調(diào)用changeImageName方法
4.3: 使用runtime的交換方法 (可以實(shí)現(xiàn), 簡(jiǎn)單)
我們希望的完美解決方案是, 我們?nèi)耘f調(diào)用'imageNamed'方法, 但是那內(nèi)部實(shí)際調(diào)用changeImageName完成功能的完善. 怎么使用那使用load和method_exchangeImplementations方法
UIImage+Extension.h
#import <UIKit/UIKit.h>
@interface UIImage (Extension)
@end
UIImage+Extension.m
#import "UIImage+Extension.h"
#import <objc/message.h>
@implementation UIImage (Extension)
// 加載分類到內(nèi)存的時(shí)候調(diào)用
+ (void)load{
// 交換方法
// 獲取changeImageName方法地址
Method imageWithName = class_getClassMethod(self, @selector(changeImageName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式
method_exchangeImplementations(imageWithName, imageName);
}
+ (UIImage *)changeImageName:(NSString *)name{
assert(name.length != 0);
UIImage *img = [self changeImageName:name];
assert(img != nil);
return img;
}
@end
注意+ (UIImage *)changeImageName:(NSString *)name中生成image使用changeImageName方法。 防止死循環(huán)。
交換前

交換后

五: runtime動(dòng)態(tài)添加方法
開(kāi)發(fā)使用場(chǎng)景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類,添加方法解決。
經(jīng)典面試題:有沒(méi)有使用performSelector,其實(shí)主要想問(wèn)你有沒(méi)有動(dòng)態(tài)添加過(guò)方法。
XZPerson.h
#import "XZPerson.h"
#import <objc/message.h>
@implementation XZPerson
void run(id self, SEL sel){
NSLog(@"%@--%@", self, NSStringFromSelector(sel));
}
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
// 1: 第一個(gè)參數(shù)給那個(gè)類增加方法
// 2: 第二個(gè)參數(shù): 添加方法的方法編號(hào)
// 3: 添加方法的實(shí)現(xiàn)
// 4: 函數(shù)的類型, (返回值+參數(shù)類型) v:void @:對(duì)象->self :表示SEL->_cmd)
class_addMethod(self, @selector(run), run, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
#import "ViewController.h"
#import "XZPerson.h"
#import <objc/message.h>
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
XZPerson *p = [[XZPerson alloc] init];
[p performSelector:@selector(run)];
}
@end
六: 給分類添加屬性
原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。
XZPerson.h
#import "XZPerson.h"
@interface XZPerson (Category)
@property (nonatomic, copy)NSString *xzName;
@end
XZPerson.m
#import "XZPerson+Category.h"
#import <objc/message.h>
#define NAME "xzName"
@implementation XZPerson (Category)
- (NSString *)xzName{
return objc_getAssociatedObject(self, NAME);
}
- (void)setXzName:(NSString *)name{
return objc_setAssociatedObject(self, NAME, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end
ViewController.m
#import "ViewController.h"
#import "XZPerson.h"
#import <objc/message.h>
#import "XZPerson+Category.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
XZPerson *p = [[XZPerson alloc] init];
p.xzName = @"啊哈";
NSLog(@"打印名字:%@", p.xzName);
}
@end