[내배캠][Swift] 문법 심화 정리 (1)

팔랑이·2024년 6월 11일
0

iOS/Swift

목록 보기
26/71
post-thumbnail

문법 2주차 개강
금요일까지 문법 심화 강의 내용 정리하고, 과제 할 예정

Swift 문법 심화 목차

1. 프로퍼티 옵저버
2. 타입 캐스팅
3. 접근제한자
4. 클로저
5. 고차함수
6. 예외처리
7. ARC와 메모리 누수
8. 프로토콜
9. 확장
10. 제네릭
11. 비동기와 네트워킹
12. Combine 맛보기
13. RxSwift 맛보기

오늘은 3번까지

문법 심화 정리 (2) 는 여기


1. 프로퍼티 옵저버

프로퍼티 옵저버?

프로퍼티 옵저버는 Swift에서 프로퍼티의 값이 변할 때 실행되는 코드 블록으로, 주로 값이 바뀔 때 특정 작업을 하거나, 값을 검사하고 조정할 때 쓴다. Swift의 property observer에는willSetdidSet 두 가지가 있다.

didSet, willSet

willSet

: 값이 변경되기 전에 호출, newValue라는 이름으로 새 프로퍼티 값이 제공됨

didSet

: 값이 변경된 후에 호출, oldValue라는 이름으로 이전 프로퍼티 값이 제공됨

예제 1: String값 검사

class Person {
    var name: String {
        willSet {
            print("이전 이름: \(name), 새로운 이름: \(newValue)")
        }
        didSet {
            print("이름이 \(oldValue)에서 \(name)으로 변경되었음")
        }
    }
}

let person = Person()
person.name = "Tom"  // 이전 이름: , 새로운 이름: Tom
person.name = "Jerry"  // 이전 이름: Tom, 새로운 이름: Jerry
// 이름이 Tom에서 Jerry으로 변경되었음

예제 2: Int값 검사

class LimitedValue {
    private(set) var value: Int = 0 {
        willSet {
            if newValue < 1 || newValue > 100 {
                print("입력 값이 범위를 벗어났습니다. 1에서 100 사이의 값으로 조정됩니다.")
            }
        }
        didSet {
            if value < 1 {
                value = 1
                print("값이 1로 설정되었습니다.")
            } else if value > 100 {
                value = 100
                print("값이 100으로 설정되었습니다.")
            }
        }
    }
    
    func updateValue(_ newValue: Int) {
        self.value = newValue
    }
}

// 사용 예시
let limitedValue = LimitedValue()

limitedValue.updateValue(50)
print(limitedValue.value) // 50

limitedValue.updateValue(150)
print(limitedValue.value) // 입력 값이 범위를 벗어났습니다. 1에서 100 사이의 값으로 조정됩니다. 값이 100으로 설정되었습니다. 100

limitedValue.updateValue(-10)
print(limitedValue.value) // 입력 값이 범위를 벗어났습니다. 1에서 100 사이의 값으로 조정됩니다. 값이 1로 설정되었습니다. 1

사용 의의

  • 데이터 무결성 유지: 값이 변경되기 전/후에 검사 실행하여 데이터 무결성 유지 가능
  • 작업 자동 수행: 값이 변경될 때마다 특정 작업을 자동으로 수행할 수 있음
  • 디버깅: 값이 언제 어떻게 변하는지 추적하기 쉬워져 디버깅에 도움

if문으로 검사하는 것과의 차이?

그냥 if문으로 검사할 수도 있지 않나 했는데 프로퍼티 옵저버로 검사하는게 다음과 같은 면에서 훨씬 효율적이라고 한다. 찾아보고 바로 납득...

  1. 자동 실행: 프로퍼티 옵저버는 값이 변경될 때마다 자동으로 실행돼서 사용하는 곳마다 추가로 검사 코드를 작성하지 않아도 반면, if문은 값을 변경하는 모든 곳에서 일일이 검사를 해줘야 한다.
  2. 코드 간소화: 프로퍼티 옵저버를 사용하면 코드가 간결해지고 가독성이 좋아진다. if문으로 검사하는 방식은 코드가 중복되고 복잡해질 수 있음.
  3. 안전성: 프로퍼티 옵저버는 클래스나 구조체의 모든 값을 변경할 때마다 실행돼서, 특정 상황에서만 if문을 사용하는 것보다 더 안전하다.

2. 타입 캐스팅 (is, as, as?, as!) 과 업캐스팅, 다운캐스팅

: 객체의 타입을 확인하거나(is) 변환(as, as?, as!), as 묶음은 업/다운 캐스팅과 함께 요약한다.

2-1. is: 타입 확인

is 키워드는 객체가 특정 타입인지 확인할 때 사용되며, 검사하는 타입에 따른 Bool 값을 반환한다.

let number: Any = 123

if number is Int {
    print("number는 Int 타입이다.")
} else {
    print("number는 Int 타입이 아니다.")
}

업캐스팅, 다운캐스팅

업캐스팅과 다운캐스팅은 객체 지향 프로그래밍에서 상속 관계에 있는 클래스들 간의 타입 변환을 의미한다.

2-2. 업캐스팅 (Upcasting) : as

