溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

eval和alias的特性是什么

發(fā)布時(shí)間:2021-10-26 16:39:29 來(lái)源:億速云 閱讀:142 作者:iii 欄目:編程語(yǔ)言

這篇文章主要講解了“eval和alias的特性是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“eval和alias的特性是什么”吧!

說(shuō)說(shuō) Eval 特性

源自 Lisp 的 Evaluation

在一些語(yǔ)言中,eval 方法是將一個(gè)字符串當(dāng)作表達(dá)式執(zhí)行而返回一個(gè)結(jié)果的方法;在另外一些中,eval 它所傳入的不一定是字符串,還有可能是抽象句法形式,Lisp 就是這種語(yǔ)言,并且 Lisp 也是首先提出使用 eval 方法的語(yǔ)言,并提出了 Evaluation 這個(gè)特性。這也使得 Lisp 這門語(yǔ)言可以實(shí)現(xiàn)脫離編譯這套體系而動(dòng)態(tài)執(zhí)行的結(jié)果。

eval和alias的特性是什么

Lisp 中的 eval 方法預(yù)期是:將表達(dá)式作為參數(shù)傳入到 eval 方法,并聲明給定形式的返回值,運(yùn)行時(shí)動(dòng)態(tài)計(jì)算。

下面是一個(gè) Lisp Evaluation 代碼的例子( Scheme[1] 方言 RRS 及以后版本):

; 將 f1 設(shè)置為表達(dá)式 (+ 1 2 3)
(define f1 '(+ 1 2 3))
 
; 執(zhí)行 f1 (+ 1 2 3) 這個(gè)表達(dá)式,并返回 6
(eval f1 user-initial-environment)
 

可能你會(huì)覺得:這只是一個(gè)簡(jiǎn)單的特性,為什么會(huì)稱作黑魔法特性?

因?yàn)?Evaluation 這種可 eval 特性是很多思想、落地工具的基礎(chǔ)。為什么這么說(shuō),下面來(lái)說(shuō)幾個(gè)很常見的場(chǎng)景。

 

REPL 的核心思想

如果你是 iOSer,你一定還會(huì)記得當(dāng)年 Swift 剛剛誕生的時(shí)候,有一個(gè)主打的功能就是 REPL 交互式開發(fā)環(huán)境

eval和alias的特性是什么

當(dāng)然,作為動(dòng)態(tài)性十分強(qiáng)大的 Lisp 和 Ruby 也有對(duì)應(yīng)的 REPL 工具。例如 Ruby 的 irb 和 pry 都是十分強(qiáng)大的 REPL。為什么這里要提及 REPL 呢?因?yàn)樵谶@個(gè)名字中,E 就是 eval 的意思。

REPL 對(duì)應(yīng)的英文是 Read-Eval-Print Loop。

eval和alias的特性是什么

  • Read 讀入一個(gè)來(lái)自于用戶的表達(dá)式,將其放入內(nèi)存;
  • Eval 求值函數(shù),負(fù)責(zé)處理內(nèi)部的數(shù)據(jù)結(jié)構(gòu)并對(duì)上下文邏輯求值;
  • Print 輸出方法,將結(jié)果呈現(xiàn)給用戶,完成交互。

REPL 的模型讓大家對(duì)于語(yǔ)言的學(xué)習(xí)和調(diào)試也有著增速作用,因?yàn)椤癛ead - Eval - Print” 這種循環(huán)要比 “Code - Compile - Run - Debug” 這種循環(huán)更加敏捷。

在 Lisp 的思想中,為了實(shí)現(xiàn)一個(gè) Lisp REPL ,只需要實(shí)現(xiàn)這三個(gè)函數(shù)和一個(gè)輪循的函數(shù)即可。當(dāng)然這里我們忽略掉復(fù)雜的求值函數(shù),因?yàn)樗褪且粋€(gè)解釋器。

有了這個(gè)思想,一個(gè)最簡(jiǎn)單的 REPL 就可以使用如下的形式表達(dá):

# Lisp 中
(loop (print (eval (read))))

# Ruby 中
while [case]
  print(eval(read))
end
   

簡(jiǎn)單聊聊 HotPatch

大約在 2 年前,iOS 比較流行使用 JSPatch/RN 基于 JavaScriptCore 提供的 iOS 熱修復(fù)和動(dòng)態(tài)化方案。其核心的思路基本都是下發(fā) JavaScript 腳本來(lái)調(diào)用 Objective-C,從而實(shí)現(xiàn)邏輯注入。

JSPatch 尤其被大家所知,需要編寫大量的 JavaScript 代碼來(lái)調(diào)用 Objective-C 方法,當(dāng)然官方也看到了這一效率的洼地,并制作了 JSPatch 的語(yǔ)法轉(zhuǎn)化器來(lái)間接優(yōu)化這一過(guò)程。

但是無(wú)論如何優(yōu)化,其實(shí)最大的根本問(wèn)題是 Objective-C 這門語(yǔ)言不具備 Evaluation 的可 eval 特性,倘若擁有該特性,那其實(shí)就可以跨越使用 JavaScript 做橋接的諸多問(wèn)題。

我們都知道 Objective-C 的 Runtime 利用消息轉(zhuǎn)發(fā)可以動(dòng)態(tài)執(zhí)行任何 Objective-C 方法,這也就給了我們一個(gè)啟示。假如我們自制一個(gè)輕量級(jí)解釋器,動(dòng)態(tài)解釋 Objective-C 代碼,利用 Runtime 消息轉(zhuǎn)發(fā)來(lái)動(dòng)態(tài)執(zhí)行 Objective-C 方法,就可以實(shí)現(xiàn)一個(gè)“準(zhǔn) eval 方法”。

eval和alias的特性是什么

這種思路在 GitHub 上也已經(jīng)有朋友開源出了 Demo - OCEval[2]。不同于 Clang 的編譯過(guò)程,他進(jìn)行了精簡(jiǎn):

  1. 去除了 Preprocesser 的預(yù)編譯環(huán)節(jié),保留了 Lexer 詞法分析和 Parser 語(yǔ)法分析,
  2. 利用     NSMethodSignature 封裝方法,結(jié)合遞歸下降,使用 Runtime 對(duì)方法進(jìn)行消息轉(zhuǎn)發(fā)。

利用這種思路的還有另外一個(gè) OCRunner[3] 項(xiàng)目。

這些都是通過(guò)自制解釋器,實(shí)現(xiàn) eval 特性,進(jìn)而配合 libffi 來(lái)實(shí)現(xiàn)。

 

Ruby 中的 evalbinding

Ruby 中的 eval 方法其實(shí)很好理解,就是將 Ruby 代碼以字符串的形式作為參數(shù)傳入,然后進(jìn)行執(zhí)行。

str = 'Hello'
puts eval("str + ' CocoaPods'") # Hello CocoaPods
 

上面就是一個(gè)例子,我們發(fā)現(xiàn)傳入的代碼 str + ' CocoaPods'  在 eval 方法中已經(jīng)變成 Ruby 代碼執(zhí)行,并返回結(jié)果 'Hello CocoaPods'  字符串。

在「Podfile 的解析邏輯」中講到, CocoaPods 中也使用了 eval 方法,從而以 Ruby 腳本的形式,執(zhí)行了 Podfile 文件中的邏輯。

def self.from_ruby(path, contents = nil)
  # ... 
  podfile = Podfile.new(path) do
    begin
      # 執(zhí)行 Podfile 中的邏輯
      eval(contents, nil, path.to_s)
    rescue Exception => e
      message = "Invalid `#{path.basename}` file: #{e.message}"
      raise DSLError.new(message, path, e, contents)
    end
  end
  podfile
end
 

當(dāng)然,在 CocoaPods 中僅僅是用了 eval 方法的第一層,對(duì)于我們學(xué)習(xí)者來(lái)說(shuō)肯定不能滿足于此。

在 Ruby 中, Kernel 有一個(gè)方法 binding ,它會(huì)返回一個(gè) Binding 類型的對(duì)象。這個(gè) Binding 對(duì)象就是我們俗稱的綁定,它封裝了當(dāng)前執(zhí)行上下文的所有綁定,包括變量、方法、Block 和 self 的名稱綁定等,這些綁定直接決定了面向?qū)ο笳Z(yǔ)言中的執(zhí)行環(huán)境。

