우리는 여러 타입에 “자기소개” 기능을 부여하고자 합니다.
name 변경 알림
변경 이전 값: 피규어
변경 이후 값: 옵티머스[Introducible] 타입 배열을 정의하고, Robot, Cat, Dog 인스턴스 1개씩을 append 해주세요.배열을 순회하며 각 타입 고유의 메서드들을 호출하는 코드를 작성해주세요.
일단 저 문제를 보고
모든 타입이 공통으로 갖는 자기소개 기능을 강제하도록 프로토콜을 만들어봄
protocol Introducible {
var name: String { get set }
func introduce() -> String
}
읽기만 하니까 let name으로 해도 되겠지 라고 생각했지만
Robot은 name을 바꾸기 때문에 get set이 필요해서 프로토콜에서도 var name: String { get set }으로 명시해야 함
-> 모든 타입에 공통된 name 속성과 introduce() 메서드를 강제하기 위해 정의
-> Robot은 name을 변경하므로 { get set }으로 명시.
Robot : didSet 사용하고, 참조타입이라 변경 감지에 유리할 거 같아서 class 사용함
Cat, Dog : struct 사용
struct는 값 타입이라 복사가 일어나는데 배열에 들어간 후에 name을 바꿔도 실제 배열 안 값엔 반영 안 됨
didSet은 구조체의 복사본엔 의미 없을 수 있음
속성 변경 감지를 확실히 하고 싶어서 class로 선언함
class Robot: Introducible {
var name: String {
didSet {
if oldValue != name {
print("name 변경 알림")
print("변경 이전 값: \(oldValue)")
print("변경 이후 값: \(name)")
}
}
}
init(name: String) {
self.name = name
}
func introduce() -> String {
return "안녕하세요, 저는 \(name)입니다."
}
func batteryCharge() {
print("\(name)이(가) 충전 중입니다.")
}
}
name 변경 감지를 위해 didSet 사용
값 타입은 변경 감지에 불리하니까 참조 타입 선택
변경 전후 비교해서 동일 값 변경은 무시
struct Cat: Introducible {
var name: String
func introduce() -> String {
return "안녕하세요, 고양이 \(name)입니다."
}
func meow() {
print("\(name): 야옹~")
}
}
struct Dog: Introducible {
var name: String
func introduce() -> String {
return "안녕하세요, 강아지 \(name)입니다."
}
func bark() {
print("\(name): 멍멍!")
}
}
상태 변경 감지가 필요 없고 값 복사가 허용되어 struct로 선언
Introducible 채택 후에 introduce 구현
각 타입 고유의 메서드도 정의
배열 타입이 Introducible로 제한되면 공통 기능(name, introduce())만 접근 가능
각 타입 고유 기능은 다운캐스팅 필요 (as?)
var beings: [Introducible] = []
let robot = Robot(name: "피규어")
let cat = Cat(name: "나비")
let dog = Dog(name: "초코")
beings.append(robot)
beings.append(cat)
beings.append(dog)
공통된 기능을 가진 객체들을 한 배열에 담기 위해 Introducible 사용
Robot, Cat, Dog 인스턴스 추가
배열에 담긴 후에도 정보 유지를 통해 다운캐스팅 가능
for being in beings {
print(being.introduce())
if let robot = being as? Robot {
robot.batteryCharge()
} else if let cat = being as? Cat {
cat.meow()
} else if let dog = being as? Dog {
dog.bark()
}
}
Introducible의 공통 기능은 바로 호출 가능
각 타입 고유 기능은 확인 후 다운캐스팅하여 호출
robot.name = "옵티머스" // 변경 알림 출력됨
robot.name = "옵티머스" // 출력 안됨 (같은 값)
Robot은 class라서 동일 인스턴스 상태 변경 가능
name 변경 시 didSet 내 비교 조건에 따라 로그 출력 여부 결정
같은 값으로 다시 설정해도 로그는 출력되지 않음!
우리는 간단한 택배 도착 예측 시스템을 만들고 있다고 가정합니다.
사용자에게 예상 도착일을 알려주려 하지만, 다음과 같은 여러 상황에서 문제가 발생할 수 있습니다:
func predictDeliveryDay(for address: String, status: DeliveryStatus) throws -> Stringenum DeliveryStatus {
case notStarted
case inTransit(daysRemaining: Int)
case error
}
→ 상태마다 다르게 처리할 수 있게 설계
→ 특히 inTransit는 연관 값을 사용해서 남은 일수 정보를 담음
enum DeliveryError: Error {
case invalidAddress
case notStarted
case systemError(reason: String)
}
func predictDeliveryDay(for address: String, status: DeliveryStatus) throws -> String
사용자에게 예상 도착일을 알려주는 함수
throws로 상황에 따라 에러를 던질 수 있음
내부 동작
guard !address.isEmpty else {
throw DeliveryError.invalidAddress
}
주소가 빈 문자열이면 invalidAddress 에러를 발생
switch status {
case .notStarted:
throw DeliveryError.notStarted
case .error:
throw DeliveryError.systemError(reason: "시스템 서버 에러 예측이 불가능합니다.")
case .inTransit(let daysRemaining):
return "배송까지 \(daysRemaining)일 남았습니다."
}
notStarted → 예측 불가능
error → 시스템 오류 이유
inTransit → 남은 일수를 이용해 메시지 반환
let testAddress = "서울시 강남구"
let statuses: [DeliveryStatus] = [
.inTransit(daysRemaining: 3),
.notStarted,
.error
]
inTransit(daysRemaining: 3) → 정상 배송 중
notStarted → 아직 배송 시작 전
error → 시스템 오류
for status in statuses {
do {
let result = try predictDeliveryDay(for: testAddress, status: status)
print("결과 :", result)
} catch let error as DeliveryError {
switch error {
case .invalidAddress:
print("주소가 올바르지 않습니다.")
case .notStarted:
print("배송이 아직 시작되지 않았습니다.")
case .systemError(let reason):
print("\(reason)")
}
} catch {
print("알 수 없는 에러 \(error)")
}
}
predictDeliveryDay(for:status:) 함수를 호출하고
do-catch로 에러를 나눠서 처리