iOS-Runtime在開發中的使用及相關面試題

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 倍。

各位,晚安

相關文章
相關標籤/搜索