iOS-Runtime在開發(fā)中的使用及相關(guān)面試題

OC語言中最為強(qiáng)大的莫過于OC的運(yùn)行時機(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)在在編譯的時候,runtime的函數(shù)不會提示,需要去配置一下:

// 配置步驟: build Seting -> 搜索msg -> 設(shè)置成NO

創(chuàng)建一個控制臺程序,在自動釋放池中寫如下代碼:

NSObject *objc = [NSObject alloc];

? ? ? ? objc = [objc init];

然后切換到終端命令行,執(zhí)行以下步驟:

cd 切換到你想生成的那個根文件的上一級目錄

clang -rewrite-objc main.m// clang -rewrite-objc 目標(biāo)文件

會在該目錄文件下生成一個.cpp文件,打開之后搜索@autoreleasepool(這也就是當(dāng)時為什么創(chuàng)建控制器程序的原因,好查找轉(zhuǎn)換后的代碼在哪兒),就會找到轉(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)建一個NSObject對象的目的:

// objc_msgSend: 兩個參數(shù)? 1. 誰發(fā)送這個消息? 2.? 發(fā)送給誰NSObject *objc =? objc_msgSend([NSObjectclass], @selector(alloc));

? ? ? ? objc = objc_msgSend(objc, @selector(init));

2.消息機(jī)制,調(diào)用私有方法

?面試題: ?runtime是什么?或者是同類的

 答: 其實runtime就是運(yùn)行時機(jī)制,可以通過命令行clang -rewrite-objc 對應(yīng)的目標(biāo)文件,就能將對應(yīng)的OC的代碼轉(zhuǎn)成對應(yīng)的運(yùn)行時的代碼

若是面試官問runtime中是怎么找到對應(yīng)的方法的,該怎么回答?

 答: 首先確定問的是對象方法還是類方法,對象方法保存到類中,類方法保存到元類(meta class),每一個類都有方法列表methodList,每一個方法在方法列表中都有對應(yīng)的方法編號.(1)根據(jù)對象的isa去對應(yīng)的類查找方法,isa: 判斷去哪個類找對應(yīng)的方法,指向方法調(diào)用的類 (2)根據(jù)傳入的方法編號,才能在方法列表中找到對應(yīng)得方法Method(方法名).(3)根據(jù)方法名(函數(shù)入口)找到函數(shù)實現(xiàn)

知識擴(kuò)充: 其實每個方法最終轉(zhuǎn)換成函數(shù)的形式,存放在方法區(qū),而每一個函數(shù)的函數(shù)名都是函數(shù)的入口

訪問類中私有方法的代碼如下:

在對應(yīng)類中的@implementation實現(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)在有一個項目,已經(jīng)開發(fā)了兩年,之前都是用UIImage中的imageNamed去加載圖片,但是組長現(xiàn)在想imageNamed,給我提示是否加載成功.

思想1:在分類實現(xiàn)該方法.(但這種方法會把系統(tǒng)的方法覆蓋,一般不采用)

思想2: 自定義一個Image類,為什么不采用這種方法(這里你就要明白什么時候需要自定義,系統(tǒng)功能不完善,就定義這樣一個類,去擴(kuò)展這個類)

前兩種方法都有一定的局限性,若是項目開發(fā)很久了,就需要更改好多東西,利用runtime交換方法實現(xiàn)的作用,可以簡單的實現(xiàn)這個需求


這個時候不得不用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)用該類的是的類的類型// 加載類的時候會調(diào)用,僅且調(diào)用一次+ (void)load {

? ? ? ? // 首先要拿到要交換的兩個方法Method method1 = class_getClassMethod([UIImageclass], @selector(BO_imageNamed:));

? ? Method method2 = class_getClassMethod([UIImageclass], @selector(imageNamed:));

? ? method_exchangeImplementations(method1, method2);

}// 加載當(dāng)前類或者子類時候.會調(diào)用.可能會調(diào)用不止一次+ (void)initialize? {


}// 在系統(tǒng)方法的之前加前綴名的作用,防止覆蓋系統(tǒng)方法,有開發(fā)經(jīng)驗的人默認(rèn)的+ (UIImage *)BO_imageNamed:(NSString *)name{

? ? // 當(dāng)運(yùn)行到這兒時,這里已經(jīng)是imageNamed中的內(nèi)容,此時再調(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有這個功能

