Swift 문법 CH.6_2

김성환·2020년 8월 25일
0

swift문법

목록 보기
8/11
post-thumbnail

안녕하세요. 본 포스트에서는 Swift의 구조체와 클래스에 대해 설명하겠습니다.

구조체와 클래스부분은 양이 많아 3부로 나눠 진행하겠습니다.
※ 참고자료 : 꼼꼼한 재은 씨의 Swift:문법편, 객체지향부분은 대학교 강의를 토대로 작성

목차

  • 3. 프로퍼티
  • 4. 메소드
  • 5. 상속
  • 6. 다형성

3. 프로퍼티

프로퍼티란?

구조체와 클래스에서 변수나 상수를 정의해 내부적인 값을 저장할 수 있는데 이렇게 정의된 변수나 상수를 프로퍼티(속성)이라고 한다.

struct A {
    var a = 0 // 구조체 A의 a 속성
    var b = 1 // 구조체 A의 b 속성
}

프로퍼티의 종류

프로퍼티는 총 3가지의 종류가 있다.

    1. 저장 프로퍼티
    1. 연산 프로퍼티
    1. 타입 프로퍼티

1. 저장 프로퍼티

클래스 내에서 선언된 변수나 상수를 부르는 이름
즉, 값을 저장해 제공하는 역활을 한다는 뜻이다.
저장 프로퍼티는 멤버라고도 부르는데, 변수형태로 저장될 경우 멤버변수, 상수형태로 저장될 경우 멤버상수이다.

지연 저장 프로퍼티

말그대로 저장을 지연시키는 프로퍼티이다.
즉, 저장 프로퍼티의 초기화를 지연시키는 프로퍼티라는 뜻이다.

  • 지연 저장 프로퍼티는 인스턴스가 생성 될때 초기화 되지 않고 지연 되다가 인스턴스가 해당하는 지연 저장 프로퍼티를 호출하는 시점에 초기화가 된다.
    lazy 라는 키워드를 사용한다.
class OnCreate{ // OnCreate를 출력하는 클래스
	init(){ // 인스턴스 생성시 출력
    	print("OnCreate")
    }
}
class LazyTest{ // LazyTest를 출력하는 클래스
    var base = 0
    lazy var late = OnCreate() // 지연 저장 프로퍼티
    init(){ // 인스턴스 생성시 출력
    	print("LazyTest")
    }
}
let lz = LazyTest() // LazyTest의 인스턴스 생성
// late는 지연 저장 프로퍼티이기 때문에 초기화 안됨
// 실행 결과
"LazyTest"

단, 첫번째 호출시에만 초기화 되고 이후로는 초기화 안됨(기존의 값을 갖고 있음)

클로저를 이용한 저장 프로퍼티 초기화

저장 프로퍼티 중의 일부는 연산 혹은 로직 처리를 통해 얻어진 값을 이용해 초기화해야하는 경우가 있는데 이때, 클로저를 이용할 수 있다.

var 프로퍼티이름 : 프로퍼티타입 = {
	로직,연산 내용
    return 반환값
}()
  • 이때, 프로퍼티타입과 반환값의 타입이 일치해야 하고, {}끝에 ()를 꼭 붙여줘야한다.
  • 또한 클로저를 이용한 저장 프로퍼티 초기화는 최초 한번만 실행된다는 것을 알아둬야 한다.
class PropertyInit {
	var value01: String! = {
    	print("value01실행")
        return "value01"
    }()
    lazy var value02: String! = { // 접근 전까진 출력 안함
    	print("value02실행")
        return "value02"
    }()
}
var a = PropertyInit()
a.value01 // value01에 접근을 해도 출력이 없다.
// 실행결과
"value01실행"

2. 연산 프로퍼티

값을 제공한다는 점에서 저장 프로퍼티와 같지만 실제 값을 저장했다가 반환하지 않고, 다른 프로퍼티의 값연산 처리해 간접적으로 값을 제공하는 것.
즉, 연산, 로직을 거쳐 값을 제공한다는 뜻이다.

연산프로퍼티 정의 형식

연산 프로퍼티는 get구문과 set구문으로 구성된다. 그 중 set구문은 생략이 가능하다.

class/struct/enum 이름 {
    var 프로퍼티이름 : 프로퍼티타입{
        get{
            필요한 연산 과정
            return 반환값
        }
        set(매개변수이름){
            필요한 연산 구문
        }
    }
}

get구문

get구문은 프로퍼티의 값을 참조하기 위해 내부적으로 사용하는 구문으로 필수적으로 작성해야하는 구문이다.
여기서 함수와 같이 return 키워드를 이용해 값을 반환하는데 이때 반환되는 값이 프로퍼티의 값이다.

set구문

연산 프로퍼티는 다른 프로퍼티의 값을 이용해 자신의 값을 만드는 프로퍼티이다. 하지만 연산 프로퍼티에 직접 값을 넣고 연산을 하는 경우가 있는데 이때 set구문을 이용하면 된다. 직접 값을 넣은 경우 set구문에서 넣은 값을 다른 프로퍼티에 대입한 뒤 get구문에서 연산을 할 수 있도록 만들어야 한다.
set구문을 작성시 매개변수명을 생략할 수 있는데 이 경우 자동적으로 매개변수는 'newValue'로 할당이 된다. 따라서 매개변수명을 생략할 경우 newValue를 이용해 연산해야 한다.

set(y){
    a = y // 프로퍼티 a에 대입하는 값을 넣음
}
------------------------
set{
    a = newValue // 프로퍼티 a에 대입하는 값을 넣음
}

set구문을 생략할 수도 있는데 생략시 읽기 전용 프로퍼티가 된다. 즉, 값을 읽기만 가능하고 쓰기(값 변경, 수정, 할당 등등)는 불가능한 상태가 된다는 뜻이다.
또한 set구문 생략시 get구문만 남게 되는데 이 경우 get블록을 아에 없애고 내용만 쓰는 것도 가능한다.

struct r{
    var a = 1
    var b = 2
    var center : Int{ // get블록 있다.
        get{
            return a + b // 반환타입과 newValue타입과 일치해야함
        }
        set{
           a = newValue 
        }
    }
}
var t = r()
print(t.center)
t.center = 5 // 대입하는 값도 반환타입과 일치해야 한다.
print(t.center)
// 실행결과
3
7
struct r{
    var a = 1
    var b = 2
    var center : Int{ // get블록 있다.
        get{
            return a + b
        }
    }
}
--------------------------
struct r{
    var a = 1
    var b = 2
    var center : Int{ // get블록 없다.
        return a + b
    }
}

3. 타입 프로퍼티

위에서 배운 2가지 프로퍼티들은 인스턴스를 생성해야만 사용가능한 인스턴스 프로퍼티였다.
이제는 인스턴스 생성 없이도 사용가능한 프로퍼티에 대해 배워보자
타입 프로퍼티는 인스턴스를 생성하지 않고 클래스나 구조체 자체에 값을 저장해 사용하는 프로퍼티를 말한다.
즉, 프로퍼티의 값이 클래스나 구조체의 것이라는 뜻이다.
이때 주의해야 하는 것은 인스턴스로는 타입 프로퍼티 접근이 불가능하다는 것이다. 따라서 타입 프로퍼티 값을 접근하기 위해서는 클래스나 구조체로만 접근이 가능하다.

타입 프로퍼티 정의 형식

타입 프로퍼티는 staticclass 두 키워드를 이용해 정의한다.
이때 타입 프로퍼티는 초기값을 반드시 할당해야 한다.

  • static을 이용하는 경우
    클래스,구조체,연산,저장 프로퍼티 모두 사용이 가능하다.
static var 프로퍼티이름 = 초기값
  • class를 이용하는 경우
    클래스에서 연산 프로퍼티에만 사용이 가능하다. 그렇기 때문에 뒤에서 배울 내용인 오버라이드가 가능하다.
class var 프로퍼티이름: 프로퍼티 타입{
    get{
        return 반환값    
    }
    set{ // set구문은 생략해서 쓴다.
    }
}

아래는 예시이다.

class r{
    var a = 1
    static var b = 2
    class var center : Int{ 
        return 10 // get구문 사용하니 set생략한 모습
    }
}
r.b = 100
print(r.b)
print(r.center)
// 실행결과
100
10

4. 메소드

메소드란?

클래스나 구조체, 열거형과 같은 객체 내에서 함수가 선언될 경우 이를 메소드라고 한다.
메소드의 선언은 앞에서 배운 함수와 똑같다.

메소드의 종류

메소드는 2가지 종류가 있다.
위에서 배운 프로퍼티와 같다.

    1. 인스턴스 메소드 -> 인스턴스를 이용해 호출하는 메소드
    1. 타입 메소드 -> 클래스,구조체의 것

self 키워드

self 키워드는 타 언어에서의 this와 같다.
self의 의미는 클래스나 구조체 자신을 가리킨다 는 의미이다.
즉, 클래스의 것(소속) 혹은 구조체의 것(소속) 이라는 뜻이다.
아래는 인스턴스 메소드의 예시이다.

class r{
    var a = 1
    var b = 2
    var num : Int{
    	return a + b
    }
    func calc(_ num: Int) -> Int {
        return self.num + num // 앞의 num은 클래스의 num 뒤의 num은 calc의 num
    }
}
var t = r()
print(t.calc(10))
// 실행결과
13

타입 메소드의 경우도 static과 class를 사용해 정의한다.
프로퍼티와 동일하게 static은 구조체,열거형,클래스 모두 사용가능하고 class는 클래스만 사용가능하다.
아래는 타입 메소드의 예시이다.

class r{
    var a = 1
    var b = 2
    var num : Int{
    	return a + b
    }
    class func calc(_ num: Int) -> Int {
        return num
    }
}
var t = r.calc // 변수에 타입 메소드를 넣고 사용할 수도 있다
// print(t.calc) // 오류!!
print(r.calc(100))
print(t(10))
// 실행결과
100
10

5. 상속

상속이란?

앞에서 배운 객체지향의 특성 4가지 중 하나이다.
상속, 뜻은 물려준다는 의미이다. 그렇다면 무엇을 물려줄까?
답은 클래스의 속성(프로퍼티),기능(메소드)을 물려준다. 이다.
구조체와는 다르게 클래스는 상속이 가능하다. 이것이 구조체와 클래스의 차이점 중 하나이다.
그렇다면 프로그램에서 물려준다는 의미는 무엇인가?
답은 확장이다.
클래스 a와 b가 있다고 생각해보자.
이때, 클래스 b는 a에게 상속을 받은 경우, 클래스 b는 클래스 a를 확장했다고 볼 수 있다.
즉, b는 a의 기능과 속성을 기반으로 추가적인 기능,속성을 확장했다는 뜻이다.

상속의 용어

상속을 이야기 하려면 기본적인 용어를 살펴볼 필요가 있다.

  • 상속을 하는 클래스(속성과 기능을 물려주는 클래스) = 부모, 슈퍼, 기본, 상위클래스
  • 상속을 받는 클래스(속성과 기능을 물려받는 클래스) = 자식, 서브, 파생, 하위클래스
  • 서브클래싱 = 클래스를 상속받아 새로운 클래스를 정의하는 과정

상속 방법

상속은 : 키워드를 을 이용해 상속을 할 수 있다.
이때, Swift에서는 다중 상속이 지원되지 않는다는 것을 알아야 한다.

class 클래스이름 : 부모클래스이름 {
    내용입력
}

아래는 상속에 대한 예시이다.

class A {
    var a1 = 1
    var a2 = 2
    func printA(num: Int) -> Int {
        return num
    }
}
class B : A { // A의 상속을 받은 B
    var a3 = 3
    func printB() -> Int {
        return a3
    }
}
var t = A()
var y = B()
print(t.printA(num: t.a1))
print(y.printA(num: t.a2))
print(y.printB())
// 실행결과
1
2
3

아래는 위의 예시에 나온 클래스들의 관계를 그림으로 그린 것이다.

그림에서 보듯 상속받은 클래스는 부모의 클래스의 속성과 기능을 모두 물려받는 것을 확인 할 수 있다.

오버라이딩이란?

자식 클래스는 일반적으로 부모 클래스로부터 상속받은 프로퍼티나 메소드를 그대로 사용하지만, 필요에 의해 이를 다시 구현하거나 재정의하여 사용하기도 한다.
이때 사용되는 것이 오버라이딩이다.
오버라이딩은 덮어쓰기 라고 생각하면 된다.
즉, 기존의 프로퍼티나 메소드에 새로운 내용,재정의할 내용을 덮어쓴다는 뜻이다.

오버라이딩 하는 방법

Swift에서는 오버라이딩을 하기 위해서는 오버라이딩 할 대상이라는 것을 컴파일러에게 알려주는 override 키워드를 사용한다. 만약 override 키워드를 사용하지 않을 경우 오류가 발생한다.
그 반대의 경우도 마찬가지로 오류가 발생한다.
즉, 오버라이딩인 경우만 override키워드를 사용해야 한다는 뜻이다.
override 키워드는 이름 앞에 붙여 사용한다.

오버라이딩 종류

  • 프로퍼티 오버라이딩
  • 메소드 오버라이딩

프로퍼티 오버라이딩

프로퍼티를 오버라이딩 할 경우 상위 클래스에서 저장 프로퍼티였건, 연산 프로퍼티였건 관계없이 연산 프로퍼티의 형태로 오버라이딩해야 한다.
즉, 프로퍼티 오버라이딩은 연산 프로퍼티 오버라이딩이라는 뜻이다.
이때, 제한사항이 있는데
1. 저장 프로퍼티 -> get,set구문 모두 있는 연산 프로퍼티
2. get,set구문 모두 있는 연산 프로퍼티 -> get,set구문 모두 있는 연산 프로퍼티
3. get구문만 있는 연산 프로퍼티 -> get,set구문 모두 있는 연산 프로퍼티
4. get구문만 있는 연산 프로퍼티 -> get구문만 있는 연산 프로퍼티
프로퍼티 오버라이딩은 이 4가지 오버라이딩만 허용한다.

class A {
    var a1 = 1
    var a2 = 2
    func printA(num: Int) -> Int {
        return num
    }
}
class B : A{
	override var a1 : Int{ // 저장 프로퍼티 a1을 오버라이딩한 모습
        get{
            return a2 + 2
        }
        set{
            // 아무 내용이 없어도 set블록을 작성해야함
        }
    }
}
var t = B()
print(t.a1)
// 실행결과
4

메소드 오버라이딩

메소드 오버라이딩은 메소드의 내용을 덮어쓰는 것이다.
즉, 메소드의 이름(식별자)는 그대로인 상태로 내용만 재정의(덮어씀)다는 뜻이다.
이때의 메소드 이름은 순수하게 메소드의 이름이 아니라 인자레이블, 반환타입까지 포함한 것을 뜻한다.

class A {
    var a1 = 1
    var a2 = 2
    func printA(num: Int) -> Int {
        return num
    }
}
class B : A{
	override func printA(num: Int) -> Int{
    //내용만 다르고 다른것들은 모두 부모 것과 같아야함
    	let a = 100
        return num + a
    }
}
var y = A()
var t = B()
print(y.printA(num: 50))
print(t.printA(num: 50))
// 실행결과
50
150

오버로딩이란?

오버라이딩은 클래스간의 상속관계를 바탕으로 상속받은 프로퍼티나 메소드를 덮어쓰는 것을 의미한다.
그렇다면 오버로딩은 무엇인가?
오버로딩은 같은 메소드 이름을 갖는 메소드를 매개변수를 다르게 하여 이름만 같은 새로운 메소드를 만드는 것을 말한다.
즉, 메소드의 중복 정의를 말한다.
예를들어 메소드 A가 있다고 가정하자.
메소드 A의 이름이(식별자) A(name: Int) ->Int 이것이라고 하자.
이때, 메소드 A를 오버로딩할 경우
A(num:Int)->Int,
A(num1:Int)->Int,
A(num:Int,num1:Int)->Int,
A(num:Stinr,num1:String,num2:Int)->String,.... 등등 수많은 경우가 있을 수 있다.

class A {
    var a1 = 1
    var a2 = 2
    func printA(num: Int) -> Int {
        return num
    }
    func printA(num: String) -> String { // printA의 오버로딩
        return num
    }
    func printA(num1: Int) -> Int { // printA의 오버로딩
        return num1 + 50
    }
}
var y = A()
print(y.printA(num: "hello"))
print(y.printA(num: 50))
print(y.printA(num1: 50))
// 실행결과
hello
50
100

super 키워드

종종 오버라이드가 된 프로퍼티,메소드 말고 오버라이드 이전의 값을 이용하고 싶은 경우가 있다.
이때 super 객체를 이용할 수 있다.
super 키워드는 부모 인스턴스를 참조할 수 있는 객체이다. self는 자기 자신을 가리킨 것이라면 super는 부모를 가리킨다고 생각하면 된다.

class A {
    var a1 = 1
    var a2 = 2
}
class B : A {
    override var a1 : Int {
        get{
            return a2 + super.a1
        }
        set{
        }
    }
}
var y = A()
var t = B()
print(y.a1)
print(t.a1)
// 실행결과
1
3

final 키워드

오버라이딩을 막는 방법도 있다. 바로 final키워드를 이용하면 된다.
final 키워드가 붙은 메소드, 프로퍼티는 오버라이딩이 불가능하다.
또한 final 키워드를 클래스 자체에도 붙일 수 있는데, 이 경우는 상속이 불가능하다는 뜻을 담고 있다.

final class A {
    final var a = 0
}

6. 다형성

오버라이딩의 개념을 이해하기 위해서 상속개념이 필요했다. 왜냐하면 상속관계 안에서 오버라이딩이 일어나기 때문이다.
그리고 객체지향의 마지막 특성인 다형성의 개념을 이해하기 위해서는 오버라이딩의 개념이 필요하다. 왜냐하면 오버라이딩이 곧 다형성을 설명하기 때문이다.
다형성, 사전의 뜻은 '여러개의 형태를 갖는다.' 이다.
이때, 무엇이 여러개의 형태를 갖는 것일까?
답은 프로퍼티와 메소드이다.
서로 상속관계인 부모 클래스와 자식클래스는 이름이 동일한 속성과 기능을 서로 갖고 있다.
그 중 속성,기능의 이름 그 자체뿐만 아니라 그 안에 들어있는 것들 모두 같은 것들이 존재할 것이다. 하지만 속성,기능의 이름 그 자체만 같은 것들도 존재할 것이다.
즉, 동일한 프로퍼티와 메소드에 대해 여러개의 형태를 갖는다는 뜻이다.
예를들어, A 강아지는 짖을때 멍멍!이라고 소리를 낸다고 생각해보자.
이때 A강아지가 새끼를 낳았는데 이 새끼 강아지는 짖을때 컹컹!이라고 소리를 낸다.
이것은 '짖다'라는 기능(행동)을 새끼 강아지가 오버라이딩하여 기존의 형태와 다른 새로운 형태를 갖는 것을 뜻한다.

profile
개발자가 되고 싶다

0개의 댓글