質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

7484閲覧

SwiftでprotocolにNSObjectを継承させる方法

Fushihara

総合スコア52

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2017/09/29 04:11

編集2017/09/29 06:41

環境はXcode9+Swift3、iOSアプリです。

以下のようなプロトコル、クラスがあります。

swift

1public protocol Pu { 2 func hogeFunc() -> String 3} 4public class CLS_A : Pu { 5 func hogeFunc() -> String{ return "A" } 6} 7public class CLS_B : Pu { 8 func hogeFunc() -> String{ return "B" } 9} 10 11let kage : [Pu] = [ CLS_A() , CLS_B() ]

このプロトコルPu(を実装したクラスCLS_AとCLS_B)にNSKeyedArchiver.archivedData(withRootObject: instance)を使ったインスタンスのシリアライズ機能を追加しようとしています。
その為にはNSObject,NSCodingの継承が必要です。

そこで、既定のPuプロトコルにNSObject,NSCodingの継承を追加しようとしたのですが
NSObjectはクラスですので「Non-class type 'Pu' cannot inherit from class 'NSObject'」というエラーが出てしまいました。
Puをプロトコルからクラスに変えるのが本来あるべき対処法かと思うのですが、PuをクラスにするとhogeFuncを実装する強制を持たせる事が出来ませんし、すでにあちこちでPuを参照しているコード、型の変数があり変更箇所を可能な限り少なくしたいです。
修正範囲をなるべく狭くしたいという理由で、PuをプロトコルのままでNSObjectを継承させるにはどうしたらよろしでしょうか。

具体的なコードは以下のような内容です。以下のP_C_BはNSObjectを継承していないので、シリアライズ化の時に落ちてしまいます。
protocol Puで、どうにかして「NSObjectを継承する」という制約を付けれればよいのですが。

swift

1protocol Pu : NSCoding{ 2 func test() -> String 3} 4class P_C_A : NSObject, Pu { 5 private let pca : String 6 func test() -> String { 7 return "T_C_A" 8 } 9 override init(){ 10 self.pca = "a" 11 super.init() 12 } 13 required public init?(coder aDecoder: NSCoder) { 14 self.pca = aDecoder.decodeObject(forKey: "pca") as! String 15 } 16 func encode(with aCoder: NSCoder) { 17 aCoder.encode(self.pca, forKey: "pca") 18 } 19} 20class P_C_B : Pu { 21 private let pcb : String 22 init(){ 23 self.pcb = "b" 24 } 25 func encode(with aCoder: NSCoder) { 26 aCoder.encode(self.pcb, forKey: "pcb") 27 } 28 required init?(coder aDecoder: NSCoder) { 29 self.pcb = aDecoder.decodeObject(forKey: "pcb") as! String 30 } 31 func test() -> String { 32 return "T_C_A" 33 } 34} 35class S_t2 { 36 static func extTest(){ 37 let v1 : [Pu] = [ 38 P_C_A(), 39 P_C_B() 40 ] 41 let v2 : Data = NSKeyedArchiver.archivedData(withRootObject: v1) 42 let v3 : String = v2.base64EncodedString() 43 let v4 : Data = Data(base64Encoded: v3)! 44 let v5 : Any = NSKeyedUnarchiver.unarchiveObject(with: v4)! 45 let v6 : [Pu] = NSKeyedUnarchiver.unarchiveObject(with: v4) as! [Pu] 46 47 v6.forEach { (i) in 48 if let d = i as? P_C_A { 49 print("これはPCAです") 50 }else if let d = i as? P_C_B { 51 print("これはPCBです") 52 }else{ 53 print("型の判定に失敗しました") 54 } 55 } 56 print(v1) 57 print(v2) 58 print(v3) 59 print(v4) 60 print(v5) 61 print(v6) 62 } 63} 64S_t2.extTest()

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

t_obara

2017/09/29 04:47

継承できたとして、encodeメソッドも追加したりする必要がありますが、その点は修正範囲の中で許容なのですよね?修正範囲を比較して考えると、単にPuにencodeメソッドを追加して、jsonとかの文字列やDictionaryとかで返却するようにして、それを格納するとかで十分なのでは?
Fushihara

2017/09/29 04:54

CLS_A、CLS_BそれぞれにencodeメソッドやNSCoderが引数にあるイニシャライザを追加する事は許容範囲です。Dictionary型にする事も実は途中までやっていたのですが、説明欄のコードで言うと変数kageをjson(Dictionary)から復元する時、kageの中身の一つ一つに、それがCLS_AなのかCLS_Bなのか判定してそれぞれのイニシャライザに処理を分岐する処理が必要ですし、今後CLS_C等を追加した時にそれを呼び出す全ての場所でCLS_Cだった時の判定を追加する必要が生じてしまいます。それに、CLS_Cの追加漏れがどこかであっても、コンパイル時エラーにならないのでそれは避けたい気持ちがあります。
t_obara

2017/09/29 05:09

判定は必要ですが、デシリアイズをUserDefaultsのextensionでまとめれば、判定箇所は一箇所で済むと思いますが。
Fushihara

2017/09/29 05:23

申し訳ありません、NSKeyedArchiver.archivedData(withRootObject: instance)を使う方法でお願いしたいと思います。
MasakiHori

2017/09/29 06:10

NSObjectのサブクラスの必要ありましたっけ? NSObjectProtocolに準拠していればよかったような気がしますが。どちらにしてもNSObjectProtocolをPuが継承しておけば NSObjectProtocolに準拠するのはとても面倒くさいのでNSObjectを継承するであろうと思われます。
MasakiHori

2017/09/29 06:19

追記: さらに実体がクラスであることを要求するclassを継承リストに加えておけばもうちょとよくなるなか?
guest

回答2

0

ベストアンサー

NSObject が持つ性質(NSKeyedArchiver が必要とする性質)は NSObjectProtocol に規定されています。実際の NSObject クラスも NSObjectProtocol に準拠しています。

ですので、プロトコル PuNSObjectProtocol プロトコルを継承すれば 実質的に Pu プロトコルを継承したクラスは NSObject クラスを継承するように制約をかけられるようになります。

swift

1protocol Pu : NSCoding, NSObjectProtocol { 2 func test() -> String 3}

もちろん NSObjectProtocol で規定されている機能を自力で実装すれば NSObject クラスを継承する必要はなくなりますけど、それならそれで、自力実装が適切であれば NSKeyedArchiver で適切に処理されるはずなので、この方法で目的は達成できるかなって思います。

投稿2017/09/29 13:05

TomohiroKumagai

総合スコア441

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

共通のものは共通化したい気持ちもわかりますが、P_C_A, P_C_Bの両方に明示的に継承とプロトコル準拠(:NSObject, NSCoding)を指定するのはそれほど悪い実装ではない気がします。

むしろ、機能や性質を継承関係でツリー状にしていくより、個別のオブジェクトに必要な機能・性質を都度プロトコルで明示していく形の方がSwift的かと思います。

投稿2017/09/29 21:44

YokemuraTakeshi

総合スコア297

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問