OC語言中最爲強大的莫過於OC的運行時機制-Runtime,但因其比較接近底層,一旦使用Runtime出現bug,將很難調試,因此Runtime在開發中能不用就不用.下面我將介紹一些Runtime在開發中的使用,已經面試可能碰見的面試題.程序員
1.OC語法和Runtime語法的區別面試
OC語法和Runtime語法的區別,換而言之就是OC中咱們寫的語句,最終被轉換成Runtime中什麼樣語句.因爲Xcode6以後,蘋果不建議使用Runtime,也就是如今在編譯的時候,runtime的函數不會提示,須要去配置一下:函數
// 配置步驟: build Seting -> 搜索msg -> 設置成NO
建立一個控制檯程序,在自動釋放池中寫以下代碼:ui
NSObject *objc = [NSObject alloc];
objc = [objc init];
而後切換到終端命令行,執行如下步驟:this
cd 切換到你想生成的那個根文件的上一級目錄 clang -rewrite-objc main.m // clang -rewrite-objc 目標文件
會在該目錄文件下生成一個.cpp文件,打開以後搜索@autoreleasepool(這也就是當時爲何建立控制器程序的緣由,好查找轉換後的代碼在哪兒),就會找到轉換後的代碼: atom
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"));
上面的代碼比較原生態,咱們要是直接寫runtime的代碼以下所示,就能達到建立一個NSObject對象的目的:spa
// objc_msgSend: 兩個參數 1. 誰發送這個消息 2. 發送給誰 NSObject *objc = objc_msgSend([NSObject class], @selector(alloc)); objc = objc_msgSend(objc, @selector(init));
2.消息機制,調用私有方法命令行
面試題: runtime是什麼?或者是同類的調試
答: 其實runtime就是運行時機制,能夠經過命令行clang -rewrite-objc 對應的目標文件,就能將對應的OC的代碼轉成對應的運行時的代碼code
如果面試官問runtime中是怎麼找到對應的方法的,該怎麼回答?
答: 首先肯定問的是對象方法仍是類方法,對象方法保存到類中,類方法保存到元類(meta class),每個類都有方法列表methodList,每個方法在方法列表中都有對應的方法編號.(1)根據對象的isa去對應的類查找方法,isa: 判斷去哪一個類找對應的方法,指向方法調用的類 (2)根據傳入的方法編號,才能在方法列表中找到對應得方法Method(方法名).(3)根據方法名(函數入口)找到函數實現
知識擴充: 其實每一個方法最終轉換成函數的形式,存放在方法區,而每個函數的函數名都是函數的入口
訪問類中私有方法的代碼以下:
在對應類中的@implementation實現私有方法:
#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 <objc/message.h> /* runtime: 千萬不要隨便使用,不得已才使用 消息機制: 1. 裝逼 2. 調用已知私有的方法 */ @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { Person *p = objc_msgSend([Person class], @selector(alloc)); p = objc_msgSend(p, @selector(init)); // objc_msgSend(p, @selector(eat)); objc_msgSend(p, @selector(run:),20); } @end
注意: 必定要導入runtime的頭文件 :
#include <objc/runtime.h> 或者 #import <objc/message.h>
3.runtime方法交換
需求1: 我如今有一個項目,已經開發了兩年,以前都是用UIImage中的imageNamed去加載圖片,可是組長如今想imageNamed,給我提示是否加載成功.
思想1:在分類實現該方法.(但這種方法會把系統的方法覆蓋,通常不採用)
思想2: 自定義一個Image類,爲何不採用這種方法(這裏你就要明白何時須要自定義,系統功能不完善,就定義這樣一個類,去擴展這個類)
前兩種方法都有必定的侷限性,如果項目開發好久了,就須要更改好多東西,利用runtime交換方法實現的做用,能夠簡單的實現這個需求
這個時候不得不用runtime去交換方法
分類中代碼以下UIImage+image.h
#import <UIKit/UIKit.h> @interface UIImage (image) + (UIImage *)BO_imageNamed:(NSString *)name; @end
分類中代碼以下UIImage+image.m
#import "UIImage+image.h" #import <objc/message.h> @implementation UIImage (image) //若是當前類中東西僅且只需加載一次,通常放在load中.固然也能夠放在initialize中,須要進行判斷調用該類的是的類的類型 // 加載類的時候會調用,僅且調用一次 + (void)load { // 首先要拿到要交換的兩個方法 Method method1 = class_getClassMethod([UIImage class], @selector(BO_imageNamed:)); Method method2 = class_getClassMethod([UIImage class], @selector(imageNamed:)); method_exchangeImplementations(method1, method2); } // 加載當前類或者子類時候.會調用.可能會調用不止一次 + (void)initialize { } // 在系統方法的以前加前綴名的做用,防止覆蓋系統方法,有開發經驗的人默認的 + (UIImage *)BO_imageNamed:(NSString *)name{ // 當運行到這兒時,這裏已是imageNamed中的內容,此時再調用BO_imageNamed至關於原來imageNamed中的內容 UIImage *image = [self BO_imageNamed:name]; if (image == nil) { NSLog(@"照片不存在"); } return image; } @end
調用的代碼以下:
#import "ViewController.h" //#import "BOImage.h" #import "UIImage+image.h" /* 需求: 不得不用runtime去交換方法 需求: 想要在調用imageNamed,就給我提示,是否加載成功 需求: 讓UIImage調用imageNamed有這個功能 需求: 好比我有一個項目,已經開發兩年,以前都是用UIImage去加載圖片.組長如今想調用imageNamed,就給我提示,是否加載成功 注意: 在分類中必定不要重寫系統方法,不然就把系統方法幹掉了 思想: 何時須要自定義,系統功能不完善,就定義一個這樣的類,去擴展這個類 // 前兩種方法都有必定的侷限性,如果項目開發好久了,則須要更改好多東西,利用runtime交換方法實現的做用.能夠簡單的實現這個需求 */ @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: 動態添加方法
應用場景:
爲何動態添加方法?OC中是懶加載,有的方法可能好久不會調用,例如: 電商,視頻,社交,收費項目,會員機制,只有會員才擁有這些動能
下面是道美團面試題:
面試官問: 有沒有使用過performSelector----->其實這裏面試官想問的是你有沒有動態的添加過方法
這裏就應該這樣答: 使用過--->何時使用----動態添加方法的時候使用--->爲何動態添加方法---又回到到上面說的何時動態添加方法.
代碼以下:
#import "Person.h" #import <objc/message.h> @implementation Person void eat(id self, SEL _cmd) { NSLog(@"我終於成功了"); } // 動態添加實例方法 //resolveInstanceMethod 何時調用?只要調用沒有實現的方法,就會產生方法去解決,這個方法有什麼做用: 去解決沒有實現方法,動態添加方法 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { /** 給一個類添加方法 @param self 給誰添加方法 @param sel 添加那個方法 @param IMP 方法實現,函數入口 @return 方法類型 */ class_addMethod(self, sel, (IMP)eat, "v@:"); } return [super resolveInstanceMethod:sel]; } // 動態添加類方法 //+ (BOOL)resolveClassMethod:(SEL)sel { // //} @end // 下面是各個字母表明的參數 //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" /* 動態添加方法: 爲何動態添加方法? OC都是懶加載,有些方法可能好久不會調用.例如: 電商,視頻,社交,收費項目,會員機制,只有會員才擁有這些動能 美團面試題 : 有沒有使用過performSelector,使用,何時使用,動態添加方法的時候使用,爲何動態添加方法? OC都是懶加載,有些方法可能好久不會調用.例如: 電商,視頻,社交,收費項目,會員機制,只有會員才擁有這些動能 */ @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.動態添加屬性
理論上在分類中@property的做用: 僅僅是生成get,set方法的聲明,並不會生成get,set方法實現,並不會生成下劃線屬性
動態添加方法實現思路: 在分類中用@property添加set,get方法以後,其實添加屬性就是要把一個變量跟一個類聯繫起來.也就是在set和get方法中處理,代碼以下所示.
給NSObject添加一個name屬性:
分類中代碼 .h:
#import <Foundation/Foundation.h> @interface NSObject (Property) // @property 在分類中做用 : 僅僅是生成get,set方法聲明.並不會生成get,set方法實現,並不會生成下劃線成員屬性 @property NSString *name; @end
.m
#import "NSObject+Property.h" #import <objc/message.h> @implementation NSObject (Property) - (void)setName:(NSString *)name { objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)name { return objc_getAssociatedObject(self, "name"); } @end
控制器中代碼:
#import "ViewController.h" #import "NSObject+Property.h" /* 開發的時候,是本身最熟悉什麼用什麼,而不是什麼逼格高用什麼,rumtime比較接近底層的語言,很差調試,儘可能少用 需求: 給NSObject添加一個name屬性,動態添加屬性 ->runtime 屬性的本質: 讓一個屬性和對象產生關聯 */ @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSObject *objc = [[NSObject alloc] init]; objc.name = @"123"; NSLog(@"%@", objc.name); } @end
6:利用運行時,本身添加屬性
若是一個字典中,有不少的key,若是你在字典轉模型的時候,逐個的寫下屬性,將會很是蛋疼,其實能夠給字典添加一個分類,利用遍歷字典中key,value,再利用字符串的拼接便可實現.
NSDictionary+propertyCode.h分類中代碼以下:
#import <Foundation/Foundation.h> @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) { // 固然這裏仍是能夠本身添加其餘類型,就不一一列舉 if ([value isKindOfClass:[NSString class]]) { NSLog(@"%@", [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@", key]); }else if ([value isKindOfClass:[NSArray class]]) { NSLog(@"%@", [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@", key]); }else if ([value isKindOfClass:[NSNumber class]]) { 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文件或者一個設置一個字典 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
附: 寫一篇博客真的很費心神,如果之後有空,我會寫一個MJExtension的底層實現.前段時間看到一句話,與各位共勉:個人代碼曾運行在幾千萬用戶的機器上,做爲一個程序員,還有什麼比這更讓人知足的呢?若是有,那就是讓這個用戶數量再擴大 10 倍。
各位,晚安