Runtime全面剖析之簡(jiǎn)單使用

Runtime是什么?見(jiàn)名知意,其概念無(wú)非就是“因?yàn)?code>Objective-C是一門動(dòng)態(tài)語(yǔ)言,所以它需要一個(gè)運(yùn)行時(shí)系統(tǒng)……這就是Runtime系統(tǒng)”云云。對(duì)博主這種菜鳥(niǎo)而言,Runtime在實(shí)際開(kāi)發(fā)中,其實(shí)就是一組C語(yǔ)言的函數(shù)。大家可以簡(jiǎn)單的認(rèn)為就是幾個(gè)C語(yǔ)言的api。 只是我們可以用這些方法干些蘋果爸爸不讓我們干的事情。

一: 打開(kāi)xcode對(duì)runtime的限制
二: runtime簡(jiǎn)介
三: runtime發(fā)送消息功能實(shí)現(xiàn)
四: runtime交換方法
五: runtime動(dòng)態(tài)添加方法
六: runtime分類增加屬性

參考資料

一: 打開(kāi)xcode對(duì)runtime的限制

但是從Xcode6之后,蘋果不推薦我們使用runtime,所以就取消了參數(shù)提示功能。估計(jì)是不想讓我們過(guò)多的了解底層。
Xcode6之后我們運(yùn)用objc_msgSend函數(shù)是這樣的:


image

從圖中很明顯可以看出來(lái)是沒(méi)有參數(shù)提示的。
我們可以通過(guò)對(duì)Xcode進(jìn)行配置進(jìn)行更改。更改如圖:


image

操作完成后再敲原來(lái)的函數(shù)就有參數(shù)提示功能了。


image

二: runtime簡(jiǎn)介

  • 1: RunTime簡(jiǎn)稱運(yùn)行時(shí)。OC就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制,其中最主要的是消息機(jī)制。
  • 2: 對(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ù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
  • 3: 在編譯階段,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ò)。

三: 發(fā)送消息(objc_msgSend)

  • 方法調(diào)用的本質(zhì),就是讓對(duì)象發(fā)送消息。
  • objc_msgSend,只有對(duì)象才能發(fā)送消息,因此以objc開(kāi)頭.
  • 使用消息機(jī)制前提,必須導(dǎo)入#import <objc/message.h>
    消息機(jī)制簡(jiǎn)單使用

XZPerson.h

#import <Foundation/Foundation.h>

@interface XZPerson : NSObject
// 對(duì)象方法
- (void)instanceMethodEat:(NSString *)foodName;

// 類方法
+ (void)classMethodEat:(NSString *)foodName;
@end

XZPerson.m

#import "XZPerson.h"

@implementation XZPerson

// 對(duì)象方法
- (void)instanceMethodEat:(NSString *)foodName{
  NSLog(@"instanceMethodEat...%@", foodName);
}

+ (void)classMethodEat:(NSString *)foodName{
  NSLog(@"classMethodEat...%@", foodName);
}
@end

ViewController.m

#import "ViewController.h"
#import "XZPerson.h"
#import <objc/message.h>

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  XZPerson *p = [[XZPerson alloc] init];
  [p instanceMethodEat:@"對(duì)象-麻辣魚"];
  // objc_msgSend 對(duì)象方法的調(diào)用
  objc_msgSend(p, @selector(instanceMethodEat:), @"對(duì)象-西瓜皮");

  // objc_msgSend 類方法的調(diào)用
  objc_msgSend([XZPerson class], @selector(classMethodEat:), @"類-麻婆豆腐");
}

@end

結(jié)果

XZRuntime[14251:707049] instanceMethodEat...對(duì)象-麻辣魚
XZRuntime[14251:707049] instanceMethodEat...對(duì)象-西瓜皮
XZRuntime[14251:707049] classMethodEat...類-麻婆豆腐

消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)

image

四: runtime交換方法

開(kāi)發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能,并且保持原有的功能。
方式一:繼承系統(tǒng)的類,重寫方法.
方式二:使用runtime,交換方法.

需求是: [UIImage imageNamed:@""]這個(gè)方法很常見(jiàn), 但是會(huì)遇到一種尷尬的問(wèn)題, 1:沒(méi)有輸入圖片的名字 2: 素材不合法 導(dǎo)致UIImageView不可以正常展示

4.1: 使用分類的方法寫一個(gè)名字為+(UIImage *)ImageNamed:(NSString *)name方法, 覆蓋原有的方法 (不可以實(shí)現(xiàn))

答: // Category is implementing a method which will also be implemented by its primary class
蘋果爸爸不允許分類重寫蘋果庫(kù)提供的方法

4.2: 使用分類的方法寫一個(gè)名字為+(UIImage *)changeImageName:(NSString *)name方法, 在需要的地方調(diào)用 (不可以實(shí)現(xiàn), 麻煩)

UIImage+Extension.h

#import <UIKit/UIKit.h>

@interface UIImage (Extension)
+ (UIImage *)changeImageName:(NSString *)name;
@end

UIImage+Extension.m

#import "UIImage+Extension.h"
#import <objc/message.h>

