감자주먹밥

[IOS] 첫 프로젝트 Thinking Mirror 회고 및 정리 본문

IOS/UIKit

[IOS] 첫 프로젝트 Thinking Mirror 회고 및 정리

JustHm 2022. 9. 28. 22:01
728x90

첫 프로젝트 시작

인터넷에서 강의를 보고 여러 라이브러리 사용방법과 기본적인 UI들 사용하는 방법을 알았는데 바로 다음단계로 넘어가기 보단 무언가 만들어 보고 가자는 생각이 들어 간단한 어플리케이션을 만들어보기로 했다.

보통 API를 활용한 어플을 처음 많이 만들어본다고 하여서 여러 API를 찾아보다 Naver에서 제공하는 Clova Face Recognition API를 사용해 보기로 했다.

처음 만들기 시작하고 끝낸 시점은 3월~4월 사이 짧게 끝났지만, 선배님게 피드백을 받고 다른것을 공부하다보니 피드백 수정도 못하고 정리도 못하고 있다가 이번에 수정과 정리를 한 번에 하게 되었다.여러가지 문제를 해결했던 것을 정리하기.먼저 프로젝트 깃헙 링크. https://github.com/JustHm/Thinking-Mirror

 

GitHub - JustHm/Thinking-Mirror

Contribute to JustHm/Thinking-Mirror development by creating an account on GitHub.

github.com

 

프로젝트 설명

Clova Face Recognition API에 닮은 유명인을 찾아주고, 나이와 성별, 표정을 추정하는 2개의 API를 활용해 만들었다.

기본적으로 이미지 업로드, API호출 및 처리, 결과로만 간단히 구성되어 있다!

 

프로젝트 중 문제

1. 사진을 찍거나 앨범에서 사진 데이터를 가져올 때.

바로 만들 수 있을거란 생각이였지만 가장 중요한 사진 가져오기 마저 해본적이 없었다. 이래서 프로젝트를 해봐야 실력이 쌓인다 하는걸까.

그래서 검색을 통해 UIImagePickerController, PHPickerViewController을 알게 되었다. 

전부 사진에 접근해 사진, 영상을 가져온다는 건 똑같지만 다른점이 있다.

a. UIImagePickerController - 유저 미디어 라이브러리에 직접 접근하기 때문에 권한이 필요하다. 사진을 직접 찍어서 가져올 수 있다. 컨텐츠를 하나만 가져올 수 있다. 이리저리 편집이 가능하다! 

* 아이패드에선 무조건 popover 방식으로 띄워야한다고 합니다.. 검색하다 알게 됐어요. (공식문서 발)

b. PHPickerViewController - 사진을 날짜, 장소, 사람 등 을 통해 검색할 수 있다. a와 다르게 권한이 필요없다.(일부 제한된 미디어에는 권한 필요함), 사진을 직접 찍어 올 수 없음. 비디오 자동 압축이 안되어 별도로 처리해야함. 컨텐츠 다중 선택 가능.

두 개 말고도 Photos? PhotoKit이란것도 알게 되었는데 위 두개만 비교해봐도 간단하게 사진을 찍거나 앨범에서 가져와 그대로 업로드 하는 기능만 있으면 될 것 같다는 판단에 UIImagePicker를 사용하게 됐다.

다음에 만들어볼 어플은 여러 이미지를 사용하는 것으로 구상해 놨으니 PHPicker or Photos에 대해서 자세히 알아보고 사용해봐야겠다.

https://velog.io/@msi753/PHPicker

 

PhotoKit

PhotoKit

velog.io

먼저 UIImagePicker를 사용하기 전, 카메라를 사용할 수 도 있기 때문에 카메라 권한을 받을 수 있게 info.plist에 추가를 해 준다.

private func configurePicker(pickermode: PickerMode) -> UIImagePickerController {
        let picker = UIImagePickerController()
        switch(pickermode) {
        case .camera:
            picker.sourceType = .camera
            picker.cameraFlashMode = .off
        case .album:
            picker.sourceType = .photoLibrary
        }
        picker.delegate = self
        return picker
}

사용 방법은 간단했다. UIImagePickerController를 할당하고 직접 찍어 가져올 것인지, 앨범에서 가져올 것인지에 대해 정하고 미디어를 가져오면 된다. 위에 보이는 Mode, Type 말고도 다양한 옵션들이 있다. 편집, 동영상 등 많이 있긴 한데 공식문서 보고 나중에 정리 해 놓는게 좋을 거 같다.

https://developer.apple.com/documentation/uikit/uiimagepickercontroller

 

Apple Developer Documentation

 

developer.apple.com

2. 사진 데이터 API를 통해 보내기.

URLSession을 사용해서 해결해도 좋지만, 라이브러리 배운김에 Alamofire를 사용해 API 처리를 하게 되었다.

그리고 Naver API에서 호출 방법을 보는데..

지금까지 연습삼아 했던 호출과는 조금 달랐다. ClientID, Secret 정보도 헤더에 포함시켜야 했고 multipart 형식으로 요청을 보내야 했다.

모두 처음 들어보는 것이였기 때문에 알아보고 정리를 하기 시작했다.

예전에 써 놓았던 글

먼저 헤더, 지금까지 했던 API 호출에 SecretKey는 모두 주소에 붙여서 사용을 했었는데 여기서는 Header에 요청자의 정보를 넣어야 했다. 그래서 먼저 HTTPHeaders 타입의 변수를 만들어서 Content-Type, Client-ID, Client-Secret 정보를 넣어놓고 Alamofire에 넘겨 주었다.

private func uploadRequest(url: String, uploadImage: UIImage) -> UploadRequest {
	return AF.upload(multipartFormData: { multipartFormData in
		multipartFormData.append(uploadImage.jpegData(compressionQuality: 1.0)!, withName: "image", fileName: "image.jpg", mimeType: "image/jpeg")
	}, to: url, method: .post, headers: headers)
}

