vanism2091 / ios-cantact-manager

연락처 관리 프로젝트 저장소입니다.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ios-cantact-manager

객체 설명

Type 이름 역할 비고
enum StringLiteral 프로그램 내 상수 문자열들 나중에 .strings로 바꿔보자
enum IOManager, PrintType 프로그램 내 입력, 출력 담당
enum IOError 프로그램 입출력 Error 정리
final class ContactManager 사용자에게 받은 입력을 1차 검증함
- 주어진 메뉴만 입력했는지
- 연락처 정보 중 '/' 형식 지켰는지
- 검색하려는 이름 형식 지켰는지
Phonebook, userInfo와 소통해 연락처 정보를 관리, 검색
final class Phonebook 연락처를 관리하는 전화번호부
CM의 요청에 따라 연락처 추가, 설명 반환
struct UserInfo 유저 정보를 담은 구조체
init시 입력 받은 연락처 정보의 이름, 나이, 연락처를 검증함

순서도

전체

연락처 추가

연락처 목록 조회

연락처_목록_조회

연락처 검색

연락처_검색

Step 1

Step1 순서도

issue

  • / 입력 검증 방법
    : ContactManager.swift 의 parse() 를 통해 ‘/‘을 검증하였습니다. 저희는 기존 기준을 더 명확히 하기 위해 추가적인 제한을 더 추가했습니다.

    • “이름 / 나이 / 전화번호” : / 기준으로 앞,뒤 space
    • “이름/나이/전화번호” : / 기준으로 앞,뒤 모두 space X
    • 이 두가지의 경우에만 ‘/‘ 검증을 통과하도록 했습니다.
    • 예를들어 “이름/ 나이/ 전화번호” 이런 경우, 통과하지 못하도록 구현했습니다.
  • / 검증 정규식

    let inputPattern = #"^.+\b(?<sep>( \/ )|(\/))(\b[^\s]+\b)\k<sep>(\b[^\s]+)$"#

Step1 실행 화면

  • 올바른 입력
  • 올바르지 않은 입력
    • 아무것도 입력되지 않은 경우
    • 입력 형식이 잘못된 경우
    • 이름, 나이, 연락처가 잘못 입력된 경우

Step 2

Step2 순서도

  • 무한루프를 반복문(while true)에 비해 재귀함수로 구현하는 것의 이점?
  1. 재귀함수

    • 함수 안에 자기 자신을 호출하는 함수와 종료를 위한 조건이 존재한다.
    • 조건에 수렴하지 않으면 무한한 함수 호출을 일으키기 때문에 스택 오버플로우를 일으킬 수 있다.
    • 반복문보다 실행 속도가 느리다.
  2. 반복문

    • 특정 조건에 도달하기 전까지 일련의 명령을 반복적으로 실행한다.
    • 반복문은 무한 루프에 빠질 경우, 스택 메모리가 아닌 CPU 사이클을 반복적으로 사용한다.
    • 재귀함수보다 실행 속도가 빠르다.
  3. 그럼에도 불구하고 재귀함수를 쓰는 이유는?

    • 알고리즘 자체가 재귀함수에 더 자연스러운 경우
    • 가독성이 더 좋아진다.
    • 변수 사용을 줄인다.
      → 반복문보다 재귀함수를 사용했을 때, 변수의 수를 줄일 수 있다. 변수의 수를 줄이면 프로그램은 변경 가능한 상태(mutable state)를 제거하고 오류가 발생할 확률을 줄일 수 있다.

Step2 실행 화면

  1. 메뉴 - 올바른 메뉴 선택 - 바르지 못한 메뉴 선택 - 종료
  2. 연락처 추가
    • Step 1에서 보여드린 연락처 추가와 동일한 코드이기 때문에 연락처 검증 test case 별 동작 사진은 따로 첨부하지 않겠습니다.
    • 중복 되지 않는 연락처 추가
    • 중복된 연락처 추가

Step 3

Step3 순서도

핵심 구현 요구 사항

  • 연락처 전체 조회
    • 출력은 이름순으로
  • 연락처 검색
    • 이름으로 검색

고민한 부분: 연락처 데이터 저장 방식 / 데이터 구조

Phonebook Class 생성

// Phonebook.swift
final class Phonebook {
    private var contacts: [String:Set<UserInfo>]

    init(contacts: [String:Set<UserInfo>]) {
        self.contacts = contacts
    }

    func add(contact: UserInfo) -> Bool {
        let (inserted, _) = contacts[contact.name, default: Set<UserInfo>()].insert(contact)
        return inserted
    }
}