那么這個(gè) Binding 對(duì)象在 eval 方法中怎么使用呢?其實(shí)就是 eval 方法的第二個(gè)參數(shù)。這個(gè)在 CocoaPods 中運(yùn)行 Podfile 代碼中并沒有使用到。我們下面來(lái)做一個(gè)例子:

def foo 
  name = 'Gua'
  binding
end

eval('p name', foo) # Gua
 

在這個(gè)例子中,我們的 foo 方法就是我們上面說(shuō)的執(zhí)行環(huán)境,在這個(gè)環(huán)境里定義了 name 這個(gè)變量,并在方法體最后返回 binding 方法調(diào)用結(jié)果。在下面使用 eval 方法的時(shí)候,當(dāng)做 Kernel#binding 入?yún)魅?,便可以成功輸?name 變量。

 

TOPLEVEL_BINDING 全局常量

在 Ruby 中 main 對(duì)象是最頂級(jí)范圍,Ruby 中的任何對(duì)象都至少需要在次作用域范圍內(nèi)被實(shí)例化。為了隨時(shí)隨地地訪問(wèn) main 對(duì)象的上下文,Ruby 提供了一個(gè)名為 TOPLEVEL_BINDING 的全局常量,它指向一個(gè)封裝了頂級(jí)綁定的對(duì)象。便于理解,舉個(gè)例子:

@a = "Hello"

class Addition
  def add
    TOPLEVEL_BINDING.eval("@a += ' Gua'")
  end
end

Addition.new.add

p TOPLEVEL_BINDING.receiver # main
p @a # Hello Gua
 

這段代碼中,Binding#receiver 方法返回 Kernel#binding 消息的接收者。為此,則保存了調(diào)用執(zhí)行上下文 - 在我們的示例中,是 main 對(duì)象。

然后我們?cè)?Addition 類的實(shí)例中使用 TOPLEVEL_BINDING 全局常量訪問(wèn)全局的 @a 變量。

 

總說(shuō) Ruby Eval 特性

以上的簡(jiǎn)單介紹如果你曾經(jīng)閱讀過(guò) SICP(Structture and Interpretation of Computer Programs)這一神書的第四章后,一定會(huì)有更加深刻的理解。

我們將所有的語(yǔ)句當(dāng)作求值,用語(yǔ)言去描述過(guò)程,用與被求值的語(yǔ)言相同的語(yǔ)言寫出的求值器被稱作元循環(huán);eval 在元循環(huán)中,參數(shù)是一個(gè)表達(dá)式和一個(gè)環(huán)境,這也與 Ruby 的 eval 方法完全吻合。

不得不說(shuō),Ruby 的很多思想,站在 SICP 的肩膀上。

eval和alias的特性是什么

 

類似于 Method Swizzling 的 alias

對(duì)于廣大 iOSer 一定都十分了解被稱作 Runtime 黑魔法的 Method Swizzling。這其實(shí)是動(dòng)態(tài)語(yǔ)言大都具有的特性。

在 iOS 中,使用 Selector 和 Implementation(即 IMP)的指向交換,從而實(shí)現(xiàn)了方法的替換。這種替換是發(fā)生在運(yùn)行時(shí)的。

eval和alias的特性是什么

在 Ruby 中,也有類似的方法。為了全面的了解 Ruby 中的 “Method Swizzling”,我們需要了解這幾個(gè)關(guān)于元編程思想的概念:Open Class 特性環(huán)繞別名。這兩個(gè)特性也是實(shí)現(xiàn) CocoaPods 插件化的核心依賴。

 

Open Class 與特異方法

Open Class 特性就是在一個(gè)類已經(jīng)完成定義之后,再次向其中添加方法。在 Ruby 中的實(shí)現(xiàn)方法就是定義同名類。

在 Ruby 中不會(huì)像 Objective-C 和 Swift 一樣被認(rèn)為是編譯錯(cuò)誤,后者需要使用 Category 和 Extension 特殊的關(guān)鍵字語(yǔ)法來(lái)約定是擴(kuò)展。而是把同名類中的定義方法全部附加到已定義的舊類中,不重名的增加,重名的覆蓋。以下為示例代碼:

