(스타터 과정이 아닌, 마스터 과정을 정리한 내용)
기본
⎮ Struct와 Class
1. Struct(구조체)
- 값 타입(Value Type). 메모리에서 값만 복사해 사용하므로 독립된 인스턴스(각자의 값을 가지고 있음) 생성.
- 상속이 불가능함.
- 주로 데이터를 저장하거나 간단한 로직을 수행할 때 사용
struct StructName {
property(속성)
method(메소드)
initializer(생성자)
subscript(서브스크립트)
}
/*
property ~ subscript까지를 member라고 부름. member의 요소는 lowcamelCase로 작성
멤버는 필요한만큼 선언하고, 불필요한 요소는 생략해도 됨
property(속성) : Global Scope나 Local Scope에서 선언하면 변수, Type 안에서 선언하면 property
method(메소드) : Global Scope나 Local Scope에서 선언하면 함수, Type 안에서 선언하면 method
Type 선언에서 첫번째 Level에는 선언만 와야함.
*/
struct Person {
var name: String
var age: Int
func speak() {
print("Hello")
}
}
let p = Person(name: "Uddt", age: 25) // 인스턴스 만들기
p.name // 속성에 접근(Uddt)
p.age // 속성에 접근(25)

2. Class(클래스)
- 참조 타입(Reference Type). 메모리에서 동일한 인스턴스를 여러 곳에서 참조(값을 변경하면 다른 변수에도 영향을 줌).
- 상속 가능
- 객체 지향 프로그래밍의 주요 개념
class ClassName {
property(속성)
method(메소드)
initializer(생성자)
deinitializer(디이니셜라이저)
subscript(서브스크립트)
}
/*
property ~ subscript까지를 member라고 부름. member의 요소는 lowerCamelCase로 작성
멤버는 필요한만큼 선언하고, 불필요한 요소는 생략해도 됨
*/
class Person {
var name: String
var age: Int
func speak() {
print("Hello")
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
let p = Person(name: "Uddt", age: 25)

3. Struct와 Class 비교
| 특징 | Struct | Class |
| 메모리 구조 | Stack | Heap |
| 타입 | 값 타입(Value Type) | 참조 타입(Reference Type) |
| 상속 | 불가능 | 가능 |
| 성능 | 성능 우수(메모리 복사가 빠름) | 성능 낮음(메모리 참조) |
| 생성자 | 컴파일러가 자동으로 인식 | init을 반드시 써줘야함 |
| 인스턴스 | 독립적 | 동일한 값을 공유 |
⎮ Protocol
1. Protocol(프로토콜)
- 특정 기능을 정의한 청사진
- Struct, Class, Enum(열거형)에서 프로토콜을 채택(adopt)하고 구현(Implement)할 수 있음.
- 용도 : 코드를 재사용 가능하고 유연하게 설계, 프로토콜 기반의 설계 방식을 가능하게 함.
protocol Greetable {
func greet() -> String
}
struct Person: Greetable {
var name: String
func greet() -> String {
return "Hello, \(name)!"
}
}
let person = Person(name: "김유저")
print(person.greet()) // "Hello, 김유저!"
실습
⎮ Struct 구현하기
이름과 나이를 저장하는 User Struct를 작성하고, 두 명의 독립적인 사용자 인스턴스를 생성해주세요.
두 인스턴스의 값을 변경한 후, 각 인스턴스가 서로 독립적임을 확인하세요.
struct User {
var name: String
var age: Int
}
// 두 개의 독립적인 사용자 인스턴스 생성
var user1 = User(name: "이유저", age: 25)
var user2 = User(name: "김유저", age: 30)
// 두 인스턴스의 값을 변경
user1.name = "박유저"
user2.age = 35
// 독립성을 확인
print("name: \(user1.name), age: \(user1.age)")
print("name: \(user2.name), age: \(user2.age)")
// name: 박유저, age: 25
// name: 김유저, age: 35
⎮ Class 구현하기
이름과 나이를 저장하는 User Class를 작성하고, 두 개의 사용자 참조를 생성해주세요.
한 참조의 값을 변경한 후, 두 참조가 동일한 값을 공유하는지 확인하세요
class UserClass {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
// 두 개의 사용자 참조 생성
var user1Inform = UserClass(name: "김유저", age: 25)
var user2Inform = user1Inform
// user1Inform의 이름을 변경
user1Inform.name = "박유저"
// user2Inform도 동일하게 변경되었음
print("1번 유저 name: \(user1Inform.name), 2번 유저 name: \(user2Inform.name)")
// 1번 유저 name: 박유저, 2번 유저 name: 박유저
// 두 참조는 동일한 값을 공유함
⎮ Protocol 구현하기
greet() 메서드를 포함한 Greetable 프로토콜을 정의합니다.
Person Struct와 Robot Class에서 이 프로토콜을 채택하고 구현하세요.
Person은 이름을 활용하여 인사를 출력, Robot은 고유 ID를 활용해 인사를 출력합니다.
// 프로토콜 선언
protocol Greetable {
func greet() -> String
}
// struct Type에 프로토콜 Type을 입력하여 함수 사용을 강제
struct People: Greetable {
var name: String
func greet() -> String {
return "Hello, \(name)"
}
}
// 인스턴트 생성 후 출력
let user1 = People(name: "김유저")
print(user1.greet())
// Hello, 김유저
// 프로토콜 선언
protocol Greetable {
func greet() -> String
}
// Robot의 Type을 프로토콜로 설정, 함수 사용을 강제
class Robot: Greetable {
var ID : String
init(ID: String) {
self.ID = ID
}
func greet() -> String {
return "Your ID is \(ID)"
}
}
// 인스턴스 생성
let user1ID = Robot(ID: "userID")
print(user1ID.greet())
// Your ID is userID
⎮ Protocol 확장하기(Extension)
Greetable 프로토콜에 기본 구현을 추가하여 greet() 메서드가 기본적으로 "Hello!"를 반환하도록 하세요.
기본 구현을 사용하지 않고 고유 인사를 반환하는 Alien Struct를 추가로 구현하세요.
protocol Greetable {
func greet() -> String
}
extension Greetable {
func greet() -> String {
return "Hello!"
}
}
struct People: Greetable {
var name: String
func greet() -> String {
return "Hello, \(name)"
}
}
struct Alien: Greetable {
var name: String
func greet() -> String {
return "Welcome My world, \(name) " // 여기서 고유 인사를 overwrite
}
}
class Robot: Greetable {
var ID : String
init(ID: String) {
self.ID = ID
}
func greet() -> String {
return "Your ID is \(ID)"
}
}
let user1 = People(name: "김유저")
let user1ID = Robot(ID: "userID")
let alien = Alien(name: "guest")
print(user1.greet())
print(user1ID.greet())
print(alien.greet())
// Hello, 김유저
// Your ID is userID
// Welcome My world, guest
⎮ Extension 더 이해하기
protocol Greetable {
func greet() -> String
}
extension Greetable {
func greet() -> String {
return "Hello!"
}
}
struct Alien: Greetable {
var name: String
func greet() -> String {
return "Greetings, \(name)!"
}
}
let alien = Alien(name: "Zeorge")
print(alien.greet()) // "Greetings, Zeorge!"
struct Person: Greetable {
var name: String
}
let person = Person(name: "Papa")
print(person.greet()) // "Hello!"
위의 코드 중 이 부분을 확인해보자.
struct Person: Greetable {
var name: String
}
원래 protocol로 함수 사용을 강제하면, struct 안에도 함수를 적어야 한다.
여기서는 어떻게 그냥 사용할 수 있었을까?
protocol를 확장하여 greet() 메서드를 기본 구현으로 설정했기 때문이다.
기본 구현 덕분에 struct에서 별도의 메서드 설정이 없어도 alien.greet()를 하면 Hello! 가 출력된다.
extension Greetable {
func greet() -> String {
return "Hello!"
}
}
let alien = Alien(name: "Guest")
print(alien.greet())
//Hello!
따라서, extension을 사용하면 기본 구현을 그대로 사용하거나 필요에 따라 오버라이드해서 사용할 수 있는 장점이 있다.
심화
⎮ 왜 Protocol을 사용해야 할까?
어플 개발을 한다고 가정해보자.
어플 시나리오: 그리기 앱
기본적인 그리기 도구들:
- 원 (Circle)
- 사각형 (Rectangle)
- 선 (Line)
위의 도구말고도 도형의 종류(삼각형, 마름모 등)가 늘어날 수 있다.
프로토콜을 사용하면 기존 코드에 영향을 주지 않고 도형을 추가할 수 있는 코드를 만들 수 있다.
프로토콜이 없을 때와 있을 때 코드가 어떻게 변하는지 확인해보자.
⎮ 프로토콜 없이 코드를 작성할 때
class Circle {
func draw() {
print("Circle drawn")
}
}
class Rectangle {
func draw() {
print("Rectangle drawn")
}
}
class Line {
func draw() {
print("Line drawn")
}
}
// as?로 Type 검사, Type을 검사하고 Type이 아니면 nil을 리턴
func drawShape(shape: Any) {
if let circle = shape as? Circle {
circle.draw()
} else if let rectangle = shape as? Rectangle {
rectangle.draw()
} else if let line = shape as? Line {
line.draw()
} else {
print("Unknown shape")
}
}
let circle = Circle()
let rectangle = Rectangle()
let line = Line()
drawShape(shape: circle) // Circle drawn
drawShape(shape: rectangle) // Rectangle drawn
drawShape(shape: line) // Line drawn
- 발생하는 문제 :
type이 추가될 때마다 조건문을 추가해줘야 함(코드가 점점 길어짐)
Any Type을 사용했기 때문에, Type에 잘못된 값이 들어오면 오류가 발생할 수 있음
⎮ 프로토콜을 사용한다면
protocol Drawable {
func draw()
}
class Circle: Drawable {
func draw() {
print("Circle drawn")
}
}
class Rectangle: Drawable {
func draw() {
print("Rectangle drawn")
}
}
class Line: Drawable {
func draw() {
print("Line drawn")
}
}
func drawShape(shape: Drawable) {
shape.draw()
}
// 도형들을 추가할 때마다 drawShape 함수는 변하지 않음
let circle = Circle()
let rectangle = Rectangle()
let line = Line()
drawShape(shape: circle) // Circle drawn
drawShape(shape: rectangle) // Rectangle drawn
drawShape(shape: line) // Line drawn
삼각형을 그려주는 코드를 작성할 때도 굉장히 수월하게 작성이 가능함
class Triangle: Drawable {
func draw() {
print("Triangle drawn")
}
}
let triangle = Triangle()
drawShape(shape: triangle) // Triangle drawn'스파르타코딩 클럽 > 사전 캠프' 카테고리의 다른 글
| 13. 스파르타 코딩클럽 [사전캠프 - 클로저 / 객체지향 프로그래밍] (0) | 2025.02.19 |
|---|---|
| 12. 스파르타 코딩클럽 [사전캠프 - 자료구조/메모리 구조 및 ARC] (0) | 2025.02.09 |
| 10. 스파르타 코딩클럽 [사전캠프 - 가위바위보 게임 만들기] (1) | 2025.02.07 |
| 9. 스파르타 코딩클럽 [사전캠프 - 함수의 사용 방법 이해하기] (0) | 2025.02.06 |
| 8. 스파르타 코딩클럽 [사전캠프 - 함수의 선언과 사용 방법 이해하기] (0) | 2025.02.06 |