Swift 環(huán)境下變更 Xcode 工程名后使用 NSKeyedUnarchiver 解檔引起的崩潰問(wèn)題

問(wèn)題描述

使用環(huán)境:

  • Xcode 10.1
  • Swift 4.0

描述:
使用 NSCoding 進(jìn)行 archiveunarchive 歸檔。舊的工程名叫 A, 新的工程名叫 B。A 曾經(jīng)在設(shè)備上運(yùn)行過(guò),并使用 NSUserDefault 針對(duì)序列化后的Data進(jìn)行持久化保存。
當(dāng)更換工程名后,B 在運(yùn)行時(shí)從userDefault中取出這個(gè)NSData來(lái)做解檔報(bào)錯(cuò)了

'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'

鏈接

自測(cè)后發(fā)現(xiàn)

測(cè)試過(guò)發(fā)現(xiàn)了如下問(wèn)題:

  • 使用OC創(chuàng)建工程,變更工程名之后,調(diào)用 [NSkeyedUnarchiver unarchiveobject] 是沒(méi)有問(wèn)題的。
  • Swift 上發(fā)現(xiàn)一定會(huì)造成閃退
  • ios 11 和 ios 12之后的版本,unarchive 分別添加了一個(gè) 函數(shù),用來(lái)配合解檔失敗時(shí)候的處理(try catch)
  • 看了我們使用的第三方登錄/分享的平臺(tái),他們也有使用到 歸檔和解檔。只是用的是 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.

—— 參考出處

何為命名空間

OC中沒(méi)有命名空間的概念,在進(jìn)行應(yīng)用開(kāi)發(fā)時(shí),所有的代碼和引用的靜態(tài)庫(kù)最終會(huì)被編譯到同一個(gè)域和二進(jìn)制文件中。這樣當(dāng)兩個(gè)類(lèi)名重復(fù)的時(shí)候,就會(huì)導(dǎo)致編譯沖突和失敗。這也就是為什么我們?cè)趯?xiě)OC代碼的時(shí)候要添加類(lèi)名前綴的原因。比如蘋(píng)果本身保留的前綴UI和NS 還有各個(gè)系統(tǒng)框架的前綴AF、SD等,這樣做可以大大降低引起沖突的幾率,但是風(fēng)險(xiǎn)仍然存在,如果你在項(xiàng)目中同時(shí)加載進(jìn)兩個(gè)不同的庫(kù),而這兩個(gè)庫(kù)都分別引用了同一個(gè)第三方庫(kù)而沒(méi)有修改名字,這樣就會(huì)發(fā)生沖突。
Swift由于命名空間的存在,既是兩個(gè)名稱(chēng)相同的類(lèi),只要他們來(lái)自不同的命名空間就不會(huì)產(chǎn)生編譯時(shí)的沖突。
"在 Swift 中,由于可以使用命名空間了,即使是名字相同的類(lèi)型,只要是來(lái)自不同的命名空間的話(huà),都是可以和平共處的。

—— 參考出處
—— 喵神寫(xiě)的命名空間的文檔


結(jié)論

由于 Swift 的機(jī)制原因,創(chuàng)建的類(lèi)都會(huì)帶上 命名空間(簡(jiǎn)單的理解就是工程名,在 Info.plist 中查看源碼,看到的那個(gè) CFBundleName 就是命名空間,實(shí)際上就是工程名)。當(dāng)我們變更了工程的同事,也就意味著命名空間跟著變了。在上一個(gè)工程中歸檔自定義類(lèi)時(shí),帶上了舊工程的命名空間。因此在新工程做解檔事,找不到對(duì)應(yīng)的命名空間,早場(chǎng)了 crash

解決方案(預(yù)防為主):

預(yù)防方法一

如果我們?cè)?Swift 中針對(duì)一個(gè)自定義類(lèi)使用 NSKeyedUnarchiver 進(jìn)行歸檔,那么這個(gè)自定義類(lèi)建議定義為 oc的類(lèi),在創(chuàng)建類(lèi)的時(shí)候, class 前要加上 @objc 關(guān)鍵字,這樣累就不會(huì)帶上命名空間了。

@objc(MyClass) class MyClass: NSObject, NSCoding {
  //... 略去,下面要去實(shí)現(xiàn) NSCoding 的 decode 和 encode delegate
}

預(yù)防方法二

如果我們就是想要保證 swift的命名空間,能做的是在工程初始的時(shí)候,不使用業(yè)務(wù)名給主 target 命名。

例如我們要寫(xiě)一個(gè)計(jì)算器的應(yīng)用,那么很自然的我們?cè)谛陆üこ堂臅r(shí)候,會(huì)給他命名為 Calculator。后續(xù)如果想要修改工程名,肯定也會(huì)修改這個(gè) target。

事實(shí)上我們可以換一種做法,將主target 命名為 Main,而在使用 pod的時(shí)候,指定他生成的 workspace名. 這樣,后續(xù)如果要修改工程名,也只需要修改 workspace 的名字就能達(dá)到目的而不去影響 命名空間

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

workspace 'Calculator'
target 'Main' do
    # pod ...
end

規(guī)避方法

如果你之前已經(jīng)發(fā)布了一個(gè) 沒(méi)有使用過(guò) @objc 關(guān)鍵字的類(lèi),這時(shí)候新發(fā)的版本肯定無(wú)法將這個(gè)已經(jīng)存在 NSUserDefault 中的類(lèi)變?yōu)椴粠臻g。意味著即使升級(jí)版本,也會(huì)造成崩潰。

那么怎么辦呢?

我看過(guò)一些社交類(lèi)的第三方應(yīng)用提供的 sdk,如facebook, vk, twitter 等等,他們的歸檔對(duì)象類(lèi)型目前還是用 OC 的。但是我在 vk 的代碼中看到了 try catch

查看了一下 NSKeyedUnarchiverswift 官方 API,發(fā)現(xiàn)了兩個(gè)帶有 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?

因?yàn)榇蠖鄶?shù)的app應(yīng)用都需要從 ios 8.0 開(kāi)始支持,所以最好的方法是我們自己去添加一個(gè) try catch 處理解檔失敗時(shí)候的崩潰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容