中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

eval和alias的特性是什么

發布時間:2021-10-26 16:39:29 來源:億速云 閱讀:143 作者:iii 欄目:編程語言

這篇文章主要講解了“eval和alias的特性是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“eval和alias的特性是什么”吧!

說說 Eval 特性

源自 Lisp 的 Evaluation

在一些語言中,eval 方法是將一個字符串當作表達式執行而返回一個結果的方法;在另外一些中,eval 它所傳入的不一定是字符串,還有可能是抽象句法形式,Lisp 就是這種語言,并且 Lisp 也是首先提出使用 eval 方法的語言,并提出了 Evaluation 這個特性。這也使得 Lisp 這門語言可以實現脫離編譯這套體系而動態執行的結果

eval和alias的特性是什么

Lisp 中的 eval 方法預期是:將表達式作為參數傳入到 eval 方法,并聲明給定形式的返回值,運行時動態計算

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

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

可能你會覺得:這只是一個簡單的特性,為什么會稱作黑魔法特性?

因為 Evaluation 這種可 eval 特性是很多思想、落地工具的基礎。為什么這么說,下面來說幾個很常見的場景。

 

REPL 的核心思想

如果你是 iOSer,你一定還會記得當年 Swift 剛剛誕生的時候,有一個主打的功能就是 REPL 交互式開發環境

eval和alias的特性是什么

當然,作為動態性十分強大的 Lisp 和 Ruby 也有對應的 REPL 工具。例如 Ruby 的 irb 和 pry 都是十分強大的 REPL。為什么這里要提及 REPL 呢?因為在這個名字中,E 就是 eval 的意思。

REPL 對應的英文是 Read-Eval-Print Loop

eval和alias的特性是什么

  • Read 讀入一個來自于用戶的表達式,將其放入內存;
  • Eval 求值函數,負責處理內部的數據結構并對上下文邏輯求值;
  • Print 輸出方法,將結果呈現給用戶,完成交互。

REPL 的模型讓大家對于語言的學習和調試也有著增速作用,因為“Read - Eval - Print” 這種循環要比 “Code - Compile - Run - Debug” 這種循環更加敏捷。

在 Lisp 的思想中,為了實現一個 Lisp REPL ,只需要實現這三個函數和一個輪循的函數即可。當然這里我們忽略掉復雜的求值函數,因為它就是一個解釋器。

有了這個思想,一個最簡單的 REPL 就可以使用如下的形式表達:

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

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

簡單聊聊 HotPatch

大約在 2 年前,iOS 比較流行使用 JSPatch/RN 基于 JavaScriptCore 提供的 iOS 熱修復和動態化方案。其核心的思路基本都是下發 JavaScript 腳本來調用 Objective-C,從而實現邏輯注入。

JSPatch 尤其被大家所知,需要編寫大量的 JavaScript 代碼來調用 Objective-C 方法,當然官方也看到了這一效率的洼地,并制作了 JSPatch 的語法轉化器來間接優化這一過程。

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

我們都知道 Objective-C 的 Runtime 利用消息轉發可以動態執行任何 Objective-C 方法,這也就給了我們一個啟示。假如我們自制一個輕量級解釋器,動態解釋 Objective-C 代碼,利用 Runtime 消息轉發來動態執行 Objective-C 方法,就可以實現一個“準 eval 方法”

eval和alias的特性是什么

這種思路在 GitHub 上也已經有朋友開源出了 Demo - OCEval[2]。不同于 Clang 的編譯過程,他進行了精簡:

  1. 去除了 Preprocesser 的預編譯環節,保留了 Lexer 詞法分析和 Parser 語法分析,
  2. 利用     NSMethodSignature 封裝方法,結合遞歸下降,使用 Runtime 對方法進行消息轉發。

利用這種思路的還有另外一個 OCRunner[3] 項目。

這些都是通過自制解釋器,實現 eval 特性,進而配合 libffi 來實現。

 

Ruby 中的 evalbinding

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

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

上面就是一個例子,我們發現傳入的代碼 str + ' CocoaPods'  在 eval 方法中已經變成 Ruby 代碼執行,并返回結果 'Hello CocoaPods'  字符串。

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

def self.from_ruby(path, contents = nil)
  # ... 
  podfile = Podfile.new(path) do
    begin
      # 執行 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
 

當然,在 CocoaPods 中僅僅是用了 eval 方法的第一層,對于我們學習者來說肯定不能滿足于此。