Alamofire에서 multipart 형식을 사용하는 건 어렵지 않았다. 원래는 request만 사용해 봐서 처음해보는 업로드가 생소했을 뿐,

데이터를 업로드 할 수 있는 AF.upload를 통해 간편하게 올릴 수 있었다.

Alamofire upload Method

그리고 multipart 형식으로 보내기 쉽게 메서드에 이미 정의가 되어 있었다. 업로드할 데이터를 API에서 요구하는 형식에 맞게 업로드를 해주고, 헤더만 넣으면 간단하게 요청을 할 수 있다.

func celebrityAPI(uploadImage: UIImage, completion: @escaping (Response<CelebrityData>)->Void ) {
        let url = APIInfo.hostInfo + APIList.celebrity
        
        uploadRequest(url: url, uploadImage: uploadImage).responseData(completionHandler: { response in
            switch response.result {// response.result를 써야 Result<value, error> type을 사용할 수 있다.
            case let .success(data):
                do {
                    let decoder = JSONDecoder()
                    let result = try decoder.decode(Response<CelebrityData>.self, from: data)
                    completion(result)
                } catch {
                    print("\(#file.split(separator: "/").last!)-\(#function)[\(#line)]")
                }
            case let .failure(error):
                print("\(error) \(#file.split(separator: "/").last!)-\(#function)[\(#line)]")
            }
        })
    }

마지막으로 요청을 통해 받은 데이터를 디코딩 해서 사용만 해주면 된다.

여기서 작은 문제가 하나 있었다. 두 개의 API가 같은 host에서 사용되고, 데이터도 하부는 다르지만 비슷한데 Generic을 사용해서 두 API를 하나의 메서드로 호출 할 수 있지 않을까? 라는 생각이 들었다.

// 한 번에 처리하고 싶은데 decode&Result에 들어갈 data type을 어떻게 해야할 지 모르겠다.
// template로 하더라도 유추할 수 있는 부분이 없움... 파라미터에 따로 넘겨줘야하나?
// enum option으로 api선택을 하고 그거에 맞게 넘기면 되지 않을까?
func allAPI<T: Decodable>(url: String, uploadImage: UIImage, completion: @escaping (Response<T>)->Void) {
}

위와 같이 함수 틀만 작성해 보긴 했는데.. 이번에 피드백 수정만 하고 정리를 빨리해야겠단 생각 때문에 실험은 해보지 않았다.

-- 추가 예정 --

3. 피드백 반영

완벽하다 싶게 만든 첫 프로젝트를 선배님께 보여드리면서 피드백을 부탁드렸더니 진짜 생각도 못한 부분들을 많이 지적을 받았다. 

이제서야 수정하는게 참 게으르긴 했지만 앞으로 열심히 하지는 마음을 갖고, 피드백을 받으면서 알게된 정보들을 정리하자.

a. Model 제네릭 사용

API를 호출해 반환받은 데이터를 가지게 될 모델은 Info, Faces로 구성되어 각각 얼굴 갯수 카운트와 얼굴에 대한 검출 정보를 상위에 가지고 있는데, 2개의 API 모두 상위 모델이 같았다.  faces에서 검출을 통해 나오는 데이터들이 다를 뿐 상위는 같기 때문에 Generic을 사용해  병합하게 되었고, 하위 모델을 직접 넣는 방식으로 변경되었다.

struct Response<T: Codable>: Codable {
    let info: Info
    let faces: [T]
}

struct Info: Codable {
    let faceCount: Int
}

b. API 정보 관리

처음 API를 호출하는 코드를 작성할 때 URL을 2개 API가 다 따로따로 가지게 만들었었다. 작은 프로젝트지만 큰 프로젝트에선 유지보수 측면에서 좋지 않은 코드기 때문에 host와 APIList를 따로 관리하여 사용하는 방법을 알려주셨다.

struct APIInfo {
    static let hostURL = "https://openapi.naver.com/v1/vision/"
}

struct APIList {
    static let face = "face"
    static let celebrity = "celebrity"
}

// in use
let url = APIInfo.hostURL + APIList.face

URL은 위와같이 이뤄져 있는데, 같은 HOST에 다른 API를 사용하고 있기 때문에, 이렇게 나눠서 관리하는 것이 훗날 수정에도 좋다는 것을 알게 되었다.

다른 API가 추가되도, 위 구조에서 크게 변경될 점이 없으니 정말 좋은 것 같다.

정리.

이번 프로젝트로 진짜 신경써야 할 부분이 많고, 조금만 생각 더 해보면 코드도 좋게 작성할 수 있다는 것을 알게 되었다. 

특히 코딩 컨벤션을 지키지 않은것도 너무 많았고, 중복코드도 너무 많았기 때문에 현타가 많이 왔었다. 

다음 프로젝트를 멋지게 만들기 위해 오늘 정리한 것들 외에도 많은 것들을 신경쓰면서 개발해야겠다.

사실 이 프로젝트도 완전 완성은 아닌 것 같다. 사진을 찍어서 넘겼을 때 사진이 넘어가지 않는 오류도 있고, UIImagePicker를 사용할 때 카메라가 사용가능한 상태인지 체크하는 구문도 없어 오류가 발생 할 수 있고, 사진 및 앨범 권한을 안 받은 채로 이미지를 선택하려 하는 것에 대한 예외처리, API오류코드 에 대한 예외처리,,, 정리하면서 코드를 돌려보니 생각보다 많은 부분에 문제가 있었다. 시간 나면 고치고, 다음 프로젝트에선 조금의 오류도 없는 코드를 만들려고 노력해야겠다.

728x90
Comments