[iOS] 네이버 클로바 API 연동

Junyoung Park·2022년 9월 4일
0

ToyProject

목록 보기
9/11
post-thumbnail

네이버 클로바 API 연동

구현 목표

  • 네이버 클로바 인공지능 서비스가 제공하는 API 사용
  • 스위프트를 통해 API 호출 담당 클래스 구현
  • 비동기 데이터를 Combine 프레임워크를 통해 다루기

구현 과정

네이버 클로바 API

  • 한국어 감성 분석 관련 데이터를 검색하던 중 네이버 클로바가 제공하는 API 서비스를 발견, 커스텀 클래스를 구현하기 전 성능의 만족도를 점검하는 지표로 활용하려는 목적으로 API 사용하기 결정

[이렇게 사용하세요!] 텍스트 감정 분석 서비스 구현하기 (CLOVA Sentiment 활용기)

  • 클로바 API가 제공하는 가이드라인에 따라 API 사용

  • 콘솔에 Clova Sentiment 파트를 찾아 등록을 하자.

  • iOS 앱을 사용하고 있기 때문에 번들 아이디만 등록
  • 클라이언트 아이디 및 시크릿 정보는 API 호출 시 필요한 정보이기 때문에 체크
  • 한 달 1000건 무료로 사용 가능한 API

ClovaDataService Class

  • API 사용 가이드라인에 따라서 URLRequest에 HTTP 메소드 및 헤더 부분을 설정
import Foundation

enum ClovaAPI {
    case sentiment
    
    var clientHeaderKeyID: String {
        switch self {
        case .sentiment: return "X-NCP-APIGW-API-KEY-ID"
        }
    }
    
    var clientHeaderSecretID: String {
        switch self {
        case .sentiment: return "X-NCP-APIGW-API-KEY"
        }
    }
    
    var clientHeaderContentType: String {
        switch self {
        case .sentiment: return "Content-Type"
        }
    }
    
    var clientID: String {
        switch self {
        case .sentiment: // [YOUR CLIENT ID]
        }
    }
    
    var clientSecret: String {
        switch self {
        case .sentiment: // [YOUR CLIENT SECRET]
        }
    }
    
    var clientContent: String {
        switch self {
        case .sentiment: return "application/json"
        }
    }
    
    var urlString: String {
        switch self {
        case .sentiment: return "https://naveropenapi.apigw.ntruss.com/sentiment-analysis/v1/analyze"
        }
    }
}
  • 추후 클로바 감성 분석이 아닌 다른 종류의 API를 호출할 때에도 적용할 수 있을 것 같아 이넘으로 설계
protocol DataService {
    var sentimentAnalysisPublisher: Published<SentimentModel?>.Publisher { get }
    func fetchSentimentAnalysis(_ text: String)
}
  • 클로바 API 데이터 서비스가 사용해야 하는 퍼블리셔되는 감정 모델 데이터 + 데이터 패치 함수
  • 퍼블리셔 값을 프로토콜에 넣기 위한 Published<[Your Model]>.Published
import SwiftUI
import Combine

class ClovaDataService: DataService {
    var sentimentAnalysisPublisher: Published<SentimentModel?>.Publisher {
        $sentimentAnalysis
    }
    @Published var sentimentAnalysis: SentimentModel? = nil
    var cancellabes = Set<AnyCancellable>()
    
    func fetchSentimentAnalysis(_ text: String) {
        guard let urlRequest = getURLRequest(text) else { return }
        URLSession.shared.dataTaskPublisher(for: urlRequest)
            .subscribe(on: DispatchQueue.global(qos: .background))
            .receive(on: DispatchQueue.main)
            .tryMap(handleOutput)
            .decode(type: SentimentModel.self, decoder: JSONDecoder())
            .sink { completion in
                switch completion {
                case .finished:
                    print("SUCCESS")
                    break
                case .failure(let error):
                    print(error.localizedDescription)
                }
            } receiveValue: { [weak self] returnedData in
                guard let self = self else { return }
                self.sentimentAnalysis = returnedData
                print(returnedData)
            }
            .store(in: &cancellabes)
    }
    
    private func getURL() -> URL? {
        let urlString = ClovaAPI.sentiment.urlString
        guard let url = URL(string: urlString) else { return nil }
        return url
    }
    
    private func getURLRequest(_ body: String) -> URLRequest? {
        guard let url = getURL(), let body = try? JSONSerialization.data(withJSONObject: ["content" : body]) else { return nil }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        urlRequest.setValue(ClovaAPI.sentiment.clientID, forHTTPHeaderField: ClovaAPI.sentiment.clientHeaderKeyID)
        urlRequest.setValue(ClovaAPI.sentiment.clientSecret, forHTTPHeaderField: ClovaAPI.sentiment.clientHeaderSecretID)
        urlRequest.setValue(ClovaAPI.sentiment.clientContent, forHTTPHeaderField: ClovaAPI.sentiment.clientHeaderContentType)
        urlRequest.httpBody = body
        return urlRequest
    }
    
    private func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
        guard
            let response = output.response as? HTTPURLResponse,
            response.statusCode == 200 else { throw URLError(.badServerResponse) }
        return output.data
    }
}
  • URLRequest를 설정하는 게 본 데이터를 패치해오는 데 있어서 핵심
  • Combine 프레임워크의 sink를 통해 API를 호출한 뒤 패치된 데이터를 sentimentAnalysis 퍼블리셔 변수에 넣기
  • 데이터 패치는 백그라운드 스레드, UI 관련 그리기는 메인 스레드 + 약한 참조
  • API 가이드라인에 따라 HTTP 상태 코드가 200인 경우에만 핸들링

SentimentModel

import Foundation

struct SentimentModel: Codable {
    let document: Document
    let sentences: [Sentence]
}

// MARK: - Document
struct Document: Codable {
    let sentiment: String
    let confidence: Confidence
}

// MARK: - Confidence
struct Confidence: Codable {
    let neutral, positive, negative: Double
}

// MARK: - Sentence
struct Sentence: Codable {
    let content: String
    let offset, length: Int
    let sentiment: String
    let confidence: Confidence
    let highlights: [Highlight]
}

// MARK: - Highlight
struct Highlight: Codable {
    let offset, length: Int
}
  • 클로바 API가 리턴하는 데이터 모델을 그대로 받기 위한 Codable 프로토콜

구현 화면


클로바 API가 리턴하는 감성 분석은 문자 그대로 '긍정', '중립', 부정' 수치로 이루어져 있었기 때문에 기쁨, 분노 등 별도의 보다 자세한 감정 분석을 위해서는 추가 작업이 필요해보였다.

profile
JUST DO IT

0개의 댓글

관련 채용 정보