: 자식 클래스의 객체를 부모 클래스 타입으로 변환하는 것. 컴파일 단계에서 검사되기 때문에 실패할 가능성이 없을 때만 사용 가능하며, 캐스팅 실패할 경우 에러가 발생한다. 업캐스팅의 경우 Swift에서는 자동으로 이루어지며, as 키워드를 사용하지 않아도 오류가 나지는 않는다. 그럼에도 사용하는 이유는...

사용 의의:
1. 다형성 활용: 부모 클래스 타입으로 객체를 처리함으로써 여러 자식 클래스들을 하나의 타입으로 다룰 수 있다. 예를 들어, 배열에 여러 자식 클래스의 객체를 담을 때 부모 클래스 타입으로 저장할 수 있다.
2. 코드 유연성: 부모 클래스 타입을 사용해 인터페이스를 설계하면, 새로운 자식 클래스를 추가하더라도 기존 코드를 수정하지 않고도 확장할 수 있다.

예제:

class Animal {
    func makeSound() {
        print("Animal sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("멍멍!")
    }
}

let myDog: Dog = Dog()
let myAnimal: Animal = myDog // 업캐스팅
myAnimal.makeSound() // 멍멍!

2-3, 2-4. 다운캐스팅 (Downcasting) : as?, as!

: 부모 클래스 타입의 객체를 자식 클래스 타입으로 변환하는 것. 타입 검사를 통해 안전하게 변환하는 as?와 강제적으로 변환하는 as! 두 가지 키워드를 사용할 수 있다. 런타임 단계에서 검사되기 때문에 캐스팅에 실패하면 런타임 에러가 발생한다.

예제:

let someAnimal: Animal = Dog()

// 안전한 다운캐스팅
if let dog = someAnimal as? Dog {
    dog.makeSound() // 멍멍!
} else {
    print("someAnimal은 Dog 타입이 아님")
}

// 강제 다운캐스팅
let forcedDog = someAnimal as! Dog
forcedDog.makeSound() // 멍멍!

업캐스팅과 달리 다운캐스팅은 꼭 언래핑을 통해, 또는 강제로 실행해줘야 한다. 이런 다운캐스팅도 굳이 왜 하나면...

사용 의의:
1. 특정 기능 사용: 자식 클래스에만 있는 특정 기능이나 메서드를 사용하기 위해 부모 클래스 타입의 객체를 자식 클래스 타입으로 변환할 때 사용한다.
2. 유연한 코드: 다양한 타입의 객체를 처리하는 코드를 작성하면서, 필요한 경우 특정 타입으로 변환해 구체적인 동작을 구현할 수 있다.

결론: 업캐스팅은 객체 지향 프로그래밍의 기본적인 다형성을 지원하고, 다운캐스팅은 필요한 경우 구체적인 타입의 기능을 사용할 수 있게 한다.


3. 접근제한자

: 코드의 접근 범위를 설정하는 데 사용되며, 접근 제한자를 통해 클래스, 구조체, 열거형, 프로토콜, 그리고 해당 멤버들에 대한 접근을 제어할 수 있다. 이를 통해 코드의 캡슐화와 모듈화를 돕고, 안전성과 유지 보수성을 향상시킬 수 있다.

접근 제한자의 종류 (1: 넓은 범위 ~ 5: 좁은 범위)

1. open

: 클래스와 클래스 멤버에만 사용 가능. 모듈 외부에서도 접근 가능하며, 서브클래싱과 오버라이딩이 가능하다.

예제:

open class Animal {
	open func makeSound() {
    	print("Animal sound")
  	}
}

2. public

: 모듈 외부에서도 접근 가능하지만, 서브클래싱과 오버라이딩은 모듈 내부에서만 가능

예제:

public class Animal {
    public func makeSound() {
        print("Animal sound")
    }
}

3.internal

: 같은 모듈 내에서만 접근 가능. 기본 접근 수준(default)으로, 접근 범위를 아무것도 명시하지 않으면 자동으로 internal로 설정된다.

예제:

class Animal { // internal 생략
    func makeSound() {
        print("Animal sound")
    }
}

4. fileprivate

: 같은 파일 내에서만 접근 가능

예제:

fileprivate class Animal {
    func makeSound() {
        print("Animal sound")
    }
}

5. private

: 같은 정의 내에서만 접근 가능. 클래스나 구조체의 멤버가 선언된 범위 내에서만 접근할 수 있다.

예제:

class Animal {
    private func makeSound() {
        print("Animal sound")
    }
}

사용 의의

  1. 캡슐화: 내부 구현을 감추고, 외부에서 접근할 수 있는 인터페이스만 제공함으로써 객체 지향 프로그래밍의 원칙을 따를 수 있다.
  2. 모듈화: 모듈 내부와 외부의 경계를 명확히 함으로써 코드의 재사용성과 유지 보수성을 높일 수 있다.
  3. 안전성: 외부에서 접근할 수 없는 부분을 보호함으로써 코드의 안전성을 향상시킬 수 있다.
  4. 가독성: 코드의 가시성을 제한함으로써 복잡성을 줄이고, 가독성을 높일 수 있다.

profile
정체되지 않는 성장

0개의 댓글