グッバイ!JSONModel
こんにちは、スペースマーケットでモバイルエンジニアをしている村田です。
スペースマーケットゲストiOSプロジェクトの言語使用率はSwift 96.2%、Objective-C 3.6%とまだまだレガシーコードが残っている状態です。主にAPI通信/ModelにObj-C製のものが残留しており、日々の業務タスクをこなしつつSwiftへの置き換えを進めています(自分が入社した当時は7%以上残っていたので1.5年で半分程度は消えました🙏)
JSONデータからObj-C製モデルへの変換ライブラリとして JSONModel を使用していましたが、タイトルの通り今回削除対応しました。その理由や削除する上で大変だったことを本記事で共有できればと思います。
削除のきっかけ
7月頃に「予約詳細画面に何も表示されない」と問い合わせがあり、確認したところiOS17β端末で予約情報取得後のJSONModelによるマッピングが失敗していることが判明しました。穴を塞ぐ作業はすぐに行い不具合解消したバージョンをリリースしたのですが、今後のOSアップデートで他モデルも同様なエラーが発生する可能性があり使用し続けるリスクが大きいため、iOS17リリースまでにJSONModel削除する事を決断しました(実際 β5あたりでレビュー一覧画面で同様のエラーが発生していました)
JSONModelとは
改めてJSONModelとは、JSONデータからObj-C製モデルオブジェクトにマッピングするためのライブラリです。自分はSwiftから入った人間でスペースマーケットに入社するまで扱ったことなかったのですが、Objective-Cを触っていた方からするとメジャーなライブラリの1つですかね?
最新リリースはv1.7.0の2016年でした🧟
洗い出し
まず、以下をFigmaへ洗い出しました。
- JSONModelのどの機能を利用しているか(JSONパーサー以外にも機能があり)
- モデルのリレーション
洗い出した結果、他モデルへの依存がない&JSONModelのメソッドほぼ使っていないモデルはそもそもObj-C脱却できるなと判断してSwift化しました(4モデルSwift化できました🙌)
JSONModelの以下メソッドを利用しており、それぞれ必要であれば各モデルに自作しました。
- initWithString: JSON形式の文字列を解析、モデルオブジェクトにマッピング
- initWithDictionary: NSDictionary型のデータを元にオブジェクトを生成
- toJSONString: オブジェクトをJSON形式の文字列に変換
- toDictionary: オブジェクトをNSDictionary型へ変換
initWithDictionary自作
ルーム情報のモデルである SMRoom
へ作成したものを例に、ほぼ全てのモデルで必要なinitWithDictionaryメソッドの置き換え対応について記載します。
(因みに、削除しかしたことなかったので今回の対応で初めてObj-Cコード書きました)
まずヘッダファイルへメソッド宣言追加。
@protocol SMRoom;
@interface SMRoom : SMModel
@property (nonatomic, strong, nonnull) NSNumber *id;
@property (nonatomic, strong, nonnull) NSString *uid;
@property (nonatomic, strong, nullable) NSString *name;
⁝
(省略)
⁝
@property (nonatomic, strong, nullable) SMSpace *space;
@property (nonatomic, strong, nullable) SMOwner *owner;
+ (nonnull SMRoom *)initWithDictionary:(nonnull NSDictionary*)dictionary;
@end
次に実装ファイルへメソッドを定義。
引数のdictionary
からキーに対応する値を取得し、各プロパティへ設定します。
(これを計17モデルへ実装。かなり骨が折れました...)
#import "SMRoom.h"
@implementation SMRoom
+ (SMRoom *)initWithDictionary:(NSDictionary*)dictionary {
SMRoom *room = [[SMRoom alloc] init];
room.id = dictionary[@"id"];
room.uid = dictionary[@"uid"];
room.name = dictionary[@"name"];
⁝
(省略)
⁝
room.owner = [SMOwner initWithDictionary:dictionary[@"owner"]];
room.space = [SMSpace initWithDictionary:dictionary[@"space"]];
return room;
}
NSNull対応
key値に対応する値がnullの場合NSNullを返却するため、Swift側でそのまま扱おうとした際にクラッシュしてしまいました(そもそもkeyが存在しない場合はnilを返すようでした)。
一律nilとして扱いたいため、NSNullの場合はnilを返却するメソッドをNSDictionaryへ生やして対応。
#import "NSDictionary+Extensions.h"
@implementation NSDictionary (Extensions)
- (nullable id)objectOrNilForKey:(nonnull NSString*)key {
id object = [self objectForKey:key];
if (object == [NSNull null] || object == nil) {
return nil;
} else {
return object;
}
}
@end
#import "SMRoom.h"
#import "NSDictionary+Extensions.h"
@implementation SMRoom
+ (SMRoom *)initWithDictionary:(NSDictionary*)dictionary {
SMRoom *room = [[SMRoom alloc] init];
room.id = dictionary[@"id"];
room.uid = dictionary[@"uid"];
room.name = [dictionary objectOrNilForKey:@"name"];
⁝
(省略)
⁝
if ([dictionary objectOrNilForKey:@"owner"] != nil) {
room.owner = [SMOwner initWithDictionary:dictionary[@"owner"]];
}
if ([dictionary objectOrNilForKey:@"space"] != nil) {
room.space = [SMSpace initWithDictionary:dictionary[@"space"]];
}
return room;
}
@end
振り返り
スペースマーケットのスクラムは1週間スプリントで動いています。
他のタスクもやりつつという事もあり、約2ヶ月(8スプリント)かかりました...。
差分3000行を超え、コミット数は190でした(PRは不具合修正対応込みで26個)
レビューも大変だったと思います。メンバーに感謝!!!
アプリ全体に関わる大規模な対応だったのでリリース怖かったですが、今の所致命的な不具合はなさそうです。
※ 是非アプリインストールよろしくお願いします!問題あればご報告もいただければ嬉しいです
iOS | Android |
---|---|
感想
プロジェクトへがっつり絡んだライブラリの削除はかなり大変でした。更新が長らく止まっているライブラリは「動いていることが奇跡」と考えて徐々に剥がしていくよう早めの動きだしが必要ですね...。
削除予定のライブラリは他にもDeprecatedになったAFNetworking、そのSwift版であるAlamofire、Combine登場により不要になりつつあるRx系。UI周りもKLCPopupなど古いもの幾つか利用しており、徐々にSwiftUI化しつつ消していければと考えています。
大変ですが自らの手で不要なものを消して数が減っていくことに喜び感じてます。幻影旅団2人狩った際のヒソカと同じ気分です。
出典:漫画「HUNTER×HUNTER」
「あと 10ライブラリ......♪」
最後に
ヒソカの気分になりたいあなた、スペースマーケットでは一緒に働く仲間を募集中です!
詳しくは以下をご確認の上ご応募ください。
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion