一汁果、Runtime簡(jiǎn)介
RunTime簡(jiǎn)稱(chēng)運(yùn)行時(shí)。OC就是運(yùn)行時(shí)機(jī)制唉堪,也就是在運(yùn)行時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制肩民。
對(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ù)的名稱(chēng)找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用工窍。
事實(shí)證明:
在編譯階段占调,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ò)纵苛。
二剿涮、Runtime作用
1.發(fā)送消息
方法調(diào)用的本質(zhì),就是讓對(duì)象發(fā)送消息攻人。
objc_msgSend,只有對(duì)象才能發(fā)送消息取试,因此以objc開(kāi)頭.
使用消息機(jī)制前提,必須導(dǎo)入#import
消息機(jī)制簡(jiǎn)單使用
clang -rewrite-objc main.m 查看最終生成代碼
// 創(chuàng)建person對(duì)象
Person *p = [[Person alloc] init];
// 調(diào)用對(duì)象方法
[p eat];
// 本質(zhì):讓對(duì)象發(fā)送消息
objc_msgSend(p, @selector(eat));
// 調(diào)用類(lèi)方法的方式:兩種
// 第一種通過(guò)類(lèi)名調(diào)用
[Person eat];
// 第二種通過(guò)類(lèi)對(duì)象調(diào)用
[[Person class] eat];
// 用類(lèi)名調(diào)用類(lèi)方法怀吻,底層會(huì)自動(dòng)把類(lèi)名轉(zhuǎn)換成類(lèi)對(duì)象調(diào)用
// 本質(zhì):讓類(lèi)對(duì)象發(fā)送消息
objc_msgSend([Person class], @selector(eat));
消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)
2.交換方法
開(kāi)發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠瞬浓,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能蓬坡。
方式一:繼承系統(tǒng)的類(lèi)猿棉,重寫(xiě)方法.
方式二:使用runtime,交換方法.
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// 需求:給imageNamed方法提供功能磅叛,每次加載圖片就判斷下圖片是否加載成功。
// 步驟一:先搞個(gè)分類(lèi)萨赁,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
// 步驟二:交換imageNamed和imageWithName的實(shí)現(xiàn)弊琴,就能調(diào)用imageWithName,間接調(diào)用imageWithName的實(shí)現(xiàn)杖爽。
UIImage *image = [UIImage imageNamed:@"123"];
}
@end
@implementation UIImage (Image)
// 加載分類(lèi)到內(nèi)存的時(shí)候調(diào)用
+ (void)load
{
// 交換方法
// 獲取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 獲取imageWithName方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交換方法地址敲董,相當(dāng)于交換實(shí)現(xiàn)方式
method_exchangeImplementations(imageWithName, imageName);
}
// 不能在分類(lèi)中重寫(xiě)系統(tǒng)方法imageNamed,因?yàn)闀?huì)把系統(tǒng)的功能給覆蓋掉慰安,而且分類(lèi)中不能調(diào)用super.
// 既能加載圖片又能打印
+ (instancetype)imageWithName:(NSString *)name
{
// 這里調(diào)用imageWithName腋寨,相當(dāng)于調(diào)用imageName
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"加載空的圖片");
}
return image;
}
@end
3.動(dòng)態(tài)添加方法
開(kāi)發(fā)使用場(chǎng)景:如果一個(gè)類(lèi)方法非常多,加載類(lèi)到內(nèi)存的時(shí)候也比較耗費(fèi)資源化焕,需要給每個(gè)方法生成映射表精置,可以使用動(dòng)態(tài)給某個(gè)類(lèi),添加方法解決锣杂。
經(jīng)典面試題:有沒(méi)有使用performSelector脂倦,其實(shí)主要想問(wèn)你有沒(méi)有動(dòng)態(tài)添加過(guò)方法。
簡(jiǎn)單使用
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *p = [[Person alloc] init];
// 默認(rèn)person元莫,沒(méi)有實(shí)現(xiàn)eat方法赖阻,可以通過(guò)performSelector調(diào)用,但是會(huì)報(bào)錯(cuò)踱蠢。
// 動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
[p performSelector:@selector(eat)];
}
@end
@implementation Person
// void(*)()
// 默認(rèn)方法都有兩個(gè)隱式參數(shù)火欧,
void eat(id self,SEL sel)
{
NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}
// 當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過(guò)來(lái).
// 剛好可以用來(lái)判斷茎截,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(eat)) {
// 動(dòng)態(tài)添加eat方法
// 第一個(gè)參數(shù):給哪個(gè)類(lèi)添加方法
// 第二個(gè)參數(shù):添加方法的方法編號(hào)
// 第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址)
// 第四個(gè)參數(shù):函數(shù)的類(lèi)型苇侵,(返回值+參數(shù)類(lèi)型) v:void @:對(duì)象->self :表示SEL->_cmd
class_addMethod(self, @selector(eat), eat, "v@:");
}
return [super resolveInstanceMethod:sel];
}
@end
4.給分類(lèi)添加屬性
原理:給一個(gè)類(lèi)聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類(lèi)添加關(guān)聯(lián)企锌,并不是直接把這個(gè)值的內(nèi)存空間添加到類(lèi)存空間榆浓。
@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.// 給系統(tǒng)NSObject類(lèi)動(dòng)態(tài)添加屬性nameNSObject *objc = [[NSObject alloc] init];objc.name= @"小碼哥";NSLog(@"%@",objc.name);}@end// 定義關(guān)聯(lián)的keystatic const char *key = "name";@implementation NSObject (Property)- (NSString *)name{// 根據(jù)關(guān)聯(lián)的key,獲取關(guān)聯(lián)的值撕攒。return objc_getAssociatedObject(self, key);}- (void)setName:(NSString *)name{// 第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)// 第二個(gè)參數(shù):關(guān)聯(lián)的key陡鹃,通過(guò)這個(gè)key獲取// 第三個(gè)參數(shù):關(guān)聯(lián)的value// 第四個(gè)參數(shù):關(guān)聯(lián)的策略objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}@end
5.字典轉(zhuǎn)模型
設(shè)計(jì)模型:字典轉(zhuǎn)模型的第一步
模型屬性,通常需要跟字典中的key一一對(duì)應(yīng)
問(wèn)題:一個(gè)一個(gè)的生成模型屬性抖坪,很慢萍鲸?
需求:能不能自動(dòng)根據(jù)一個(gè)字典,生成對(duì)應(yīng)的屬性擦俐。
解決:提供一個(gè)分類(lèi)脊阴,專(zhuān)門(mén)根據(jù)字典生成對(duì)應(yīng)的屬性字符串。
@implementation NSObject (Log)
// 自動(dòng)打印屬性字符串
+ (void)resolveDict:(NSDictionary *)dict{
// 拼接屬性字符串代碼
NSMutableString *strM = [NSMutableString string];
// 1.遍歷字典,把字典中的所有key取出來(lái)嘿期,生成對(duì)應(yīng)的屬性代碼
[dict enumerateKeysAndObjectsUsingBlock:^(id? _Nonnull key, id? _Nonnull obj, BOOL * _Nonnull stop) {
// 類(lèi)型經(jīng)常變品擎,抽出來(lái)
NSString *type;
if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
type = @"NSString";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
type = @"NSArray";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
type = @"int";
}else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
type = @"NSDictionary";
}
// 屬性字符串
NSString *str;
if ([type containsString:@"NS"]) {
str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
}else{
str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
}
// 每生成屬性字符串,就自動(dòng)換行秽五。
[strM appendFormat:@"\n%@\n",str];
}];
// 把拼接好的字符串打印出來(lái)孽查,就好了饥悴。
NSLog(@"%@",strM);
}
@end
字典轉(zhuǎn)模型的方式一:KVC
@implementation Status
+ (instancetype)statusWithDict:(NSDictionary *)dict
{
Status *status = [[self alloc] init];
[status setValuesForKeysWithDictionary:dict];
return status;
}
@end
KVC字典轉(zhuǎn)模型弊端:必須保證坦喘,模型中的屬性和字典中的key一一對(duì)應(yīng)。
如果不一致西设,就會(huì)調(diào)用[ setValue:forUndefinedKey:]報(bào)key找不到的錯(cuò)瓣铣。
分析:模型中的屬性和字典的key不一一對(duì)應(yīng),系統(tǒng)就會(huì)調(diào)用setValue:forUndefinedKey:報(bào)錯(cuò)贷揽。
解決:重寫(xiě)對(duì)象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋棠笑, 就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了禽绪。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
字典轉(zhuǎn)模型的方式二:Runtime
思路:利用運(yùn)行時(shí)蓖救,遍歷模型中所有屬性,根據(jù)模型的屬性名印屁,去字典中查找key循捺,取出對(duì)應(yīng)的值,給模型的屬性賦值雄人。
步驟:提供一個(gè)NSObject分類(lèi)从橘,專(zhuān)門(mén)字典轉(zhuǎn)模型,以后所有模型都可以通過(guò)這個(gè)分類(lèi)轉(zhuǎn)础钠。
@implementation?ViewController
-?(void)viewDidLoad?{
[super?viewDidLoad];
//?Do?any?additional?setup?after?loading?the?view,?typically?from?a?nib.
//?解析Plist文件
NSString?*filePath?=?[[NSBundle?mainBundle]?pathForResource:@"status.plist"?ofType:nil];
NSDictionary?*statusDict?=?[NSDictionary?dictionaryWithContentsOfFile:filePath];
//?獲取字典數(shù)組
NSArray?*dictArr?=?statusDict[@"statuses"];
//?自動(dòng)生成模型的屬性字符串
//? ? [NSObject?resolveDict:dictArr[0][@"user"]];
_statuses?=?[NSMutableArray?array];
//?遍歷字典數(shù)組
for?(NSDictionary?*dict?in?dictArr)?{
Status?*status?=?[Status?modelWithDict:dict];
[_statuses?addObject:status];
}
//?測(cè)試數(shù)據(jù)
NSLog(@"%@?%@",_statuses,[_statuses[0]?user]);
}
@end
@implementation?NSObject?(Model)
+?(instancetype)modelWithDict:(NSDictionary?*)dict
{
//?思路:遍歷模型中所有屬性-》使用運(yùn)行時(shí)
//?0.創(chuàng)建對(duì)應(yīng)的對(duì)象
id?objc?=?[[self?alloc]?init];
//?1.利用runtime給對(duì)象中的成員屬性賦值
//?class_copyIvarList:獲取類(lèi)中的所有成員屬性
//?Ivar:成員屬性的意思
//?第一個(gè)參數(shù):表示獲取哪個(gè)類(lèi)中的成員屬性
//?第二個(gè)參數(shù):表示這個(gè)類(lèi)有多少成員屬性恰力,傳入一個(gè)Int變量地址,會(huì)自動(dòng)給這個(gè)變量賦值
//?返回值Ivar?*:指的是一個(gè)ivar數(shù)組旗吁,會(huì)把所有成員屬性放在一個(gè)數(shù)組中踩萎,通過(guò)返回的數(shù)組就能全部獲取到。
/*?類(lèi)似下面這種寫(xiě)法
Ivar?ivar;
Ivar?ivar1;
Ivar?ivar2;
//?定義一個(gè)ivar的數(shù)組a
Ivar?a[]?=?{ivar,ivar1,ivar2};
//?用一個(gè)Ivar?*指針指向數(shù)組第一個(gè)元素
Ivar?*ivarList?=?a;
//?根據(jù)指針訪問(wèn)數(shù)組第一個(gè)元素
ivarList[0];
*/
unsigned?int?count;
//?獲取類(lèi)中的所有成員屬性
Ivar?*ivarList?=?class_copyIvarList(self,?&count);
for?(int?i?=?0;?i?<?count;?i++)?{
//?根據(jù)角標(biāo)很钓,從數(shù)組取出對(duì)應(yīng)的成員屬性
Ivar?ivar?=?ivarList[i];
//?獲取成員屬性名
NSString?*name?=?[NSString?stringWithUTF8String:ivar_getName(ivar)];
//?處理成員屬性名->字典中的key
//?從第一個(gè)角標(biāo)開(kāi)始截取
NSString?*key?=?[name?substringFromIndex:1];
//?根據(jù)成員屬性名去字典中查找對(duì)應(yīng)的value
id?value?=?dict[key];
//?二級(jí)轉(zhuǎn)換:如果字典中還有字典驻民,也需要把對(duì)應(yīng)的字典轉(zhuǎn)換成模型
//?判斷下value是否是字典
if?([value?isKindOfClass:[NSDictionary?class]])?{
//?字典轉(zhuǎn)模型
//?獲取模型的類(lèi)對(duì)象,調(diào)用modelWithDict
//?模型的類(lèi)名已知履怯,就是成員屬性的類(lèi)型
//?獲取成員屬性類(lèi)型
NSString?*type?=?[NSString?stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//?生成的是這種@"@\"User\""?類(lèi)型?-》?@"User"? 在OC字符串中?\"?->?"回还,\是轉(zhuǎn)義的意思,不占用字符
//?裁剪類(lèi)型字符串
NSRange?range?=?[type?rangeOfString:@"\""];
type?=?[type?substringFromIndex:range.location?+?range.length];
range?=?[type?rangeOfString:@"\""];
//?裁剪到哪個(gè)角標(biāo)叹洲,不包括當(dāng)前角標(biāo)
type?=?[type?substringToIndex:range.location];
//?根據(jù)字符串類(lèi)名生成類(lèi)對(duì)象
Class?modelClass?=?NSClassFromString(type);
if?(modelClass)?{?//?有對(duì)應(yīng)的模型才需要轉(zhuǎn)
//?把字典轉(zhuǎn)模型
value? =? [modelClass?modelWithDict:value];
}
}
//?三級(jí)轉(zhuǎn)換:NSArray中也是字典柠硕,把數(shù)組中的字典轉(zhuǎn)換成模型.
//?判斷值是否是數(shù)組
if?([value?isKindOfClass:[NSArray?class]])?{
//?判斷對(duì)應(yīng)類(lèi)有沒(méi)有實(shí)現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
if?([self?respondsToSelector:@selector(arrayContainModelClass)])?{
//?轉(zhuǎn)換成id類(lèi)型,就能調(diào)用任何對(duì)象的方法
id?idSelf?=?self;
//?獲取數(shù)組中字典對(duì)應(yīng)的模型
NSString?*type?=? [idSelf?arrayContainModelClass][key];
//?生成模型
Class?classModel?=?NSClassFromString(type);
NSMutableArray?*arrM?=?[NSMutableArray?array];
//?遍歷字典數(shù)組,生成模型數(shù)組
for?(NSDictionary?*dict?in?value)?{
//?字典轉(zhuǎn)模型
id?model?=? [classModel?modelWithDict:dict];
[arrM?addObject:model];
}
//?把模型數(shù)組賦值給value
value?=?arrM;
}
}
if?(value)?{?//?有值蝗柔,才需要給模型的屬性賦值
//?利用KVC給模型中的屬性賦值
[objc?setValue:value?forKey:key];
}
}
return?objc;
}
@end