@implementation UIImage (Extension)

+ (UIImage *)changeImageName:(NSString *)name{
    assert(name.length != 0);
    UIImage *img = [self changeImageName:name];
    assert(img != nil);
    return img;
}

@end

可以實(shí)現(xiàn)但是相對(duì)調(diào)用比較復(fù)雜 需要每次引入UIImage+Extension.h, 調(diào)用changeImageName方法

4.3: 使用runtime的交換方法 (可以實(shí)現(xiàn), 簡(jiǎn)單)

我們希望的完美解決方案是, 我們?nèi)耘f調(diào)用'imageNamed'方法, 但是那內(nèi)部實(shí)際調(diào)用changeImageName完成功能的完善. 怎么使用那使用loadmethod_exchangeImplementations方法

UIImage+Extension.h

#import <UIKit/UIKit.h>

@interface UIImage (Extension)
@end

UIImage+Extension.m

#import "UIImage+Extension.h"
#import <objc/message.h>

@implementation UIImage (Extension)

// 加載分類到內(nèi)存的時(shí)候調(diào)用
+ (void)load{

  // 交換方法
  // 獲取changeImageName方法地址
  Method imageWithName = class_getClassMethod(self, @selector(changeImageName:));

  // 獲取imageWithName方法地址
  Method imageName = class_getClassMethod(self, @selector(imageNamed:));

  // 交換方法地址,相當(dāng)于交換實(shí)現(xiàn)方式
  method_exchangeImplementations(imageWithName, imageName);
}

+ (UIImage *)changeImageName:(NSString *)name{
    assert(name.length != 0);
    UIImage *img = [self changeImageName:name];
    assert(img != nil);
    return img;
}
@end

注意+ (UIImage *)changeImageName:(NSString *)name中生成image使用changeImageName方法。 防止死循環(huán)。

交換前

交換前

交換后

交換后

五: runtime動(dòng)態(tài)添加方法

開(kāi)發(fā)使用場(chǎng)景:如果一個(gè)類方法非常多,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類,添加方法解決。
經(jīng)典面試題:有沒(méi)有使用performSelector,其實(shí)主要想問(wèn)你有沒(méi)有動(dòng)態(tài)添加過(guò)方法。

XZPerson.h

#import "XZPerson.h"
#import <objc/message.h>
@implementation XZPerson

void run(id self, SEL sel){
  NSLog(@"%@--%@", self, NSStringFromSelector(sel));
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
  if (sel == @selector(run)) {
    // 1: 第一個(gè)參數(shù)給那個(gè)類增加方法
    // 2: 第二個(gè)參數(shù): 添加方法的方法編號(hào)
    // 3: 添加方法的實(shí)現(xiàn)
    // 4: 函數(shù)的類型, (返回值+參數(shù)類型) v:void @:對(duì)象->self :表示SEL->_cmd)
    class_addMethod(self, @selector(run), run, "v@:");
  }
  return [super resolveInstanceMethod:sel];
}
@end

#import "ViewController.h"
#import "XZPerson.h"
#import <objc/message.h>

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];


  XZPerson *p = [[XZPerson alloc] init];
  [p performSelector:@selector(run)];

}

@end

六: 給分類添加屬性

原理:給一個(gè)類聲明屬性,其實(shí)本質(zhì)就是給這個(gè)類添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類存空間。

XZPerson.h

#import "XZPerson.h"

@interface XZPerson (Category)
@property (nonatomic, copy)NSString *xzName;
@end

XZPerson.m

#import "XZPerson+Category.h"
#import <objc/message.h>

#define NAME  "xzName"

@implementation XZPerson (Category)

- (NSString *)xzName{
  return objc_getAssociatedObject(self, NAME);
}

- (void)setXzName:(NSString *)name{
  return objc_setAssociatedObject(self, NAME, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

ViewController.m

#import "ViewController.h"
#import "XZPerson.h"
#import <objc/message.h>
#import "XZPerson+Category.h"
 

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];


  XZPerson *p = [[XZPerson alloc] init];
  p.xzName = @"啊哈";
  NSLog(@"打印名字:%@", p.xzName);

}

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

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

  • 對(duì)于從事 iOS 開(kāi)發(fā)人員來(lái)說(shuō),所有的人都會(huì)答出【runtime 是運(yùn)行時(shí)】什么情況下用runtime?大部分人能...
    夢(mèng)夜繁星閱讀 3,812評(píng)論 7 64
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,074評(píng)論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,893評(píng)論 33 466
  • 如果想了解Runtime的實(shí)際應(yīng)用請(qǐng)看Runtime全面剖析之簡(jiǎn)單使用 一:Runtime簡(jiǎn)介二: Runtime...
    iYeso閱讀 852評(píng)論 0 2
  • 如果說(shuō)有什么能令我激動(dòng)好幾天的話 應(yīng)該就是一場(chǎng)旅行了 喜歡旅行的寶寶有多少 舉個(gè)爪來(lái)看看 作為一個(gè)既喜歡旅行又喜歡...
    B姐整啥呢閱讀 416評(píng)論 1 8

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