Phonebook: class vs struct

  • Phonebook의 contacts 속성의 타입 때문에 이를 class로 구현했다. contacts는 Key/Value로 String과 Set을 사용한다. String과 Set은 struct지만, 가변 길이 데이터이기 때문에 Heap에 저장되게 된다. Understanding Swift Performance를 참고한 결과,
    • Struct containing many reference가 Class에 비해 reference counting 더 높고
    • 비록 Phonebook의 property는 contacts 1개지만,
    • contacts가 dictionary이고, key, value가 String, Set으로 모두 Heap에 저장되기 때문에
    • reference counting이 Struct로 만들었을 때 더 높을 것이라고 판단했다
    • 따라서 Phonebook은 struct보다 class에 저장하는 것이 더 맞다고 생각했다!
    • 개념적으로 배운 것을 실제 코드에 적용하기에 아직 확신이 부족하여,
    • ❓우리가 잘 이해한게 맞고, 실제로 그렇게 동작하는 것인지 더 공부해서 알아봐야겠다😊

Dictionary를 선택한 이유

유저가 추가한 연락처를 ContactManager 객체가 어떻게 저장할지, 연락처 관리를 위한 데이터 구조를 고민했다.

  • Key값을 userInfo.name: String, Value값이 Set<UserInfo>인 contacts Dictionary를 생성했다
/// key: userInfo.name
// 중복 고려 전
let contacts: [String: Array<UserInfo>]
// 중복 고려 후
let contacts: [String: Set<UserInfo>]

처음에는 유저의 이름이 key이고 value가 UserInfo의 Collections Type인 Dictionary를 생각했다 Collections Type 중 Dictionary를 선택한 이유는, 프로그램의 기능 중 검색 기능이 이름으로 검색하기 때문

  • 평균 시간 복잡도
    Collection Type 검색 by 이름 추가 조회
    Dictionary_Array O(1) O(1) O(NlogN)
    Dictionary_Set O(1) O(1) O(NlogN)

처음에는 중복을 고려하지 못해서 Dictionary_Array로 가닥을 잡고 구현했다
그런데 이름, 나이, 연락처가 같은 중복 연락처인 경우에도 여과없이 Array에 추가되는 점이 바람직하지 않다고 생각했고 이를 해결하기 위해 Dictionary_Set으로 데이터 구조를 수정했다

Dictionary Default Parameter

Dictionary 구현하며 새로 배운 Swift 문법은 subscript의 default parameter 이다
key의 존재 여부를 따로 체크하지 않아도 괜찮아서 편리했다! 그런데 위험하지는 않은지, 실제 디바이스의 메모리단에서 어떻게 동작하는지는 등 아직 Dictionary, Array의 실제 구현과 컴퓨터 구조에 익숙하지 않아서 설명할 수 없는 점이 아쉬웠다. 조금 더 공부하고 고민해봐야겠다🙃

var d = ["a": []]
d["a"]?.append("haha")
print(d) // ["a": ["haha"]]

d["b"]?.append("haha") // ["a": ["haha"]]

d["b", default: []].append("bb")
print(d) // ["a": ["haha"], "b": ["bb"]]

d["b", default: []].append("bbc")
print(d) // ["a": ["haha"], "b": ["bb", "bbc"]]

연락처 중복 추가 방지를 위해 Set<UserInfo>로 변경

  1. Set의 특성을 이용해 Value값에 연락처가 중복 저장되지 않도록 구현
    • 연락처 중 이름이 동일한 연락처는 존재할 수 있지만, 이름-나이-번호 가 모두 동일한 연락처는 중복으로 생각하고
    • "동일한 연락처가 존재합니다. 다른 연락처를 입력해주세요."를 출력
  2. Array에서 Set으로 구현을 변경하면서,
    1. UserInfo를 Hashable 프로토콜을 채택했다
      • static let == 연산과
      • hash(into:) 함수를 구현하고 hasher.combine()에 name, age, phone을 넣었다
    2. UserInfo를 phonebook.contacts에 추가할 때,
      • append() 대신 insert() method를 사용했다
      • subscript(_:default:)는 Set에서도 구현되어 있었다👍
    3. phonebook.add(contact:)가 반환하는 Bool을 이용해서 ContactManager는 연락처가 성공적으로 추가되었는지 체크한다

Step3 실행 화면

  • 연락처 전체 조회 - 연락처가 저장되지 않은 경우
  • 연락처 검색
    • 연락처에 찾는 이름이 없을떄

About

연락처 관리 프로젝트 저장소입니다.


Languages

Language:Swift 100.0%