[Swift] 놓치고 있던 기초 문법 3

‍deprecated·2021년 9월 17일
0

Swift 시작하기

목록 보기
11/12

상속

오버라이딩을 통해 물려받을 메서드, 프로퍼티, 서브스크립트 등을 자신만의 내용으로 재정의할 수 있다.
상속 받은 프로퍼티의 값이 변경 되었을 때 알려주는 프로퍼티 감시자도 구현 가능 !

  • 부모 클래스에서 연산, 저장 프로퍼티로 정의한 프로퍼티 모두 자식 클래스에서 프로퍼티 감시자 구현이 가능하다.

연산, 저장 프로퍼티를 오버라이드한 프로퍼티는 getter, setter를 가질 수 있고, 자식 클래스에서 재정의하려는 프로퍼티는 슈퍼클래스 프로퍼티의 이름과 타입이 일치해야 한다.
슈퍼클래스에서 read, write로 선언된 프로퍼티를 서브클래스에서 read-only로 오버라이드는 불가하다. 하지만 슈퍼클래스에서 read-onlyfh tjsdjsgks vmfhvjxlsms read-write로 오버라이드 가능하다.

class Vehicle{
    var currentSpeed = 0.0
    var description : String{
        return "traveling at \(currentSpeed)"
    }
    func makeNoise(){
        print("this is parent")
    }
}
class Train : Vehicle {
    override func makeNoise() {
        super.makeNoise() // 먼저 this is parent부터 출력
        print("this is train")
    }
}
let train = Train()
train.makeNoise() // this is train 출력

super

super class의 메서드 호출 가능

class Car : Vehicle {
    var gear = 1
    override var description: String {
        return super.description + "in gear \(gear)"
    }
}
let car = Car()
car.currentSpeed = 30
car.gear = 2
print(car.description) // traveling in 30 in gear 2

프로퍼티 옵저버

상속된 프로퍼티에 오버라이드를 추가해서 프로퍼티 옵저버를 사용할 수 있다.

class AutomaticCar: Car{
    override var currentSpeed: Double{
        didSet { // 변하면
            gear = Int(currentSpeed/10)+1
        }
    }
}
let automatic = AutomaticCar()
automatic.currentSpeed = 30.5
print("automatic : \(automatic.description)")

final

final 작성시 오버라이드 불가하다.

class Vehicle{
    final var currentSpeed = 0.0 // -> 재정의 선언한 라인에 컴파일 에러
    var description : String{
        return "traveling at \(currentSpeed)"
    }
    func makeNoise(){
        print("this is parent")
    }
}
class AutomaticCar: Car{
    override var currentSpeed: Double{ // -> 에러
        didSet { // 변하면
            gear = Int(currentSpeed/10)+1
        }
    }
}

class 정의할 때 앞에 final 붙여도 해당 클래스를 슈퍼클래스로하는 서브클래스 못 만든다.

타입 캐스팅

이전 게시글에서 잠깐 다뤘다. 인스턴스의 타입을 확인하거나 어떠한 클래스의 인스턴스를 해당 클래스 계층 구조의 슈퍼클래스나 서브클래스로 취급하는 방법이다. is, as로 구현한다.
가령,

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

이런 슈퍼클래스가 있다고 할 때,

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}
let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]

MediaItem을 상속받는 Movie, Song 클래스가 있다고 하자. 그리고 library엔 MediaItem의 서브클래스 객체를 원소로 하는 배열을 선언하자.

Checking Type

var movieCount = 0
var songCount = 0

for item in library { // item은 MediaItem 인스턴스이다! 
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs"

위와 같이 is문으로 특정한 서브클래스 타입인지 아닌지 판단할 수 있다.

참고) library 배열에 Movie, Song 둘 다 있기에 libraryItem의 원소가 MediaItem인 것이다. 둘 중 하나만 있다면 해당 클래스이다.

위와 같이 Movie만 선언한다면 lib2는 MediaItem이 아닌 Movie 배열이다.

Downcasting

for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.name), by \(song.artist)")
    }
}

다운캐스팅은 실패할 수 있기 때문에 조건 형식인 as?는 옵셔널 값을 반환한다. 다운캐스팅이 항상 성공한다고 보장할 때만 as!를 사용한다.

Assert

특정 조건을 체크하고, 조건이 성립되지 않으면 메세지를 출력하게 할 수 있는 함수
assert함수는 디버깅 모드에서만 동작하고, 디버깅 중 조건의 검증을 위해 사용한다.

var val = 0
assert(val == 0) // value가 0인지 검증. 조건 만족시 통과.

val = 2
assert(val == 0, "값이 0이 아닙니다.") // 런타임 에러 발생,
// 에러문 : Assertion failed: 값이 0이 아닙니다. 조건 성립 안될시 메세지와 함께 에러를 발생

Guard

