Hi yoahn 개발블로그

[Swift] #7 다양한 표현 및 확장 5 (assert / guard / 프로토콜 / 익스텐션) 본문

프로그래밍 언어/swift

[Swift] #7 다양한 표현 및 확장 5 (assert / guard / 프로토콜 / 익스텐션)

hi._.0seon 2021. 1. 21. 23:24
반응형

1. Assert / guard

애플리케이션이 동작 도중에 생성하는 다양한 연산 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 확인하고 빠르게 처리할 수 있습니다.

1.1 Assertion

  • assert(_:_:file:line:) 함수를 사용합니다.
  • assert 함수는 디버깅 모드에서만 동작합니다.
  • 배포하는 애플리케이션에서는 제외됩니다.
  • 예상했던 조건의 검증을 위하여 사용합니다.
var someInt: Int = 0

// 검증 조건과 실패시 나타날 문구를 작성해 줍니다
// 검증 조건에 부합하므로 지나갑니다
// 검증 조건에 부합하지 않는 경우 메시지를 출력하고 멈춤
assert(someInt == 0, "someInt != 0")

someInt = 1
//assert(someInt == 0) // 동작 중지, 검증 실패
//assert(someInt == 0, "someInt != 0") // 동작 중지, 검증 실패
// assertion failed: someInt != 0: file guard_assert.swift, line 26


func functionWithAssert(age: Int?) {
    
    assert(age != nil, "age == nil")
    
    assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못되었습니다")
    print("당신의 나이는 \(age!)세입니다")
}

functionWithAssert(age: 50)
//functionWithAssert(age: -1) // 동작 중지, 검증 실패
//functionWithAssert(age: nil) // 동작 중지, 검증 실패

* assert(_:_:file:line:)와 같은 역할을 하지만 실제 배포 환경에서도 동작하는 precondition(_:_:file:line:) 함수도 있습니다. 함께 살펴보세요.

1.2 guard (빠른 종료 - Early Exit)

  • guard를 사용하여 잘못된 값의 전달 시 특정 실행구문을 빠르게 종료합니다.
  • 디버깅 모드 뿐만 아니라 어떤 조건에서도 동작합니다.
  • guard else 블럭 내부에는 특정 코드블럭을 종료하는 지시어(return, break 등)가 꼭 있어야 합니다.
  • 타입 캐스팅, 옵셔널과도 자주 사용됩니다.
  • 그 외에도 단순 조건 판단 후 빠르게 종료할 때도 용이합니다.
  • guard 에서 unwrapped 된 변수는 아래에서 계속 사용할 수 있다.
func functionWithGuard(age: Int?) {
    
    guard let unwrappedAge = age,
        unwrappedAge < 130,
        unwrappedAge >= 0 else {
        print("나이값 입력이 잘못되었습니다")
        return
    }
    
    print("당신의 나이는 \(unwrappedAge)세입니다")
}

var count = 1

while true {
    guard count < 3 else {
        break
    }
    print(count)
    count += 1
}
// 1
// 2

func someFunction(info: [String: Any]) {
	// String 으로 캐스팅 해보고 nil 이면 종료
    guard let name = info["name"] as? String else {
        return
    }
    // Int 로 캐스팅 해보고 nil이면 종료
    guard let age = info["age"] as? Int, age >= 0 else {
        return
    }
    
    print("\(name): \(age)")
}

someFunction(info: ["name": "jenny", "age": "10"])
someFunction(info: ["name": "mike"])
someFunction(info: ["name": "yagom", "age": 10]) // yagom: 10

2. 프로토콜

  • 프로토콜(Protocol) 은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의합니다. 
  • 구조체, 클래스, 열거형은 프로토콜을 채택(Adopted) 해서 특정 기능을 수행하기 위한 프로토콜의 요구사항을 실제로 구현할 수 있습니다. 
  • 어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 프로토콜을 준수한다(Conform) 고 표현합니다. 
  • 타입에서 프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 청사진의 기능을 모두 구현해야 합니다. 즉, 프로토콜은 기능을 정의하고 제시 할 뿐이지 스스로 기능을 구현하지는 않습니다.

* Java 의 인터페이스와 비슷한 기능, 역할로 보이는데 강의 생각해보기 부분에는 그렇게 생각하지 말라고 되어있어서 좀 의아하다

protocol Talkable {
    
    // 프로퍼티 요구
    var topic: String { get set }
    var language: String { get }
    
    // 메서드 요구
    func talk()
    
    // 이니셜라이저 요구
    init(topic: String, language: String)
}

- 프로퍼티 요구는 항상 var 키워드 사용

  get : 읽기만 가능하면 됨 -> 연산 프로퍼티로 대체 가능

  set : 쓰기 가능

  get set : 읽기 쓰기 가능

2.1 프로토콜 채택 및 준수

<<프로토콜 채택>>

- 타입명: 프로토콜 이름

// Person 구조체는 Talkable 프로토콜을 채택했습니다
struct Person: Talkable {
    // 프로퍼티 요구 준수
    var topic: String
    let language: String // 상수 : 읽기 전용, get 은 var / let 상관 없음
    
    // 읽기전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
     var language: String { return "한국어" } // 읽기 전용
    
    // 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체할 수 있습니다
      var subject: String = ""
      var topic: String {
      
              self.subject = newValue
          }
          get {
              return self.subject
          }
      }
    
    // 메서드 요구 준수    
    func talk() {
        print("\(topic)에 대해 \(language)로 말합니다")
    }

    // 이니셜라이저 요구 준수    
    init(topic: String, language: String) {
        self.topic = topic
        self.language = language
    }
}

