iOS/Refactoring

MyPageProfileEditViewModel 코드 개선하기 (2)

suee97 2024. 7. 17. 17:42

이전글 : https://ios-dev-su.tistory.com/13

 

MyPageProfileEditViewModel 코드 개선하기 (1)

기존 코드 수정 전의 기존 코드는 다음과 같습니다.더보기 import UIKitimport Combineimport Alamofireimport Kingfisherfinal class MyPageProfileEditViewModel { // MARK: - Properties private let coordinator: MyPageCoordinator private var

ios-dev-su.tistory.com

 

이번 포스팅에서는 코드와 구조도 개선하도록 하겠습니다.

 

- tagFieldChangedFromUser() 함수 개선

// 유저의 텍스트필드 입력에 따른 뷰모델 및 UI 동기화
func tagFieldChangedFromUser() {
    if !tagFieldString.isEmpty {
        let tags = tagFieldString
            .components(separatedBy: ",")
            .map{ $0.trimmingCharacters(in: .whitespaces) }

        for i in 0..<recommendTagItems.count {
            let idx = tags.firstIndex(of: recommendTagItems[i].tag ?? "") ?? -1
            recommendTagItems[i].isSelected = (idx != -1)
        }
    }
    updateRecommendTagItemSelectCount()
}

 

유저가 직접 tagFieldString을 변경했을 때, 추천 태그의 상태를 올바르게 변경하는 함수입니다.

 

개선 후 함수는 다음과 같습니다.

func updateRecommendTagSelectionFromUserTagFieldEditing() {
    if !tagFieldString.isEmpty {
        let tags = tagFieldString
            .components(separatedBy: ",")
            .map{ $0.trimmingCharacters(in: .whitespaces) }
        for i in 0..<recommendTagItems.count {
            guard let recommendTag = recommendTagItems[i].tag else { continue }
            recommendTagItems[i].isSelected = tags.contains(recommendTag)
        }
    }
    updateRecommendTagItemSelectCount()
}

 

1. 함수명 수정

함수의 역할을 보다 명확하게 설명하는 이름으로 변경했습니다.

updateRecommendTagSelectionFromUserTagFieldEditing = 유저 태그 필드 수정으로부터 추천 태그 선택을 업데이트한다

tagFieldChangedFromUser(유저로부터 태그 필드가 변경되었을 때..) 보다는 함수가 어떤 일을 하는지 쉽게 이해할 수 있습니다.

 

2. 스타일링, 주석제거

// 유저의 텍스트필드 입력에 따른 뷰모델 및 UI 동기화

이 주석은, 사실, 저 또는 이걸 보는 누군가가 이러한 정보가 필요하다고 생각해서 작성한 것입니다. 즉, 함수를 제대로 안짜고 주석으로 퉁칠려고 한 것입니다. 따라서 지워줍니다.

고차함수는 한 줄을 넘는 것보다 간략하게 표현할 수 있으면 그렇게 하는게 보기가 이쁘더라구요. 그래서 한 줄로 만들었습니다.

 

3. 로직 수정

recommend tag를 가져오고, 근데 없으면 빈 문자열로 퉁치고, 인덱스를 찾고, 만약 없으면 -1로 만들고 .. 이런건 알고리즘 풀이에서나 볼법한 쓰레기같은 코드입니다.

tags가 recommendTag를 포함하는지 확인하는 로직으로 수정했습니다.

 

- validateEmail

func validateEmail(text: String) -> Bool {
    if text.isEmpty { return true }
    let pattern = "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}\\@[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25})+"
    guard let _ = text.range(of: pattern, options: .regularExpression) else {
        return false
    }
    return true
}

 

이 함수는 다음과같이 개선할 수 있습니다.

func checkEmailValidation(email: String) -> Bool {
    if email.isEmpty { return true }
    let pattern = "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}\\@[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25})+"
    if let _ = email.range(of: pattern, options: .regularExpression) {
        return true
    } else {
        return false
    }
}

 

1. 함수와 파라미터 이름을 보다 명확하게 수정했습니다.

2. guard문보다 if-let 문이 보다 읽기 자연스럽기에 if-let 으로 수정했습니다.

예를 들어, if let의 경우 "만약 이메일이 특정패턴에 속한다면 ..." 이라고 읽을 수 있습니다.

 

 

- 태그 아이템 관련 구조 개선

함수 수정은 여기까지 하고, 구조를 조금 고쳐보도록 하겠습니다.

일단 가장 꼴뵈기싫고 이상하게 보이는 부분은 여기일 것입니다. (전체 코드는 이전 포스팅에서 확인할 수 있습니다.)

 

뭔가 이상합니다.

가장 이상한 것은 저 태그들의 목록이 "세부사항"이 아니라, "정책"과 관련된 것인데, 대놓고 뷰모델에 있다는 것입니다.

따라서, 저 태그들의 목록을 포함하는 구조체를 만들고 거기에 담도록 하겠습니다.

 

1. JobTags, RecommendTags 구조체 생성

struct JobTags {
    private(set) var tagList = ["제품 디자이너", "시각 디자이너", "UX 디자이너", "패션 디자이너", "3D 아티스트", "크리에이터", "일러스트레이터", "공예가", "화가"]
}

struct RecommendTags {
    private(set) var tagList = ["제품", "공예", "그래픽", "회화", "UX", "UI", "모던", "클래식", "오브제", "감성적인", "심플", "귀여운", "키치한", "힙한", "레트로", "트렌디", "부드러운", "몽환적인", "실용적인", "빈티지", "화려한", "재활용", "친환경", "지속가능한", "밝은", "어두운", "차가운", "따뜻한"]
}

 

2. 태그들을 TagItem으로 바꿔 전달해주는 클래스 생성

final class TagItemConverter {
    private let jobTags = JobTags().tagList
    private let recommendTags = RecommendTags().tagList
    
    static func getTagItems(from tagList: [String]) -> [TagItem] {
        return tagList.map { TagItem(tag: $0, isSelected: false) }
    }
    
    func getJobTagItems() -> [TagItem] {
        return TagItemConverter.getTagItems(from: jobTags)
    }
    
    func getRecommendTagItems() -> [TagItem] {
        return TagItemConverter.getTagItems(from: recommendTags)
    }
}

 

3. 수정사항 반영

// properties
private let tagItemConverter = TagItemConverter()
var categoryTagItems: [TagItem]
var recommendTagItems: [TagItem]

// init
self.categoryTagItems = tagItemConverter.getJobTagItems()
self.recommendTagItems = tagItemConverter.getRecommendTagItems()

 

4. 리리 팩토링

Converter는 태그를 아이템으로 "변환"해주는 함수이지 마이페이지 프로필 설정에 활용될 데이터를 바꿔주는 역할을 하는 것은 아닙니다.

따라서 Converter는 init함수에서 태그 리스트를 받고, 아이템을 반환하는 함수를 제공하는 클래스로 변경했습니다.

struct JobTags {
    private init() {}
    
    static var tagList = ["제품 디자이너", "시각 디자이너", "UX 디자이너", "패션 디자이너", "3D 아티스트", "크리에이터", "일러스트레이터", "공예가", "화가"]
}

struct RecommendTags {
    private init() {}
    
    static var tagList = ["제품", "공예", "그래픽", "회화", "UX", "UI", "모던", "클래식", "오브제", "감성적인", "심플", "귀여운", "키치한", "힙한", "레트로", "트렌디", "부드러운", "몽환적인", "실용적인", "빈티지", "화려한", "재활용", "친환경", "지속가능한", "밝은", "어두운", "차가운", "따뜻한"]
}

final class TagItemConverter {
    private var tagList: [String]
    
    init(tagList: [String]) {
        self.tagList = tagList
    }
    
    func getTagItems() -> [TagItem] {
        return convertTagItems()
    }
    
    private func convertTagItems() -> [TagItem] {
        return tagList.map { TagItem(tag: $0, isSelected: false) }
    }
}

// ViewModel 변경
var categoryTagItems: [TagItem]
var recommendTagItems: [TagItem]

init(user: PlainUser) {
    self.user = user
    let jobTagList = JobTags.tagList
    let RecommendTagList = RecommendTags.tagList
    self.categoryTagItems = TagItemConverter(tagList: jobTagList).getTagItems()
    self.recommendTagItems = TagItemConverter(tagList: RecommendTagList).getTagItems()
    setUpCategoryTagItems()
    setUpRecommendTagItems()
    downloadProfileImage()
    downloadBackgroundImage()
}

 

5. TagItemConverter Test

 

 

포스팅이 길어져서 다음 포스팅에 이어서 작성하겠습니다.