サイバーエージェントのインターンに参加してきました!

大学二年生でiOSアプリの個人開発をしているりゅうです!
サイバーエージェントさんの
「無視できない!設計が学べるiOSハンズオン」
に参加してきました。
自分は個人開発しかしたことがなく、自分が書いているコードが本当に合っているのかがわからなかったので、実際に実務でも使われているようなコードを教えていただきとても勉強になりました。

今回はなにをしたのかや、インターンで学んだことを備忘録も兼ねて書いていきたいと思います。

なにをしたの?

音楽再生アプリのリファクタリングをしました!ViewControllerにほぼ全てのロジックがあり、viewDidLoadに処理をベタ書きしているような伸び代のあるコードでした。どこから手をつけるか悩みましたね。
私はMVVM+RxSwiftをつかってこれらのコードをリファクタリングしましたが、Combineを使っている方もいました。

Day1

10:30 オープニング
12:30 ランチ
13:30 開発スタート
16:30 1on1@メンター
18:30 夕会
18:30 日報タイム

この日のランチは会社から歩いてすぐのお高そうなしゃぶしゃぶでした!
1on1@メンターの時間では、30分間で開発の状況だったり、MVVMについての質問をさせていただきました。

Day2 

10:30 朝会
12:00 ランチ
13:30 開発スタート
16:30 1on1@人事
18:30 夕会
18:30 日報タイム

この日のランチは焼肉!ひさびさに牛タンをたべました。

Day3

10:30 朝会
12:00 ランチ
15:00 コードフリーズ
15:00 資料作成開始
16:00 成果発表会
17:30 表彰/振り返り会
18:30 懇親会

この日のランチは焼肉弁当でした!3日間豪華な食事をありがとうございます。
私はすべてのViewControllerをMVVMにして、さらにテストもしたいと思っていたのですが、テストだけできませんでした😭

そして成果発表会!!!
最優秀賞とベストグロース賞が用意されていました。
なんと、最優秀賞をいただくことができました!本当に嬉しかったですね。
ただ賞を取った人からの一言、というのがあったのですが、そこでテンパって何も思い浮かばずに「ほんとに嬉しいです!」
だけしか言えなかったのが本インターン最大の後悔ですね。ちなみに席に座ってから、「あ、これいえばよかったな」っていうのがすぐに思い浮かんでちょっと悔しかったですw

18:30~の懇親会は居酒屋でやったのですが、ほんとに楽しかったです。
メンターさんから会社のことについていろいろなことを聞いたり、
プライベートな話をしたり、貴重な時間でした。面白い人ばっかで最後の方はもうずっと笑いっぱなしでしたね笑
#筋肉食堂

最後にインターンで学んだ備忘録だけ書かせてください!!

ライフサイクルメソッドをObservableに変換

UIViewControllerのライフサイクルメソッドが呼び出されたときにViewModelのinputsの処理をしたい時、

override func viewWillAppear(_ animated: Bool) {
    viewModel.inputs.loadItems()
}

このように書いていました。

ですが、下のようにUIViewControllerにlifeCycleプロパティを追加します。

struct LifeCycle {
    let viewDidLoad: Observable<Void>
    let viewWillAppear: Observable<Void>
}

extension UIViewController {
    var lifeCycle: LifeCycle {
        .init(
            viewDidLoad: rx.sentMessage(#selector(viewDidLoad)).map { _ in ()}.share(replay: 1).asObservable(),
            viewWillAppear: rx.sentMessage(#selector(viewWillAppear(_:))).map { _ in ()}.share(replay: 1).asObservable()
        )
    }
}

すると、

class PlayListViewModel: PlayListViewModelType, PlayListViewModelInputs, PlayListViewModelOutputs {
    .....
    init(
        lifeCycle: LifeCycle,
        .....
    ) {
        lifeCycle.viewWillAppear
            .withUnretained(self)
            .map { strong, _ in strong.dataStorage.playLists }
            .bind(to: sectionItemsRelay)
            .disposed(by: disposeBag)
        .....
    }
}

このようによりReactiveXらしい書き方が可能になります。

DIについて

インターンに参加する前に「DIとはなにか」を事前にしらべていたのですが、いまいち理解ができていませんでした。
ですが、メンターの方に教えていただいてDIについてやDIするメリットを教えていただいて理解することができたので、それについて書いていきます。

どんなときにつかう?

DIはユニットテストをする時に効果を発揮します。
例えばViewModelにUserDefaultsにデータを保存するModel層のクラスがプロパティで保持されていた場合を考えます。
その時にViewModelのユニットテストを行うと、UserDefaultsにテストで使ったデータまでもが保存されてしまいます(UserDefaultsに依存している)。
また、APIと通信するModel層のクラスがプロパティで保持されていた場合も考えます。
その時、インターネットの接続状況によってエラーの有無が左右されてしまったり、常にレスポンスが一定ではないので、不安定なテストとなってしまいます(サーバー側にテストが依存)。
なのでViewModelをテストする際、これらのクラスをUserDefaultsやサーバーに依存しないテスト用のクラスに差し替える必要が出てきます。
その時にDIが必要になってきます。
実際に今回のインターンで使用したコードを見てみます。

final class DataStorage: DataStorageProtocol {
    private init() {}
    static let `default` = DataStorage()
    
    @Savable<[PlayListItem]>("PlayList", register: [])
    var playLists: [PlayListItem]
}

protocol DataStorageProtocol: AnyObject {
    var playLists: [PlayListItem] { get set }
}

final class PlayListDetailViewModel: PlayListDetailViewModelType, PlayListDetailViewModelInputs, PlayListDetailViewModelOutputs {
    
    struct Dependency {
        let dataStorage: DataStorageProtocol
        static let `default` = Dependency(dataStorage: DataStorage.default)
    }

    private let dataStorage: DataStorageProtocol
    .....
    init(
         dependency: Dependency,
         .....
    ) {
        self.dataStorage = dependency.dataStorage
    }

    .....
}

DependencyというViewModelが依存しているクラスを定義する構造体を作成しました。
DataStorageクラスの@SavableはUserDefaultsに保存するプロパティラッパーなのですが、本題とはズレるので省略します。
今回はUserDefaultsを保存するクラスであるDataStorageを通常時とテスト時で差し替える必要性があるので、Dependencyで定義しています。

このようにしておくことで通常時には

let viewModel = PlayListDetailViewModel(dependency: .default, ....)

テスト時には

class DataStorageTest {
    private init() {}
    static let `default` = DataStorageTest()

    var playLists = [PlayListItem]()
}

let viewModel = PlayListDetailViewModel(dependency: .init(dataStorage: DataStorageTest.default))

とテスト用のクラスに簡単に切り替えることができます。
依存性の注入…?なんだそれ…?と思ってましたが、これを教えていただいて、確かに依存性の注入がされているなと思いました。
今回のインターンではテストまで行けませんでしたが、個人開発をする時に実際にテストを導入してみたいと思います。

さいごに

このほかにも実務で実際につかっている書き方などたくさんのことを学ばせていただき、本当に楽しかったです!インターンがこんなに楽しいものだとは思わなかったです。
また、普段周りにSwiftをやっている人がほとんどいなかったのですが、今回のインターンでたくさんの学生と話すことができました。インターン生同士でチーム開発をすることになったりしてめっちゃ楽しみです!
メンターの方、そしてインターン生の方、お疲れ様です!3日間ありがとうございました!


この記事が気に入ったらサポートをしてみませんか?