使用NSSecureCoding協議進行物件編解碼

shinancao發表於2014-05-12

NSCoding是把資料儲存在iOS和Mac OS上的一種極其簡單和方便的方式,它把模型物件直接轉變成一個檔案,然後再把這個檔案重新載入到記憶體裡,並不需要任何檔案解析和序列化的邏輯。如果要把物件儲存到一個資料檔案中(假設這個物件實現了NSCoding協議),那麼你可以像下面這樣做:

稍後再載入它:

這樣做對於編譯進APP裡的資源來說是可以的(例如nib檔案,它在底層使用了NSCoding),但是使用NSCoding來讀寫使用者資料檔案的問題在於,把全部的類編碼到一個檔案裡,也就間接地給了這個檔案訪問你APP裡面例項類的許可權。

雖然你不能在一個NSCoded檔案裡(至少在iOS中的)儲存可執行程式碼,但是一名黑客可以使用特製地檔案騙過你的APP進入到例項化類中,這是你從沒打算做的,或者是你想要在另一個不同的上下文時才做的。儘管以這種方式造成實際性的破壞很難,但是無疑會導致使用者的APP崩潰掉或者資料丟失。

在iOS6中,蘋果引入了一個新的協議,是基於NSCoding的,叫做NSSecureCoding。NSSecureCoding和NSCoding是一樣的,除了在解碼時要同時指定key和要解碼的物件的類,如果要求的類和從檔案中解碼出的物件的類不匹配,NSCoder會丟擲異常,告訴你資料已經被篡改了。

大部分支援NSCoding的系統物件都已經升級到支援NSSecureCoding了,所以能安全地寫有關歸檔的程式碼,你可以確保正在載入的資料檔案是安全的。實現的方式如下:

注意一下,如果要讓編寫歸檔的程式碼是安全的,那麼儲存在檔案中的每一個物件都要實現NSSecureCoding協議,否則會有異常丟擲。如果要告訴框架自定義的類支援NSSecureCoding協議,那麼你必須在initWithCoder: method方法中實現新的解碼邏輯,並且supportsSecureCodin方法要返回YES。encodeWithCoder:方法沒有變化,因為與安全相關的事是圍繞載入進行的,而不是儲存:

幾周前,我寫了一篇關於如何自動實現NSCoding的文章,它利用反射機制確定執行時類的屬性。

這是一種給所有的模型物件新增NSCoding支援的很好的方式,在initWithCoder:/encodeWithCoder: 方法中,你不再需要寫重複的並且容易出錯的程式碼了。但是我們使用的方法沒有支援NSSecureCoding,因為我們不打算在物件被載入時校驗其型別。

那麼怎麼改善這個自動NSCoding系統,使其以正確的方式支援NSSecureCoding呢?

回想一下,最開始的實現原理是利用class_copyPropertyList() 和 property_getName()這樣兩個執行時方法,產生屬性名稱列表,我們再把它們在陣列中排序:

使用KVC(鍵-值編碼),我們能夠利用名稱設定和獲取一個物件的所有屬性,並且在一個NSCoder物件中對這些屬性進行編碼/解碼。

為了要實現NSSecureCoding,我們要遵循同樣的原則,但是不僅僅是獲取屬性名,還需要獲取它們的型別。幸運地是,Objective C執行時儲存了類的屬性型別的詳細資訊,所以可以很容易和名字一起取到這些資料。

一個類的屬性可以是基本資料型別(例如整型、布林型別和結構體),或者物件(例如字串、陣列等等)。KVC中的valueForKey: and setValue:forKey:方法實現了對基本型別的自動“裝箱”,也就是說它們會把整型、布林型和結構體各自轉變成NSNumber和NSValue物件。這使事情變得簡單了很多,因為我們只要處理裝箱過的型別(物件)即可,所以我們可以宣告屬性型別為類,而不用為不同的屬性型別呼叫不同的解碼方法。

儘管執行時方法沒有提供已裝箱的類名,但是它們提供了型別編碼—一種特殊格式化的C風格的字串,它包含了型別資訊(與@encode(var);返回的形式一樣)。因為沒有方法自動獲取到基本型別對應的裝箱過的類,所以我們需要解析這個字串,然後指定其合適的型別。

型別編碼字串形式的文件說明在這裡

第一個字母代表了基本型別。Objective C使用一個唯一的字母表示每一個支援的基本型別,例如’i’表示integer,’f’表示float,’d’表示double,等等。物件用’@’表示(緊跟著的是類名),還有其他一些不常見的型別,例如’:’表示selectors,’#’表示類。

結構體和聯合體表示為大括號裡面的表示式。只有幾種型別是KVC機制所支援的,但是支援的那些類通常被裝箱為NSValue物件,所以可用一種方式處理以’{’開頭的任何值。

如果根據字串的首字母來轉換,那麼我們可以處理所有已知的型別:

如果要處理’@’型別,則需要提去出類名。類名可能包括協議(實際上我們並不需要用到),所以劃分字串拿準確的類名,然後使用NSClassFromString得到類:

最後,把上面的解析過程和前面實現的propertyNames方法結合起來,建立一個方法返回屬性類的字典,屬性名稱作為字典的鍵。下面是完成的實現過程:

最難的部分已經完成了。現在,要實現NSSecureCoding,只要將initWithCoder:方法中之前寫的自動編碼實現的部分,改為在解析時考慮到屬性的類就可以了。此外,還需讓supportsSecureCoding方法返回YES:

這樣就得到了一個用於描述模型物件的簡單的基類,並且它以正確的方式支援NSSecureCoding。此外,你可以使用我的AutoCoding擴充套件,它利用這種方法自動給沒有實現NSCoding 和 NSSecureCoding協議的物件新增對它們的支援。

相關文章