? ? 需求: 比如我有一個項目,已經(jīng)開發(fā)兩年,之前都是用UIImage去加載圖片.組長現(xiàn)在想調(diào)用imageNamed,就給我提示,是否加載成功


? ? 注意: 在分類中一定不要重寫系統(tǒng)方法,否則就把系統(tǒng)方法干掉了

? ? 思想: 什么時候需要自定義,系統(tǒng)功能不完善,就定義一個這樣的類,去擴(kuò)展這個類

//? ? 前兩種方法都有一定的局限性,若是項目開發(fā)很久了,則需要更改好多東西,利用runtime交換方法實現(xiàn)的作用.可以簡單的實現(xiàn)這個需求

*/@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: 動態(tài)添加方法

應(yīng)用場景:

  為什么動態(tài)添加方法?OC中是懶加載,有的方法可能很久不會調(diào)用,例如: 電商,視頻,社交,收費(fèi)項目,會員機(jī)制,只有會員才擁有這些動能

下面是道美團(tuán)面試題:

面試官問: 有沒有使用過performSelector----->其實這里面試官想問的是你有沒有動態(tài)的添加過方法

  這里就應(yīng)該這樣答: 使用過--->什么時候使用----動態(tài)添加方法的時候使用--->為什么動態(tài)添加方法---又回到到上面說的什么時候動態(tài)添加方法.

代碼如下:

#import"Person.h"#import@implementation Personvoideat(id self, SEL _cmd) {

? ? NSLog(@"我終于成功了");

}// 動態(tài)添加實例方法//resolveInstanceMethod 什么時候調(diào)用?只要調(diào)用沒有實現(xiàn)的方法,就會產(chǎn)生方法去解決,這個方法有什么作用: 去解決沒有實現(xiàn)方法,動態(tài)添加方法+ (BOOL)resolveInstanceMethod:(SEL)sel {


? ? if(sel == @selector(eat)) {


? ? ? ? /**

? ? ? ? 給一個類添加方法

? ? ? ? @param self 給誰添加方法

? ? ? ? @param sel 添加那個方法

? ? ? ? @param IMP 方法實現(xiàn),函數(shù)入口

? ? ? ? @return 方法類型

? ? ? ? */? ? ? ? class_addMethod(self, sel, (IMP)eat, "v@:");

? ? }

? ? return [super resolveInstanceMethod:sel];

}// 動態(tài)添加類方法//+ (BOOL)resolveClassMethod:(SEL)sel {////}@end// 下面是各個字母代表的參數(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"/*? ? 動態(tài)添加方法:

? ? 為什么動態(tài)添加方法? OC都是懶加載,有些方法可能很久不會調(diào)用.例如: 電商,視頻,社交,收費(fèi)項目,會員機(jī)制,只有會員才擁有這些動能

美團(tuán)面試題 : 有沒有使用過performSelector,使用,什么時候使用,動態(tài)添加方法的時候使用,為什么動態(tài)添加方法?

OC都是懶加載,有些方法可能很久不會調(diào)用.例如: 電商,視頻,社交,收費(fèi)項目,會員機(jī)制,只有會員才擁有這些動能

*/@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.動態(tài)添加屬性

理論上在分類中@property的作用: 僅僅是生成get,set方法的聲明,并不會生成get,set方法實現(xiàn),并不會生成下劃線屬性

動態(tài)添加方法實現(xiàn)思路: 在分類中用@property添加set,get方法之后,其實添加屬性就是要把一個變量跟一個類聯(lián)系起來.也就是在set和get方法中處理,代碼如下所示.

給NSObject添加一個name屬性:

分類中代碼 .h:

#import@interface NSObject (Property)// @property 在分類中作用 : 僅僅是生成get,set方法聲明.并不會生成get,set方法實現(xiàn),并不會生成下劃線成員屬性@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ā)的時候,是自己最熟悉什么用什么,而不是什么逼格高用什么,rumtime比較接近底層的語言,不好調(diào)試,盡量少用

? ? 需求: 給NSObject添加一個name屬性,動態(tài)添加屬性 ->runtime

? ? 屬性的本質(zhì): 讓一個屬性和對象產(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)行時,自己添加屬性

如果一個字典中,有很多的key,如果你在字典轉(zhuǎn)模型的時候,逐個的寫下屬性,將會非常蛋疼,其實可以給字典添加一個分類,利用遍歷字典中key,value,再利用字符串的拼接即可實現(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];


? ? // 這里需要拿到一個plist文件或者一個設(shè)置一個字典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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容