2.2 프로토콜 상속

  • 프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있습니다. 
  • 프로토콜 상속 문법은 클래스의 상속 문법과 유사하지만, 프로토콜은 클래스와 다르게 다중상속이 가능합니다.
protocol Readable {
    func read()
}
protocol Writeable {
    func write()
}
protocol ReadSpeakable: Readable {
    func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
    func speak()
}

-> 상속받은 프로토콜과 채택이 동시에 이루어지는 경우, 상속받은 프로토콜에 대해 모두 구현해야 한다.

  • 클래스 상속과 프로토콜 채택을 동시에 하려면, 클래스 먼저 명시
  • is, as 연산자 이용하여 어떤 프로토콜을 준수하는지 알 수 있다.
let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()

var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false

someAny = sub
someAny is Readable // true
someAny is ReadSpeakable // true

someAny = sup

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read

if let someReadSpeakable: ReadSpeakable = someAny as? ReadSpeakable {
    someReadSpeakable.speak()
} // 동작하지 않음

someAny = sub

if let someReadable: Readable = someAny as? Readable {
    someReadable.read()
} // read

3. 익스텐션

  • 익스텐션(Extension) 은 스위프트의 강력한 기능 중 하나입니다. 
  • 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가 할 수 있는 기능입니다.
  • 기능을 추가하려는 타입의 구현된 소스 코드를 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수도 있습니다.

<<스위프트의 익스텐션이 타입에 추가할 수 있는 기능>>

  • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
  • 타입 메서드 / 인스턴스 메서드
  • 이니셜라이저
  • 서브스크립트
  • 중첩 타입
  • 특정 프로토콜을 준수할 수 있도록 기능 추가

** 익스텐션은 타입에 새로운 기능을 추가할 수는 있지만, 기존에 존재하는 기능을 재정의할 수는 없습니다.

3.1 정의

  • extension 키워드 사용
  • 익스텐션은 기존에 존재하는 타입이 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다. 이런 경우에는 클래스나 구조체에서 사용하던 것과 똑같은 방법으로 프로토콜 이름을 나열해줍니다.

 

 

extension String {
	// 새로운 기능 구현
}

extension String: protocol1, protocol2,,..{
	// 프로토콜 요구사항 구현
}

스위프트 라이브러리를 살펴보면 실제로 익스텐션이 굉장히 많이 사용되고 있음을 알 수 있습니다. 

Double 타입에는 수많은 프로퍼티와 메서드, 이니셜라이저가 정의되어 있으며 수많은 프로토콜을 채택하고 있을 것이라고 예상되지만, 실제로 Double 타입의 정의를 살펴보면 그 모든것이 다 정의되어 있지는 않습니다. 그러면 Double 타입이 채택하고 준수해야 하는 수많은 프로토콜은 어디로 갔을까요? 어디에서 채택하고 어디에서 준수하도록 정의되어 있을까요? 당연히 답은 익스텐션입니다. 이처럼 스위프트 표준 라이브러리 타입의 기능은 대부분 익스텐션으로 구현되어 있습니다. Double 외에도 다른 타입들의 정의와 익스텐션을 찾아보면 더 많은 예를 보실 수 있습니다.

 

<<연산 프로퍼티 추가>>

  • 아래 익스텐션은 Int 타입에 두 개의 연산 프로퍼티를 추가한 것입니다. 
  • Int 타입의 인스턴스가 홀수인지 짝수인지 판별하여 Bool타입으로 알려주는 연산 프로퍼티입니다. 
  • 익스텐션으로 Int 타입에 추가해준 연산 프로퍼티는 Int 타입의 어떤 인스턴스에도 사용이 가능합니다.
  • 인스턴스 연산 프로퍼티를 추가할 수도 있으며, static 키워드를 사용하여 타입 연산 프로퍼티도 추가할 수 있습니다.
extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
    var isOdd: Bool {
        return self % 2 == 1
    }
}

print(1.isEven) // false
print(2.isEven) // true

<<메서드 추가>>

  • 메서드 익스텐션을 통해 Int 타입에 인스턴스 메서드 multiply(by:) 메서드를 추가했습니다.
  • 여러 기능을 여러 익스텐션 블록으로 나눠서 구현해도 전혀 문제가 없습니다.
  • 관련된 기능별로 하나의 익스텐션 블록에 묶어주는 것도 좋습니다.
xtension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
}
print(3.multiply(by: 2))  // 6
print(4.multiply(by: 5))  // 20

number = 3
print(number.multiply(by: 2))   // 6
print(number.multiply(by: 3))   // 9

<<이니셜라이저 추가>>

  • 인스턴스를 초기화(이니셜라이즈)할 때 인스턴스 초기화에 필요한 다양한 데이터를 전달받을 수 있도록 여러 종류의 이니셜라이저를 만들 수 있습니다. 타입의 정의부에 이니셜라이저를 추가하지 않더라도 익스텐션을 통해 이니셜라이저를 추가할 수 있습니다.
  • 익스텐션으로 클래스 타입에 편의 이니셜라이저는 추가할 수 있지만, 지정 이니셜라이저는 추가할 수 없습니다. 지정 이니셜라이저와 디이니셜라이저는 반드시 클래스 타입의 구현부에 위치해야 합니다(값 타입은 상관없습니다).
extension String {
    init(int: Int) {
        self = "\(int)"
    }
    
    init(double: Double) {
        self = "\(double)"
    }
}

let stringFromInt: String = String(int: 100) 
// "100"

let stringFromDouble: String = String(double: 100.0)    
// "100.0"

 

 

[boostcourse.org swift 프로그래밍 -야곰]

반응형
Comments