您好,登錄后才能下訂單哦!
這篇文章主要講解了“eval和alias的特性是什么”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“eval和alias的特性是什么”吧!
在一些語(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é)果。
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)景。
如果你是 iOSer,你一定還會(huì)記得當(dāng)年 Swift 剛剛誕生的時(shí)候,有一個(gè)主打的功能就是 REPL 交互式開發(fā)環(huán)境。
當(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。
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
大約在 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 方法”。
這種思路在 GitHub 上也已經(jīng)有朋友開源出了 Demo - OCEval[2]。不同于 Clang 的編譯過(guò)程,他進(jìn)行了精簡(jiǎn):
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)。
eval
和 binding
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
變量。
以上的簡(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 的肩膀上。
alias
對(duì)于廣大 iOSer 一定都十分了解被稱作 Runtime 黑魔法的 Method Swizzling。這其實(shí)是動(dòng)態(tài)語(yǔ)言大都具有的特性。
在 iOS 中,使用 Selector 和 Implementation(即 IMP)的指向交換,從而實(shí)現(xiàn)了方法的替換。這種替換是發(fā)生在運(yùn)行時(shí)的。
在 Ruby 中,也有類似的方法。為了全面的了解 Ruby 中的 “Method Swizzling”,我們需要了解這幾個(gè)關(guān)于元編程思想的概念:Open Class 特性與環(huán)繞別名。這兩個(gè)特性也是實(shí)現(xiàn) CocoaPods 插件化的核心依賴。
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)
其實(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)用老方法。
既然說(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)注!
免責(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)容。