Swift + Alamofire + ObjectMapper + Realm [Swift3 대응]

18195 단어 SwiftRealm
이제 Swift를 사내의 안건에 도입할까 검토중입니다.
수탁 안건에서는 거의 필수 기능이라고 할 수 있다
  • 네트워크를 통해 JSON 얻기
  • JSON을 구문 분석하고 DB에 삽입
  • DB의 값을 TableView에 표시

  • 라는 흐름.
    모처럼 나우한 언어인 Swift이니까, 이케 하고 있는 라이브러리로 키메키메인 코딩을 하고 싶다!
    그래서 Swift나 새로운 라이브러리의 조사가 끝나고, 얼른 샘플을 만들어 보았습니다.

    샘플



    샘플을 여기에 올려 보았습니다.
    Github - SatoshiN21/RealmSample
    ※자연스럽게 Swift3에 대응하고 있습니다.

    이번에는 하부의 IT 카테고리의 핫 엔트리 RSS를 JSON 형식으로 취득하고 있습니다.
    나우한 Material Design의 CardView 같은 모양으로 해 보았습니다.



    1. JSON 취득



    Alamofire는 부의 피드 정보를 얻고 있습니다.
    핫 엔트리는 RSS 형식으로 배포되었으므로 google의 API을 사용하여 JSON 형식으로 변환했습니다.
    import Alamofire
    import SwiftyJSON
    
    //  google JSON api + hatena bookmark hotentry
    let hotEntryUrl = "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://b.hatena.ne.jp/hotentry/it.rss&num=100"
    
    // Alamofireを用いてGETリクエスト
    Alamofire.request(hotEntryUrl).responseJSON { (response) in
    
        guard response.result.isSuccess, let value = response.result.value else {
    
            // FIXME:便宜上こちら無視してますが、実装時はエラーのハンドリングを行う必要があります
            return
        }
    
        // SwiftyJSON方式
        let json = JSON(value)
    
    

    얻은 JSON을 SwiftyJSON 형식으로 변환하고 다시 실행합니다.
    이번 샘플에서는 리퀘스트 에러시의 처리는 구현하고 있지 않으므로, 주의해 주세요!

    2. JSON을 구문 분석하여 DB에 삽입



    SwiftyJSON 형식으로 하면 퍼스 작업이 되겠네요! 체인 모양으로 원하는 요소를 얻을 수 있습니다.
    항목 목록을 가져옵니다.
    let json = JSON(responseObject!)
    let entries = json["responseData"]["feed"]["entries"]
    

    ObjectMapper를 사용하여 Entry 객체에 매핑합니다.
    ObjectMapper와 Realm을 공존시키는 방법은 gologo13 님의 게시물 를 참고로 했습니다.
    import ObjectMapper
    
    realm.beginWrite()
    
    // JSONをEntryオブジェクトにマッピング
    for (_, subJson) in entries {
        if let entry = Mapper<Entry>().map(JSONObject: subJson.dictionaryObject) {
            realm.add(entry, update: true)
        }
    }
    
    realm.commitWrite()
    

    RealmSwift.Object를 상속한 Entry 객체 내의 사양은 거의 참고로 한 기사와 같습니다만,
    게시일 날짜를 사용자 정의 DateTransform에서 Date로 변환하고 있습니다.
    extension Entry : Mappable {
    
        func mapping(map: Map) {
            content         <- map["content"]
            link            <- map["link"]
            publishedDate   <- (map["publishedDate"] , EntryDateTransform())
            title           <- map["title"]
            contentSnippet  <- map["contentSnippet"]
        }
    }
    
    // 独自定義したDateTransform
    // フォーマットとNSLocaleを指定してStringをNSDateに変換
    class EntryDateTransform : DateTransform {
        override func transformFromJSON(_ value: Any?) -> Date? {
            if let dateStr = value as? String {
                return Date.dateWithString(
                    dateStr,
                    format: "E, dd MMM yyyy HH:mm:ss zzzz" ,
                    locale : Locale(identifier: "en_US"))
            }
            return nil
        }
    }
    
    extension Date {
        public static func dateWithString(_ dateStr : String? , format : String, locale : Locale) ->Date? {
    
            guard let dateStr = dateStr else {
                return nil
            }
            let df : DateFormatter = DateFormatter()
            df.locale = Locale(identifier: "en_US")
            df.timeZone = TimeZone.current
            df.dateFormat = format
            return df.date(from: dateStr)
        }
    }
    

    3. DB 값을 TableView에 표시



    Realm의 갱신이 발생한 타이밍에 UITableView#reloadData()를 실행해, UITableView의 표시를 갱신.
    RealmSwift.Object를 상속한 클래스에서 objects(type: Object.Type)를 호출하면 DB에 저장된 모든 값을 얻을 수 있습니다.
        func updateTableView() {
    
            do {
                self.entries = try Realm().objects(Entry.self).sorted(by: { (entry1, entry2) -> Bool in
                let res = entry1.publishedDate.compare(entry2.publishedDate)
                return (res == .orderedAscending || res == .orderedSame)
                })
            }catch {}
    
            tableView.reloadData()
        }
    
        // MARK:- UITableView DataSource / Delegate
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            if let entries = entries {
                return entries.count
            }
            return 0
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
            let cell  = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier") as! EntryTableViewCell
    
            // if entries have been nil,"cellForRowAtIndexPath:indexPath:" isn't called.
            let entry = entries![indexPath.row]
    
            // date format.
            let df = DateFormatter()
            df.locale = Locale(identifier: "ja_JP")
            df.timeZone = TimeZone.current
            df.dateFormat = "MM/dd"
            let dateStr = df.string(from: entry.publishedDate as Date)
    
            cell.titleLabel.text = [dateStr,entry.title].joined(separator: " ")
            cell.descriptionLabel.text = entry.contentSnippet
    
            return cell
        }
    

    이상



    꽤 엉망이지만 JSON을 얻고 내부 DB에 저장/표시 방향이 보였을까라는 느낌입니다.
    Objective-C 때는 CoreData를 사용하고 있었습니다만, 데이터가 비대화할수록 보이는 퍼포먼스가 떨어지는 듯 어리석은 기억이 있습니다.
    Realm을 사용해 얼마나 퍼포먼스에 차이가 나오는지, 지금부터 기대입니다.

    참고로 한 기사



    [Swift]ObjectMapper를 사용하여 Realm 모델에 JSON 매핑 - Qiita
    정약이 Swift 용 ObjectMapper 읽어 보았다 - Qiita

    좋은 웹페이지 즐겨찾기