쌩 guard를 사용하는 경우는 모르고 있었다...

조건을 검사하여 다음에 오는 코드를 실행할 지 말지를 결정
잘못된 값이 함수에 들어오는 걸 방어하기 위해 사용한다.
guard문에 주어진 조건문이 거짓일 때 구문이 실행된다.

guard 조건 else {
    // 조건이 false면 else 구문이 실행되고
    return or throw or break를 통해 이 후 코드를 실행하지 않도록 한다.
 }
func guardTest(value:Int){
    guard value == 0 else { return }
    print("안녕하세요") // 0이 아니면 Guard문에 의해 else문이 실행되기에 함수 종료
}
//guardTest(value: 1)
guardTest(value: 0) // 안녕하세요 출력 

이를 이용한 옵셔널 바인딩은 알다시피,

func guardTestOpt(value:Int?){
    guard let value = value else { return }
    print(value) // 0이 아니면 Guard문에 의해 else문이 실행되기에 함수 종료
}
//guardTestOpt(value: 2)
guardTestOpt(value: nil)

프로토콜

protocol 이름 {
 
}

요딴식으로 선언한다.

특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진을 정의한다.
프로토콜을 채택할 클래스, 구조체, 열거형에게 정의한 요구사항을 준수할 것을 요청한다.
프로토콜에선 프로퍼티의 이름과 타입만 지정하면 되며, 읽기만 가능한지, 읽기-쓰기가 모두 가능한지 get, set을 이용해 지정해줘야 한다. 읽기만 가능한 경우 모든 종류의 프로퍼티를 설정 가능하다.

protocol FirstProtocol {
    var name:Int { get set } // 읽기 쓰기 가능
    var age: Int { get } // 읽기전용
}
protocol SomeProtocol { // 정의    
}
protocol SomeProtocol2 { // 정의
}
struct SomeStucture: SomeProtocol, SomeProtocol2 {
}

위와 같이 여러개의 프로토콜 동시 채택 가능하며, class의 경우 상속을 받는다면 상속 받ㅇ을 슈퍼클래스를 가장 앞에 쓰며 클론 다음 프로토콜들을 정의한다.

class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
}

프로토콜에서 타입 프로퍼티를 요구하려면 스태틱 키워드 써줘야한다.

protocol AnotherProtocol {
    static var someTypeProperty: Int {get set }
}

사용 예)

protocol FullyNames{
    var fullName:String { get set }
    func printFullName()
}
struct Person : FullyNames{
    var fullName: String // 정의해야 에러가 사라짐 - 프로토콜 준수
    func printFullName() { // 정의해야 에러가 사라짐
        print(fullName)
    }
} 

함수를 프로토콜에서 지정한다면,

protocol SomeProtocol3 {
    func someTypeMethod() // 프로토콜에서 메서드 정의 시 중괄호, 메서드 본문은 필요없지만, 메서드에 필요한 매개변수는 정의해줘야. 디폴트값은 지정할 수 없음.
}

init

protocol은 자신을 선택한 타입에 생성자도 요구할 수 있다.

protocol SomeProtocol4 {
    init(someParam : Int) // 생성자 몸통은 작성 안해도 됨. 키워드와 매개변수만 정의하면 됨.
}

protocol SomeProtocol5 {
    init()
}
class someClass:SomeProtocol5{
    // 생성자 요구사항 준수해야! -> class에서 프로토콜이 요구하는 생성자를 채택하려면 required 식별자를 사용해야
    required init(){ 
    }
}

구조체에선 required 필요 없다. 클래스에서만 required 붙여줌.
단, 만약 클래스 자체가 상속을 허락하지 않는 final 클래스라면 required 식별자 붙여줄 필요 없다.

Extension

기존의 클래스, 구조체, 열거형, 프로토콜에 새로운 기능을 추가하는 기능.

익스텐션이 타입에 추가할 수 있는 기능

  1. 연산 타입 프로퍼티, 연산 인스턴스 프로퍼티
  2. 타입 메서드 / 인스턴스 메서드
  3. 이니셜라이저
  4. 서브스크립트
  5. 중첩 타입
  6. 특정 프로토콜을 준수할 수 있도록 기능 추가
  • 저장 프로퍼티는 추가할 수 없다.
  • 타입에 저장되어 있는 프로퍼티 감시자는 추가할 수 없다.
  • 기존에 존재하는 기능을 오버라이드 할 순 없다.
extension Int {
    var isEven: Bool {
        return self%2 == 0
    }
    var isOdd:Bool {
        return self%2 == 1
    }
}
var numberX = 3
print(numberX.isOdd)
print(numberX.isEven)

메서드 추가도 가능

extension String {
    //문자열을 intguddmfh
    func convertToInt() -> Int? {
        return Int(self)
    }
}
var stringX = "0"
stringX.convertToInt()
profile
deprecated

0개의 댓글