Swift 環境下變動 Xcode 工程名後使用 NSKeyedUnarchiver 解檔引發的崩潰問題

問題描述

使用環境:ios

  • Xcode 10.1
  • Swift 4.0

描述: 使用 NSCoding 進行 archiveunarchive 歸檔。舊的工程名叫 A, 新的工程名叫 B。A 曾經在設備上運行過,並使用 NSUserDefault 針對序列化後的Data進行持久化保存。 當更換工程名後,B 在運行時從userDefault中取出這個NSData來作解檔報錯了swift

'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked'ruby

連接app

自測後發現

測試過發現了以下問題:框架

  • 使用OC建立工程,變動工程名以後,調用 [NSkeyedUnarchiver unarchiveobject] 是沒有問題的。
  • Swift 上發現必定會形成閃退
  • ios 11 和 ios 12以後的版本,unarchive 分別添加了一個 函數,用來配合解檔失敗時候的處理(try catch)
  • 看了咱們使用的第三方登陸/分享的平臺,他們也有使用到 歸檔和解檔。只是用的是 OC

緣由

When you use the @objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the @objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.函數

—— 參考出處post

何爲命名空間

OC中沒有命名空間的概念,在進行應用開發時,全部的代碼和引用的靜態庫最終會被編譯到同一個域和二進制文件中。這樣當兩個類名重複的時候,就會致使編譯衝突和失敗。這也就是爲何咱們在寫OC代碼的時候要添加類名前綴的緣由。好比蘋果自己保留的前綴UI和NS 還有各個系統框架的前綴AF、SD等,這樣作能夠大大下降引發衝突的概率,可是風險仍然存在,若是你在項目中同時加載進兩個不一樣的庫,而這兩個庫都分別引用了同一個第三方庫而沒有修更名字,這樣就會發生衝突。 Swift因爲命名空間的存在,既是兩個名稱相同的類,只要他們來自不一樣的命名空間就不會產生編譯時的衝突。 "在 Swift 中,因爲可使用命名空間了,即便是名字相同的類型,只要是來自不一樣的命名空間的話,都是能夠和平共處的。測試

—— 參考出處 —— 喵神寫的命名空間的文檔this


結論

因爲 Swift 的機制緣由,建立的類都會帶上 命名空間(簡單的理解就是工程名,在 Info.plist 中查看源碼,看到的那個 CFBundleName 就是命名空間,實際上就是工程名)。當咱們變動了工程的同事,也就意味着命名空間跟着變了。在上一個工程中歸檔自定義類時,帶上了舊工程的命名空間。所以在新工程作解檔事,找不到對應的命名空間,早場了 crashspa

解決方案(預防爲主):

預防方法一

若是咱們在 Swift 中針對一個自定義類使用 NSKeyedUnarchiver 進行歸檔,那麼這個自定義類建議定義爲 oc的類,在建立類的時候, class 前要加上 @objc 關鍵字,這樣累就不會帶上命名空間了。

@objc(MyClass) class MyClass: NSObject, NSCoding {
  //... 略去,下面要去實現 NSCoding 的 decode 和 encode delegate
}
複製代碼

預防方法二

若是咱們就是想要保證 swift的命名空間,能作的是在工程初始的時候,不使用業務名給主 target 命名。

例如咱們要寫一個計算器的應用,那麼很天然的咱們在新建工程名的時候,會給他命名爲 Calculator。後續若是想要修改工程名,確定也會修改這個 target

事實上咱們能夠換一種作法,將主target 命名爲 Main,而在使用 pod的時候,指定他生成的 workspace名. 這樣,後續若是要修改工程名,也只須要修改 workspace 的名字就能達到目的而不去影響 命名空間

platform :ios, '8.0'
# inhibit_all_warnings!
use_frameworks!

workspace 'Calculator'
target 'Main' do
    # pod ...
end
複製代碼

規避方法

若是你以前已經發布了一個 沒有使用過 @objc 關鍵字的類,這時候新發的版本確定沒法將這個已經存在 NSUserDefault 中的類變爲不帶命名空間。意味着即便升級版本,也會形成崩潰。

那麼怎麼辦呢?

我看過一些社交類的第三方應用提供的 sdk,如facebook, vk, twitter 等等,他們的歸檔對象類型目前仍是用 OC 的。可是我在 vk 的代碼中看到了 try catch

查看了一下 NSKeyedUnarchiverswift 官方 API,發現了兩個帶有 throwsAPI,可是他們支持的版本都在 11.0 以後。

@available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *)
  @nonobjc public static func unarchivedObject<DecodedObjectType>(ofClass cls: DecodedObjectType.Type, from data: Data) throws -> DecodedObjectType? where DecodedObjectType : NSObject, DecodedObjectType : NSCoding

  @available(OSX 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *)
  @nonobjc public static func unarchivedObject(ofClasses classes: [AnyClass], from data: Data) throws -> Any?
複製代碼

由於大多數的app應用都須要從 ios 8.0 開始支持,因此最好的方法是咱們本身去添加一個 try catch 處理解檔失敗時候的崩潰。

相關文章
相關標籤/搜索