在 Ruby 中, Kernel 有一個方法 binding ,它會返回一個 Binding 類型的對象。這個 Binding 對象就是我們俗稱的綁定,它封裝了當前執行上下文的所有綁定,包括變量、方法、Block 和 self 的名稱綁定等,這些綁定直接決定了面向對象語言中的執行環境。

那么這個 Binding 對象在 eval 方法中怎么使用呢?其實就是 eval 方法的第二個參數。這個在 CocoaPods 中運行 Podfile 代碼中并沒有使用到。我們下面來做一個例子:

def foo 
  name = 'Gua'
  binding
end

eval('p name', foo) # Gua
 

在這個例子中,我們的 foo 方法就是我們上面說的執行環境,在這個環境里定義了 name 這個變量,并在方法體最后返回 binding 方法調用結果。在下面使用 eval 方法的時候,當做 Kernel#binding 入參傳入,便可以成功輸出 name 變量。

 

TOPLEVEL_BINDING 全局常量

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

@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 消息的接收者。為此,則保存了調用執行上下文 - 在我們的示例中,是 main 對象。

然后我們在 Addition 類的實例中使用 TOPLEVEL_BINDING 全局常量訪問全局的 @a 變量。

 

總說 Ruby Eval 特性

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

我們將所有的語句當作求值,用語言去描述過程,用與被求值的語言相同的語言寫出的求值器被稱作元循環;eval 在元循環中,參數是一個表達式和一個環境,這也與 Ruby 的 eval 方法完全吻合。

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

eval和alias的特性是什么

 

類似于 Method Swizzling 的 alias

對于廣大 iOSer 一定都十分了解被稱作 Runtime 黑魔法的 Method Swizzling。這其實是動態語言大都具有的特性。

在 iOS 中,使用 Selector 和 Implementation(即 IMP)的指向交換,從而實現了方法的替換。這種替換是發生在運行時的。

eval和alias的特性是什么

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

 

Open Class 與特異方法

Open Class 特性就是在一個類已經完成定義之后,再次向其中添加方法。在 Ruby 中的實現方法就是定義同名類

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

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 有點類似,不過附加的方法不是附加到類中,而是附加到特定到實例中。被附加到方法僅僅在目標實例中存在,不會影響該類到其他實例。示例代碼:

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)
   

環繞別名(Around Aliases)

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

首先先介紹一下 Ruby 的 alias 關鍵字,其實很簡單,就是給一個方法起一個別名。但是 alias 配合上之前的 Open Class 特性,就可以達到我們所說的 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!
 

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

總結一下環繞別名,其實就是給方法定義一個別名,然后重新定義這個方法,在新的方法中使用別名調用老方法

eval和alias的特性是什么

 

猴子補丁(Monkey Patch)

既然說到了 alias 別名,那么就順便說一下猴子補丁這個特性。猴子補丁區別于環繞別名的方式,它主要目的是在運行時動態替換并可以暫時性避免程序崩潰

先聊聊背景,由于 Open Class 和環繞別名這兩個特性,Ruby 在運行時改變屬性已經十分容易了。但是如果我們現在有一個需求,就是 **需要動態的進行 Patch ** ,而不是只要 alias 就全局替換,這要怎么做呢?

這里我們引入 Ruby 中的另外兩個關鍵字 refine 和 using ,通過它們我們可以動態實現 Patch。舉個例子:

class Foo
  def m1
    puts "m1"
  end
end

foo = Foo.new
foo.m1 # m1

"""
定義一個 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 方法,定義完之后它并不會立即生效,而是在我們使用 using TemporaryPatch 時,才會生效。這樣也就實現了動態 Patch 的需求。

感謝各位的閱讀,以上就是“eval和alias的特性是什么”的內容了,經過本文的學習后,相信大家對eval和alias的特性是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

彩票| 拉萨市| 马尔康县| 西城区| 崇州市| 甘孜县| 临沂市| 前郭尔| 通州区| 达州市| 交城县| 石城县| 穆棱市| 山阳县| 保定市| 武城县| 茂名市| 峨边| 屯留县| 静安区| 汉寿县| 西畴县| 朝阳区| 滦平县| 中西区| 兴文县| 桐柏县| 焦作市| 淮南市| 福清市| 西盟| 雅江县| 静安区| 马公市| 景宁| 电白县| 景谷| 西华县| 塔城市| 资源县| 庆城县|