軟件是一個巨大的有限狀態機。工程師平常作的bug修復、性能調優,本質上就是儘量保證代碼處於有序狀態下。
不論OC仍是Swift,都擁有強大的編譯器做爲輔助。儘量多地將狀態固定在編碼時,就就減小了運行期的狀態,使得軟件的狀態總數減小了。html
狀態總數少了,錯誤就少了,性能也就提高了。
這個例子的關鍵點是性能。git
OC爲了作到ABI兼容,改變了C、C++以來對象的內存結構,每一個成員變量都須要兩次尋址才能訪問到。好比要獲取obj的第n個變量的偏移地址,就要github
*((&obj->isa.cls->data()->ro->ivars->first)[N]->offset);
看起來就慢爆了。編程
在實現上,LLVM爲每一個類的每一個成員變量都分配了一個全局變量,用於存儲該成員變量的偏移值。這樣,訪問每一個變量須要兩次尋址,先獲取全局變量,再取全局變量的值做爲地址找到真正的變量。swift
編譯後的佈局
obj->myInt = 42;
對應於以下的簡單C語言代碼性能
int32_t g_ivar_MyClass_myInt = 40; // 全局變量 *(int32_t *)((uint8_t *)obj + g_ivar_MyClass_myInt) = 42;
這就是爲何ivar_t.offset用int指針來存儲偏移值,而不是直接放一個int的緣由。優化
struct ivar_t { int32_t *offset; //注意,這裏是指針 const char *name; const char *type; //... }
真正存放偏移值的地址是固定不變的,在編譯時就肯定了下來。所以才能用區區2條指令搞定動態佈局的成員變量。ui
這個例子的關鍵點是減小錯誤。編碼
如圖所示,要保證第一個矩陣中的列數必須與第二個矩陣中的行數相同。簡單的作法是作運行時檢查
struct Matrix { let rows: Int let columns: Int } func multiply(m1: Matrix, _ m2: Matrix) -> Matrix? { // do the matrices have the correct sizes? precondition(m1.columns == m2.rows) // bunch of math... }
更好的作法是在編碼時,就不容許出現行列不等的狀況
protocol Dimension { static var size: Int { get set } } func multiply<U: Dimension, V: Dimension, W: Dimension> (m1: Matrix<U,V>, _ m2: Matrix<V,W>) -> Matrix<U,W> { // bunch of math... return Matrix<U,W>() }
運行結果
struct NumExamples: Dimension { static var size = 20 } struct NumFeatures: Dimension { static var size = 10 } struct OneDimensional: Dimension { static var size = 1 } let A = Matrix<NumExamples, NumFeatures>() let B = Matrix<NumFeatures, OneDimensional>() let C = multiply(A, B) // yay! let D = multiply(B, A) // compiler error
完整的優化過程不在本文的討論範圍內,感興趣的能夠看 這裏。
objc explain: Non-fragile ivars
Dynamic ivars: solving a fragile base class problem
編程世界的熵增原理