class Foo
  def m1
    puts "m1"
  end
end

class Foo
  def m2 
    puts "m2"
  end
end

Foo.new.m1 # m1
Foo.new.m2 # m2

class Foo
  def m1
    puts "m1 new"
  end
end

Foo.new.m1 # m1 new
Foo.new.m2 # m2
 

特異方法和 Open Class 有點(diǎn)類似,不過(guò)附加的方法不是附加到類中,而是附加到特定到實(shí)例中。被附加到方法僅僅在目標(biāo)實(shí)例中存在,不會(huì)影響該類到其他實(shí)例。示例代碼:

class Foo
  def m1
    puts "m1"
  end
end

foo1 = Foo.new

def foo1.m2()
  puts "m2"
end

foo1.m1 # m1
foo1.m2 # m2

foo2 = Foo.new
foo2.m1 # m1
# foo2.m2 undefined method `m2' for #<Foo:0x00007f88bb08e238> (NoMethodError)
   

環(huán)繞別名(Around Aliases)

其實(shí)環(huán)繞別名只是一種特殊的寫法,這里使用了 Ruby 的 alias 關(guān)鍵字以及上文提到的 Open Class 的特性。

首先先介紹一下 Ruby 的 alias 關(guān)鍵字,其實(shí)很簡(jiǎn)單,就是給一個(gè)方法起一個(gè)別名。但是 alias 配合上之前的 Open Class 特性,就可以達(dá)到我們所說(shuō)的 Method Swizzling 效果。

class Foo
  def m1
    puts "m1"
  end
end

foo = Foo.new
foo.m1 # m1

class Foo
  alias :origin_m1 :m1
  def m1
    origin_m1
    puts "Hook it!"
  end
end

foo.m1 
# m1
# Hook it!
 

雖然在第一個(gè)位置已經(jīng)定義了 Foo#m1  方法,但是由于 Open Class 的重寫機(jī)制以及 alias 的別名設(shè)置,我們將 m1 已經(jīng)修改成了新的方法,舊的 m1 方法使用 origin_m1 也可以調(diào)用到。如此也就完成了類似于 Objective-C 中的 Method Swizzling 機(jī)制。

總結(jié)一下環(huán)繞別名,其實(shí)就是給方法定義一個(gè)別名,然后重新定義這個(gè)方法,在新的方法中使用別名調(diào)用老方法。

eval和alias的特性是什么

 

猴子補(bǔ)?。∕onkey Patch)

既然說(shuō)到了 alias 別名,那么就順便說(shuō)一下猴子補(bǔ)丁這個(gè)特性。猴子補(bǔ)丁區(qū)別于環(huán)繞別名的方式,它主要目的是在運(yùn)行時(shí)動(dòng)態(tài)替換并可以暫時(shí)性避免程序崩潰。

先聊聊背景,由于 Open Class 和環(huán)繞別名這兩個(gè)特性,Ruby 在運(yùn)行時(shí)改變屬性已經(jīng)十分容易了。但是如果我們現(xiàn)在有一個(gè)需求,就是 **需要?jiǎng)討B(tài)的進(jìn)行 Patch ** ,而不是只要 alias 就全局替換,這要怎么做呢?

這里我們引入 Ruby 中的另外兩個(gè)關(guān)鍵字 refine 和 using ,通過(guò)它們我們可以動(dòng)態(tài)實(shí)現(xiàn) Patch。舉個(gè)例子:

class Foo
  def m1
    puts "m1"
  end
end

foo = Foo.new
foo.m1 # m1

"""
定義一個(gè) Patch
"""

module TemproaryPatch
  refine Foo do 
    def m1 
      puts "m1 bugfix"
    end
  end
end

using TemproaryPatch

foo2 = Foo.new
foo2.m1 # m1 bugfix

上面代碼中,我們先使用了 refine 方法重新定義了 m1 方法,定義完之后它并不會(huì)立即生效,而是在我們使用 using TemporaryPatch 時(shí),才會(huì)生效。這樣也就實(shí)現(xiàn)了動(dòng)態(tài) Patch 的需求。

感謝各位的閱讀,以上就是“eval和alias的特性是什么”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)eval和alias的特性是什么這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI