본문 바로가기

학습 노트/Swift (2021)

104 ~ 108. Structure and Class

Structures and Classes (구조체와 클래스)

애플은 많은 형식을 제공하지만 모든 형식을 제공하진 않는다.
따라서 개발자 스스로가 형식을 새로 만들어야 할 필요가 있을 수 있는데, 이때 필요한 것이 구조체와 클래스이다.
열거형까지 포함해 이들을 User defined type 이라고 부르거나 Custom data type이라고 부른다.

User Defined Type / Custom Data Type
Enumeration Structure Class

Programming Paradigm

프로그래밍 언어들은 저마다의 규칙을 가지고 있고, 이를 프로그래밍 패러다임이라고 한다.
현대의 언어들은 여러 패러다임 중 두가지 이상을 구현할 수 있는 mUlti-Paradigm 언어이고, 스위프트도 마찬가지이다.

Swift Programming Paradgm
Object-Oriented Pragramming Protocol-Oriented Programming Functional Programming
객체지향    

Object-Oriented Pragramming (객체지향)

객체지향 프로그래밍에서 중심을 이루는 것은 객체이며,
우리 주변의 모든 것들을 객체로 표현한다.
모든 데이터를 객체로 만들고, 객체의 상태와 동작을 조작해 원하는 기능을 만든다.

  1. Class
    객체지향 프로그래밍에서 필요한 여러가지 요소를 도출하는 것을 추상화라고 하며,
    이 추상화의 결과물이 클래스라고 할 수 있다.

    객체의 특징과 상태를 속성으로 구현
    객체의 동작을 method로 구현

    이러한 클래스는 객체의 설계도로서의 역할을 한다.

  1. Instance
    클래스를 통해 생성된 하나의 객체를 인스턴스라고 한다.
    다, 같은 클래스로 구현됐어도 속성과 결과는 각 인스턴스마다 차이가 있을 수 있다.

  2. Interaction
    객체지향 프로그래밍에서는 각각의 객체들이 서로 상호작용 하도록 구현한다.
    이때, 상호작용이란 서로의 속성을 변경하거나 method를 호출할 수 있음을 의미하고, 이러한 동작을 Sending Meassage라고 부르기도 한다.
    애플에서 제공하는 Cocoa에서는 message를 보내는 객체를 sender, 받는 객체를 receiver라고 부른다.

  3. Structure
    객체지향에서 새로운 형식을 만다는 경우 대부분 클래스를 사용하지만,
    비교적 작은 데이터를 저장하거나 값 형식이 필요한 경우 구조체를 사용한다.
    클래스에 비해 기능이 제한되는 경우가 대부분이지만,
    Swift에서는 multi paradigm 지원을 위해 클래스와 거의 동등한 기능을 지원한다.

    구조체도 클래스와 마찬가지로 설계도의 역할을 하기 때문에
    객체지향 이후에 다룰 함수형, 프로토콜 지향형 프로그래밍에서는 새로운 형식을 만들 때 주로 이 구조체를 이용한다.
    단, 구조체로 만들어진 결과물을 객체라 하지 않고 값이라 부른다는 차이가 있다.
    Swift에서는 구조체나 클래스로 만들어진 값과 객체를 구분 없이 인스턴스라고 부른다.

Structure

Syntax

struct StructName{
    property
    method
    initializer
    subscript
}

구조체의 이름은 UpperCamelCase로,
구조체에 속하는 member(멤버)의 이름은 lowerCamelCase로 짓는다.

struct Person {
	var name: String
	var age: Int
	
	func speak() {
		print("Hi there.")
	}
}

let p = Person(name: "James", age: 20)
결과

Person

method는 함수와 같은 형식으로 구조체의 안에서 선언한다.
구조체는 설계도의 역할만 하므로, 구조체를 사용해 인스턴스를 생성해야 한다.
let이나 var키워드와 함께 위와 같이 인스턴스를 생성한다.

struct Person {
	var name: String
	var age: Int
	
	func speak() {
		print("Hi there.")
	}
}

let p = Person(name: "James", age: 20)

//-----

p.name
p.age
p.speak()
결과

James
20
Hi there

인스턴스의 속성과 method에 접근하기 위해서는 위와 같이 '.'을 사용해 접근한다.
함수는 함수의 이름 만으로 호출이 되고, method는 인스턴스의 이름과 함께 '.'으로 연결돼서 호출된다.
혼동하지 않도록 한다.

Class (클래스)

Syntax

class ClassName {
    property
    method
    initializer
    deinitializer
    subscrupt
}

class 키워드로 시작한다는 점만 제외하면 구조체와 거의 동일하다.

class Person {
	var name = "Tom"
	var age = 30
	
	funx speak() {
		print("hello, there")
	}
}

let p = Person()

p.name
p.age
p.speak()
결과

Tom
30
hello, there

속성과 method에 접근하는 방법도 구조체와 동일하다.

  Structure Class
Property ⭕️ ⭕️
Method ⭕️ ⭕️
Initializer ⭕️ ⭕️
Subscrupt ⭕️ ⭕️
Extension ⭕️ ⭕️
Protocol ⭕️ ⭕️
Type Value Type Reference Type
Deinitializer ⭕️
Inheritance ⭕️
Reference
Counting
⭕️

구조체와 클래스는 저장공간을 처리하는 방식에 차이가 있다.
구조체는 스택에 저장하며 값을 저장할 때마다 복사본을 생성한다. 이를 값 형식이라고 하며, 열거형과 기본 자료형들 모두가 이에 속한다.
클래스는 힙에 값을 저장하고, 힙의 주소를 스택에 저장한다. 값을 전달하면 복사본을 생성하지 않으며, 주소만 전달한다. 이를 참조 형식이라고 하며, 클로저가 이에 속한다.
소멸자와 상속은 클래스에서만 지원하며, 클래스는 인스턴스가 종료되면 메모리에서 삭제되지만 클래스는 래퍼런스 카운팅을 지원한다.

 

Initializer Syntax (새 인스턴스 생성하기)

let str = "123"
let num = Int(str)
결과

"123"
123

이전에 type conversion에서 사용했던 코드이다.
당시에는 정수 타입 생성자를 사용해 문자열 "123"을 정수로 바꾸었다.
정수 생성자는 전달되는 값을 숫자로 바꿔 새로운 인스턴스로 반환한다.
만약 불가능하다면 nil을 반환한다.

생성자는 인스턴스를 만들 때 사용하는 특별한 method이며,
다시 말하면 생성자는 모든 값의 초기값을 저장하며, 이를 인스턴스 초기화라고 한다.

Syntax

init(parameters) {
    statements
}

생성자는 따로 이름을 갖지 않기 때문에 init 키워드와 함께 parameter를 바로 사용한다.
생성자 안에서는 속성을 초기화하는 코드를 작성한다.
생성자의 목적인 '속성 초기화'에 맞도록 최대한 간단하게 작성하는 것이 좋다.

class Position {
	var x: Double
	var y: Double
	
	init() {
		x = 0
		y = 0
	}
}

위의 코드는 좌표를 저장하는 클래스의 생성자이다.
따로 전달받을 parameter가 없기 때문에 함수 생성과 동일하게 빈 괄호로 작성한다.
생성자는 '속성의 초기화'가 목적이다.
생성자가 종료되는 시점엔 반드시 해당 클래스나 구조체의 모든 속성이 빠짐없이 초기화되어 있어야 한다.

class Position {
	var x: Double
	var y: Double
	
	init() {
		x = 0
		y = 0
	}

	init(value: Double) {
		x = value
		y = value
	}
}

이번엔 값을 전달했을 경우 해당 값으로 좌표를 초기화하는 생성자를 추가했다.
클래스를 제외한 생성자를 지원하는 구조체와 열거형에도 동일한 문법을 사용한다.

인스턴스를 생성할 때 이름이 없는 생성자는 형식 이름을 사용하여 호출한다.

class Position {
	var x: Double
	var y: Double

	init() {
		x = 0
		y = 0
	}

	init(value: Double) {
		x = value
		y = value
	}
}

//-----

let a = Position()
a.x
a.y

let b = Position(value: 12.3)
b.x
b.y
결과

0
0
12.3
12.3

 

Value Types vs Reference Types

Value Type Reference Type
Structure
Enumeration
Tuple
Class
Closure

구조체, 열거형, 튜플은 모두 값 형식이다.
Int 등의 기본 자료형들 또한 구조체로 만들어져 있으므로 값 형식이다.
클래스와 클로저는 참조 형식이다.

struct PositionValue {
	var x = 0.0
	var y = 0.0
}

class PositionValue2 {
	var x = 0.0
	var y = 0.0
}

위의 클래스와 구조체는 속성에서 기본값을 지정한다.
이러면 parameter가 존재하지 않는 생성자가 자동으로 제공되는데, 이를 기본 생성자라고 한다.

struct PositionValue {
	var x = 0.0
	var y = 0.0
}

class PositionValue2 {
	var x = 0.0
	var y = 0.0
}

//-----

var v = PositionValue()
v.x
v.y
var v2 = PositionValue2()
v2.x
v2.y
결과

0
0
0
0

따로 생성자를 선언하지 않았음에도 정상적으로 초기화된 것을 볼 수 있다.

struct PositionValue {
	var x = 0.0
	var y = 0.0
}

class PositionValue2 {
	var x = 0.0
	var y = 0.0
}

var v = PositionValue()
var v2 = PositionValue2()

//-----

var o = v
o.x
o.y
var o2 = v2
o2.x
o2.y

print("-----")
o.x = 12
o.y = 34

v.x
v.y
o.x
o.y
결과

0
0
0
0
-----
0
0
12
34

구조체인 v와 v를 저장한 o는 값 형식으로
o는 v를 복사해 저장한 것으로 v를 바꾸어도 o는 변하지 않는다.

struct PositionValue {
	var x = 0.0
	var y = 0.0
}

class PositionValue2 {
	var x = 0.0
	var y = 0.0
}

var v = PositionValue()
var v2 = PositionValue2()

var o = v
var o2 = v2

//-----

o2.x = 12
o2.y = 34

v2.x
v2.y
o2.x
o2.y
결과

12
34
12
34

클래스인 v2와 v2를 저장한 o2는 참조 형식으로
o2를 바꾸면 v2도 따라서 바뀌게 된다.
참조 형식은 값을 복사해 저장하는 것이 아닌 원본의 주소를 전달한다.
o2나 v2나 이름이 다르지만 대상은 하나이므로 어떤 것을 바꾸어도 값이 동시에 변하게 된다.

이것이 값 형식과 참조 형식의 차이이다.

두 가지는 상황에 맞게 자유롭게 쓸 수 있지만, 적응하기 전까지는 일반적인 규칙에 맞게 사용한다.

  1. 일반적으로 객체지향 프로그래밍에서는 참조 형식인 클래스를 사용한다.
  2. 상대적으로 적은 데이터를 저장하거나 상속이 필요하지 않다면 값 형식을 사용한다.
  3. 값이 전달되는 시점마다 복사본이 생성되어야 하는 경우에도 값형식을 사용한다.
  4. 연관된 상수 그룹을 표현하는 경우엔 열거형을 사용한다.
  5. 코드 내에서 한 번만 사용되는 형식은 튜플을 사용한다.
  6. 나머지는 모두 구조체를 사용한다.
  7. 함수형, 프로토콜형 프로그래밍에서는 주로 구조체를 사용한다.
  8. 상속이 필요하거나 참조를 전달해야 하는 경우 클래스를 사용한다.

 

Identify Operator (항등연산자)

동일성 비교

값형식

값 형식이 저장되는 메모리 공간은 stack(스택)이다.
하나의 공간에 저장되기 때문에 값 형식의 비교에는 비교 연산자 하나로 충분하다.

참조형식

참조 형식은 값을 heap(힙)에 저장하고, 메모리 주소를 스택에 저장한다.
두 개의 공간에 나눠 저장하기 때문에 비교하는 방법도 두 가지가 필요하다.
값을 비교하는 경우엔 값 형식과 마찬가지로 비교 연산자를 사용한다.
하지만 메모리 주소를 비교할 때는 항등 연산자를 사용한다.

 

Identical to Operator
classInstance === classInstance

Notidentical to Operator
classInstance !== classInstance

 

각각은 주소를 비교하며 Identical to Operator는 주소가 같을 경우 true를,
Notidentical to Operator는 주소가 다를 경우 true를 반환한다.

class A {

}

let a = A()
let b = a
let c = A()

a === b
a !== b
a !== c
결과

true
false
false

b는 a를 저장하기 때문에 주소까지 같은 인스턴스이므로 Identical to Operator는 true를 반환한다.
a와 c는 형식만 같은 다른 인스턴트이므로 Notidentical to Operator가 true를 반환한다.

일반 비교 연산자와 동일하게 같다, 동일하다로 표현되는데
메모리 주소가 같다면 Identical 하다.
값이 같다면 Equal 하다로 구분된다.

 

Nested Types (내포된/포함된 형식)

특별한 사용법 없이, 다른 형식 내부에 선언하면 해당된다.

 

Syntax

String.CompareOptions

 

위와 같이 이름을 '.'으로 연결하여 사용한다.

class One {
	struct Two {
		enum Three {
			case a

			class Four {

			}
		}
	}
}

클래스, 구조체, 열거형 모두 nested type을 사용할 수 있다.
순서도 상관없고, 호환에도 문제가 없다.

class One {
	struct Two {
		enum Three {
			case a

			class Four {
		
			}
		}
	}

	var a = Two()
}

let two: One.Two = One.Two()

선언된 스코프 내에서는 이름으로만 호출하는 것이 가능하다.
하지만 인스턴스를 생성할 때에는 선언된 스코프를 빠져나오므로 위와 같이 '.'으로 연결해야 한다.

nested type의 장점은 가독성이다.
애플이 제공하는 formatter는 두 가지 종류가 있는데
NumberFormatter는 숫자를 특정한 형식의 문자열로 바꿀 때 사용하고,
DateFormatter는 날짜를 계산하는 데 사용한다.

두 클래스는 문자열 형식을 지정하는 스타일 속성을 가지고 있다.
만약 nested 형식을 지원하지 않는다면 다음과 같은 모습이 된다.

Style

Style
NumberFormatter DateFormatter
var style: Style var style: Style

이 경우 잘못된 것은 아니지만 Style만 보면 이게 어느 formatter의 style 형식인지 구별이 쉽지 않다.

NumberFormatter DateFormatter
var style: NumberStyle var style: DateStyle
Style Style

반면 nested type을 사용하면 이를 구분할 필요도 없고, 신경 쓸 필요도 없다.

연습

새로운 인스턴스를 만들어 Four 클래스 저장하기, 새로운 변수에 케이스 a 저장하기.

class One {
	struct Two {
		enum Three {
			case a
			
			class Four {

			}
		}
	}
	
	var a = Two()
}

let custom: One.Two.Three.Four = One.Two.Three.Four()
var cutomVal = One.Two.Three.a

 


Log

2021.09.05.
블로그 이전으로 인한 글 옮김 및 수정