OC語言中最為強(qiáng)大的莫過于OC的運(yùn)行時(shí)機(jī)制-Runtime,但因其比較接近底層,一旦使用Runtime出現(xiàn)bug,將很難調(diào)試,所以Runtime在開發(fā)中能不用就不用.下面我將介紹一些Runtime在開發(fā)中的使用,已經(jīng)面試可能遇見的面試題.
1.OC語法和Runtime語法的區(qū)別
OC語法和Runtime語法的區(qū)別,換而言之就是OC中我們寫的語句,最終被轉(zhuǎn)換成Runtime中什么樣語句.由于Xcode6之后,蘋果不建議使用Runtime,也就是現(xiàn)在在編譯的時(shí)候,runtime的函數(shù)不會(huì)提示,需要去配置一下:
// 配置步驟: build Seting -> 搜索msg -> 設(shè)置成NO
創(chuàng)建一個(gè)控制臺(tái)程序,在自動(dòng)釋放池中寫如下代碼:
NSObject *objc = [NSObject alloc];
? ? ? ? objc = [objc init];
然后切換到終端命令行,執(zhí)行以下步驟:
cd 切換到你想生成的那個(gè)根文件的上一級(jí)目錄
clang -rewrite-objc main.m// clang -rewrite-objc 目標(biāo)文件
會(huì)在該目錄文件下生成一個(gè).cpp文件,打開之后搜索@autoreleasepool(這也就是當(dāng)時(shí)為什么創(chuàng)建控制器程序的原因,好查找轉(zhuǎn)換后的代碼在哪兒),就會(huì)找到轉(zhuǎn)換后的代碼:??
NSObject *objc = ((NSObject *(*)(id, SEL))(void*)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
objc = ((NSObject *(*)(id, SEL))(void*)objc_msgSend)((id)objc, sel_registerName("init"));
上面的代碼比較原生態(tài),我們要是直接寫runtime的代碼如下所示,就能達(dá)到創(chuàng)建一個(gè)NSObject對(duì)象的目的:
// objc_msgSend: 兩個(gè)參數(shù)? 1. 誰發(fā)送這個(gè)消息? 2.? 發(fā)送給誰NSObject *objc =? objc_msgSend([NSObjectclass], @selector(alloc));
? ? ? ? objc = objc_msgSend(objc, @selector(init));
2.消息機(jī)制,調(diào)用私有方法
?面試題: ?runtime是什么?或者是同類的
答: 其實(shí)runtime就是運(yùn)行時(shí)機(jī)制,可以通過命令行clang -rewrite-objc 對(duì)應(yīng)的目標(biāo)文件,就能將對(duì)應(yīng)的OC的代碼轉(zhuǎn)成對(duì)應(yīng)的運(yùn)行時(shí)的代碼
若是面試官問runtime中是怎么找到對(duì)應(yīng)的方法的,該怎么回答?
答: 首先確定問的是對(duì)象方法還是類方法,對(duì)象方法保存到類中,類方法保存到元類(meta class),每一個(gè)類都有方法列表methodList,每一個(gè)方法在方法列表中都有對(duì)應(yīng)的方法編號(hào).(1)根據(jù)對(duì)象的isa去對(duì)應(yīng)的類查找方法,isa: 判斷去哪個(gè)類找對(duì)應(yīng)的方法,指向方法調(diào)用的類 (2)根據(jù)傳入的方法編號(hào),才能在方法列表中找到對(duì)應(yīng)得方法Method(方法名).(3)根據(jù)方法名(函數(shù)入口)找到函數(shù)實(shí)現(xiàn)
知識(shí)擴(kuò)充: 其實(shí)每個(gè)方法最終轉(zhuǎn)換成函數(shù)的形式,存放在方法區(qū),而每一個(gè)函數(shù)的函數(shù)名都是函數(shù)的入口
訪問類中私有方法的代碼如下:
在對(duì)應(yīng)類中的@implementation實(shí)現(xiàn)私有方法:
#import"Person.h"@implementation Person- (void)eat {
? ? NSLog(@"吃吃吃");
}- (void)run: (int)num {
? ? NSLog(@"跑了%d米", num);
}@end
?在ViewController.m中的代碼如下:
#import"ViewController.h"#import"Person.h"#import/*? ? runtime: 千萬不要隨便使用,不得已才使用
消息機(jī)制:
1. 裝逼
2. 調(diào)用已知私有的方法
*/@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {
? ? Person *p = objc_msgSend([Personclass], @selector(alloc));
? ? p = objc_msgSend(p, @selector(init));
? ? //? ? objc_msgSend(p, @selector(eat));objc_msgSend(p, @selector(run:),20);
}@end
注意: 一定要導(dǎo)入runtime的頭文件 :
#include 或者#import
?3.runtime方法交換
需求1: 我現(xiàn)在有一個(gè)項(xiàng)目,已經(jīng)開發(fā)了兩年,之前都是用UIImage中的imageNamed去加載圖片,但是組長(zhǎng)現(xiàn)在想imageNamed,給我提示是否加載成功.
思想1:在分類實(shí)現(xiàn)該方法.(但這種方法會(huì)把系統(tǒng)的方法覆蓋,一般不采用)
思想2: 自定義一個(gè)Image類,為什么不采用這種方法(這里你就要明白什么時(shí)候需要自定義,系統(tǒng)功能不完善,就定義這樣一個(gè)類,去擴(kuò)展這個(gè)類)
前兩種方法都有一定的局限性,若是項(xiàng)目開發(fā)很久了,就需要更改好多東西,利用runtime交換方法實(shí)現(xiàn)的作用,可以簡(jiǎn)單的實(shí)現(xiàn)這個(gè)需求
這個(gè)時(shí)候不得不用runtime去交換方法
分類中代碼如下UIImage+image.h
#import@interface UIImage (image)+ (UIImage *)BO_imageNamed:(NSString *)name;@end
分類中代碼如下UIImage+image.m
#import"UIImage+image.h"#import@implementation UIImage (image)//如果當(dāng)前類中東西僅且只需加載一次,一般放在load中.當(dāng)然也可以放在initialize中,需要進(jìn)行判斷調(diào)用該類的是的類的類型// 加載類的時(shí)候會(huì)調(diào)用,僅且調(diào)用一次+ (void)load {
? ? ? ? // 首先要拿到要交換的兩個(gè)方法Method method1 = class_getClassMethod([UIImageclass], @selector(BO_imageNamed:));
? ? Method method2 = class_getClassMethod([UIImageclass], @selector(imageNamed:));
? ? method_exchangeImplementations(method1, method2);
}// 加載當(dāng)前類或者子類時(shí)候.會(huì)調(diào)用.可能會(huì)調(diào)用不止一次+ (void)initialize? {
}// 在系統(tǒng)方法的之前加前綴名的作用,防止覆蓋系統(tǒng)方法,有開發(fā)經(jīng)驗(yàn)的人默認(rèn)的+ (UIImage *)BO_imageNamed:(NSString *)name{
? ? // 當(dāng)運(yùn)行到這兒時(shí),這里已經(jīng)是imageNamed中的內(nèi)容,此時(shí)再調(diào)用BO_imageNamed相當(dāng)于原來imageNamed中的內(nèi)容UIImage *image = [self BO_imageNamed:name];
? ? if(image == nil) {
? ? ? ? NSLog(@"照片不存在");
? ? }
? ? return image;
}@end
調(diào)用的代碼如下:
#import"ViewController.h"http://#import "BOImage.h"#import"UIImage+image.h"/*? ? 需求: 不得不用runtime去交換方法
? ? 需求: 想要在調(diào)用imageNamed,就給我提示,是否加載成功
? ? 需求: 讓UIImage調(diào)用imageNamed有這個(gè)功能
? ? 需求: 比如我有一個(gè)項(xiàng)目,已經(jīng)開發(fā)兩年,之前都是用UIImage去加載圖片.組長(zhǎng)現(xiàn)在想調(diào)用imageNamed,就給我提示,是否加載成功
? ? 注意: 在分類中一定不要重寫系統(tǒng)方法,否則就把系統(tǒng)方法干掉了
? ? 思想: 什么時(shí)候需要自定義,系統(tǒng)功能不完善,就定義一個(gè)這樣的類,去擴(kuò)展這個(gè)類
//? ? 前兩種方法都有一定的局限性,若是項(xiàng)目開發(fā)很久了,則需要更改好多東西,利用runtime交換方法實(shí)現(xiàn)的作用.可以簡(jiǎn)單的實(shí)現(xiàn)這個(gè)需求
*/@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? //? ? [BOImage imageNamed:@"123"];[UIImage BO_imageNamed:@"123"];
}- (void)didReceiveMemoryWarning {
? ? [super didReceiveMemoryWarning];
? ? // Dispose of any resources that can be recreated.}@end
4: 動(dòng)態(tài)添加方法
應(yīng)用場(chǎng)景:
為什么動(dòng)態(tài)添加方法?OC中是懶加載,有的方法可能很久不會(huì)調(diào)用,例如: 電商,視頻,社交,收費(fèi)項(xiàng)目,會(huì)員機(jī)制,只有會(huì)員才擁有這些動(dòng)能
下面是道美團(tuán)面試題:
面試官問: 有沒有使用過performSelector----->其實(shí)這里面試官想問的是你有沒有動(dòng)態(tài)的添加過方法
這里就應(yīng)該這樣答: 使用過--->什么時(shí)候使用----動(dòng)態(tài)添加方法的時(shí)候使用--->為什么動(dòng)態(tài)添加方法---又回到到上面說的什么時(shí)候動(dòng)態(tài)添加方法.
代碼如下:
#import"Person.h"#import@implementation Personvoideat(id self, SEL _cmd) {
? ? NSLog(@"我終于成功了");
}// 動(dòng)態(tài)添加實(shí)例方法//resolveInstanceMethod 什么時(shí)候調(diào)用?只要調(diào)用沒有實(shí)現(xiàn)的方法,就會(huì)產(chǎn)生方法去解決,這個(gè)方法有什么作用: 去解決沒有實(shí)現(xiàn)方法,動(dòng)態(tài)添加方法+ (BOOL)resolveInstanceMethod:(SEL)sel {
? ? if(sel == @selector(eat)) {
? ? ? ? /**
? ? ? ? 給一個(gè)類添加方法
? ? ? ? @param self 給誰添加方法
? ? ? ? @param sel 添加那個(gè)方法
? ? ? ? @param IMP 方法實(shí)現(xiàn),函數(shù)入口
? ? ? ? @return 方法類型
? ? ? ? */? ? ? ? class_addMethod(self, sel, (IMP)eat, "v@:");
? ? }
? ? return [super resolveInstanceMethod:sel];
}// 動(dòng)態(tài)添加類方法//+ (BOOL)resolveClassMethod:(SEL)sel {////}@end// 下面是各個(gè)字母代表的參數(shù)//c? A char//i? An int//s? A short//l? A long//l? 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)
控制器中方法如下:
#import"ViewController.h"#import"Person.h"/*? ? 動(dòng)態(tài)添加方法:
? ? 為什么動(dòng)態(tài)添加方法? OC都是懶加載,有些方法可能很久不會(huì)調(diào)用.例如: 電商,視頻,社交,收費(fèi)項(xiàng)目,會(huì)員機(jī)制,只有會(huì)員才擁有這些動(dòng)能
美團(tuán)面試題 : 有沒有使用過performSelector,使用,什么時(shí)候使用,動(dòng)態(tài)添加方法的時(shí)候使用,為什么動(dòng)態(tài)添加方法?
OC都是懶加載,有些方法可能很久不會(huì)調(diào)用.例如: 電商,視頻,社交,收費(fèi)項(xiàng)目,會(huì)員機(jī)制,只有會(huì)員才擁有這些動(dòng)能
*/@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? Person *p = [[Person alloc] init];
? ? [p performSelector:@selector(eat)];
}- (void)didReceiveMemoryWarning {
? ? [super didReceiveMemoryWarning];
? ? // Dispose of any resources that can be recreated.}@end
5.動(dòng)態(tài)添加屬性
理論上在分類中@property的作用: 僅僅是生成get,set方法的聲明,并不會(huì)生成get,set方法實(shí)現(xiàn),并不會(huì)生成下劃線屬性
動(dòng)態(tài)添加方法實(shí)現(xiàn)思路: 在分類中用@property添加set,get方法之后,其實(shí)添加屬性就是要把一個(gè)變量跟一個(gè)類聯(lián)系起來.也就是在set和get方法中處理,代碼如下所示.
給NSObject添加一個(gè)name屬性:
分類中代碼 .h:
#import@interface NSObject (Property)// @property 在分類中作用 : 僅僅是生成get,set方法聲明.并不會(huì)生成get,set方法實(shí)現(xiàn),并不會(huì)生成下劃線成員屬性@property NSString *name;@end
.m
#import"NSObject+Property.h"#import@implementation NSObject (Property)- (void)setName:(NSString *)name {
? ? objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}- (NSString *)name {
? ? returnobjc_getAssociatedObject(self,"name");
}@end
控制器中代碼:
#import"ViewController.h"#import"NSObject+Property.h"/*? ? 開發(fā)的時(shí)候,是自己最熟悉什么用什么,而不是什么逼格高用什么,rumtime比較接近底層的語言,不好調(diào)試,盡量少用
? ? 需求: 給NSObject添加一個(gè)name屬性,動(dòng)態(tài)添加屬性 ->runtime
? ? 屬性的本質(zhì): 讓一個(gè)屬性和對(duì)象產(chǎn)生關(guān)聯(lián)
*/@interface ViewController ()@end@implementation ViewController- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? NSObject *objc = [[NSObject alloc] init];
? ? objc.name =@"123";
? ? NSLog(@"%@", objc.name);
}@end
6:利用運(yùn)行時(shí),自己添加屬性
如果一個(gè)字典中,有很多的key,如果你在字典轉(zhuǎn)模型的時(shí)候,逐個(gè)的寫下屬性,將會(huì)非常蛋疼,其實(shí)可以給字典添加一個(gè)分類,利用遍歷字典中key,value,再利用字符串的拼接即可實(shí)現(xiàn).
NSDictionary+propertyCode.h分類中代碼如下:
#import@interface NSDictionary (propertyCode)- (void)createProperty;@end
NSDictionary+propertyCode.m:
#import"NSDictionary+propertyCode.h"@implementation NSDictionary (propertyCode)- (void)createProperty {
? ? [self enumerateKeysAndObjectsUsingBlock:^(id_Nonnull key,id_Nonnull value, BOOL * _Nonnull stop) {
? ? ? ? // 當(dāng)然這里還是可以自己添加其他類型,就不一一列舉if([value isKindOfClass:[NSStringclass]]) {
? ? ? ? ? ? NSLog(@"%@", [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@", key]);
? ? ? ? }elseif([value isKindOfClass:[NSArrayclass]]) {
? ? ? ? ? ? NSLog(@"%@", [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@", key]);
? ? ? ? }elseif([value isKindOfClass:[NSNumberclass]]) {
? ? ? ? ? ? NSLog(@"%@", [NSString stringWithFormat:@"@property (nonatomic, assign) NSInteger key"]);
? ? ? ? }
? ? }];
}@end
控制器中代碼:
#import"ViewController.h"#import"NSDictionary+propertyCode.h"@interface ViewController ()
@property (nonatomic, strong) NSArray *array;@end@implementation ViewController- (NSArray *)array {
? ? if(_array == nil) {
? ? ? ? _array = [NSArray array];
? ? }
? ? return _array;
}- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? // 這里需要拿到一個(gè)plist文件或者一個(gè)設(shè)置一個(gè)字典self.array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"cars.plist" ofType:nil]];
? ? for(NSInteger i =0; i < self.array.count; i++) {
? ? ? ? NSDictionary *dict = self.array[i];
? ? ? ? [dict createProperty];
? ? }//? ? NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"" ofType:nil]];}@end