이 포스팅은 Swift 시리즈 20 편 중 2 번째 글 입니다.
목차
구조체(struct)
- 대부분의 타입이 구조체
- 대문자 Camel-case
- Property와 method로 나뉨
struct Sample{
var mutableProperty = 100 // 가변 프로퍼티 - 값 변경 가능
let immutableProperty = 100 // 불변 프로퍼티 - 값 변경 불가
static var typeProperty = 100 // 타입 프로퍼티 - struct 자체가 사용하는 프로퍼티
}
var a:Sample = Sample()
print(a.immutableProperty) // 100
print(a.mutableProperty) // 100
//print(a.typeProperty) // 사용 불가
struct Sample{
func instanceMethod() {
print("instance method")
}
static func typeMethod() {
print("type method")
}
}
var a:Sample = Sample()
a.instanceMethod()
//a.typeMethod() // 사용 불가
- static 키워드는 해당 타입 안에서만 사용할 수 있는 프로퍼티나 메서드를 정의할 때 사용
struct Sample{
var mutableProperty = 100 // 가변 프로퍼티 - 값 변경 가능
let immutableProperty = 100 // 불변 프로퍼티 - 값 변경 불가
static var typeProperty = 100 // 타입 프로퍼티 - struct 자체가 사용하는 프로퍼티
func instanceMethod() {
print("instance method")
}
static func typeMethod() {
print("type method")
}
}
- 또한 해당 struct를 사용할 때도 var, let을 사용하여 가변, 불변 인스턴스를 만들 수 있음
var mutableStruct: Sample = Sample()
let immutableStruct: Sample = Sample()
//mutableStruct.immutableProperty = 200 // 할당 불가
mutableStruct.mutableProperty = 200 // 할당 가능
//immutableStruct.immutableProperty = 200 // 할당 불가
//immutableStruct.mutableProperty = 200 // 할당 불가
- 어떻게 struct를 선언하느냐에 따라 해당 안의 변수까지 모두 할당 불가할 수 있음
Sample.typeProperty = 300
Sample.typeMethod()
- 구조체 자체에서 사용하는 값은 위와 같이 변경 가능
연습하기
struct Student{
var name: String = "unknown"
var `class`:String = "Swift" // 키워드도 `로 묶어주면 이름으로 사용가능
static var totalNumOfStudent: Int = 0
// self를 명시하지 않아도 기본적으로 사용가능
func selfIntroduce(){
print("저는 \(self.class)반 \(name)입니다.")
}
// static 키워드를 사용할 경우 오버로딩 가능
static func selfIntroduce() {
// print("저는 \(self.class)반 \(name)입니다.") // static 변수만 타입 메서드에서 사용 가능
print("학생 타입입니다. 현재 등록된 학생 인스턴스는 \(totalNumOfStudent)입니다.") // static property는 사용가능
}
}
Student.selfIntroduce() // 학생 타입입니다. 현재 등록된 학생 인스턴스는 0입니다.
var wansik: Student = Student()
wansik.class = "스위프트"
wansik.name = "wansik"
wansik.selfIntroduce() // 저는 스위프트반 wansik입니다.
let wansik2: Student = Student()
//wansik2.class = "스위프트" // 불변 인스턴스로 선언했기 때문에 값 변경 불가
//wansik2.name = "wansik"
wansik2.selfIntroduce() // 저는 Swift반 unknown입니다.
클래스(class)
- 클래스는 참조 타입
- 참조 타입이기 때문에, let으로 선언해도 인스턴스 프로퍼티의 값을 변경할 수 있다.
- 다만 해당 변수의 참조정보(포인터)를 변경할 수는 없다. 아래에서 보자.
- 타입 이름은 대문자 Camel-case 사용
- 다중 상속 불가 - 요즘 언어의 특징
타입 메서드. class, static
class Sample {
var mutableProperty: Int = 100
let immutableProperty: Int = 100
static var typeProperty: Int = 100
// 인스턴스에서 사용할 수 있는 메서드
func instanceMethod() {
print("instance Method")
}
// 해당 타입 안에서 사용할 수 있는 메서드
// struct는 애초에 상속 개념이 없기 때문에 얘기하지 않았지만
// class에서 static은 상속했을 때 오버라이딩이 불가하다.
static func typeMethod() {
print("type method - static")
}
// 상속해서 사용하기 위한 타입 메서드는 class 키워드를 붙여야 한다.
class func classMethod() {
print("class method - static")
}
}
var a: Sample = Sample()
a.instanceMethod() // instance Method
Sample.classMethod() // class method - static
Sample.typeMethod() // type method - static
- 기본적으로 struct와 동일하다.
- 가변 프로퍼티
- 불변 프로퍼티
- 타입 프로퍼티
- 타입 메서드
- 그런데, 결정적으로 struct와 다른 점은 class는 상속의 개념이 들어간다는 것.
- 그렇기 때문에 type 메서드 역시 상속이 되는 경우, 그렇지 않은 경우로 나눌 수 있다.
- static 키워드를 사용하게 되면, 해당 class type만이 사용하는 타입 메서드
- class 키워드를 사용하게 되면, 자식 class에서 오버라이딩해서 사용할 수 있는 타입메서드가 된다.
- 기본적으로 처음 타입메서드를 사용한 클래스에서는 static, class 키워드가 붙은 타입 메서드 모두 실행이 가능하다.
class SampleInheritance: Sample{
// 인스턴스를 만들었을 때, 인스턴스메서드를 상속하여 오버라이딩 가능
override func instanceMethod(){
print("inheritance instance method")
}
// 상속했을 때, class 키워드가 붙은 함수는 슈퍼 클래스의 타입 메서드를 상속할 수 있음
override class func classMethod(){
print("inheritance class method - static")
}
}
- 이런식으로 슈퍼클래스의 class 키워드가 붙은 타입 메서드의 경우 상속하여 자식 클래스의 타입 메서드로 사용이 가능하다.
- static 키워드가 붙은 타입 메서드의 경우 애초에 상속이 불가능하다.
- instance method의 경우 당연히 상속이 가능하다.
var b:SampleInheritance = SampleInheritance()
b.instanceMethod() // inheritance instance method
SampleInheritance.classMethod() // inheritance class method - static
클래스 인스턴스
class Sample{
var mutableProperty = 100 // 가변 프로퍼티 - 값 변경 가능
let immutableProperty = 100 // 불변 프로퍼티 - 값 변경 불가
static var typeProperty = 100 // 타입 프로퍼티 - class 자체가 사용하는 프로퍼티
}
var mutableClass: Sample = Sample()
let immutableClass: Sample = Sample()
//mutableClass.immutableProperty = 200 // 할당 불가
mutableClass.mutableProperty = 200 // 할당 가능
// 클래스는 참조 타입(포인터)이기 때문에 접근해서 인스턴스 프로퍼티의 값을 변경할 수 있다.
//immutableClass.immutableProperty = 200 // 할당 불가
immutableClass.mutableProperty = 200 // 할당 가능
// 다만 해당 참조 정보(포인터)를 변경할 수는 없다.
//immutableClass = mutableClass // 에러
이전에 Struct는 새롭게 변수에 할당하면서 인스턴스화 할 때, 값자체를 복사해서 주기 때문에 let
으로 선언한 경우, 해당 struct안에 있는 instance property에 대해 var
로 선언된 가변 프로퍼티나, let
으로 선언된 불변 프로퍼티 상관없이 수정이 불가능했다.
하지만 class의 경우 참조타입이기 때문에 let
으로 선언한 경우에도 인스턴스 프로퍼티에 대해 수정이 가능하다. 다만 해당 참조 정보, 즉 포인터 정보자체를 변경하는 것은 불가능하다.
연습하기
class Student{
var name: String = "unknown"
var `class`: String = "Swift"
static func selfIntroduce() {
print("학생타입입니다. static 키워드 사용")
}
class func selfIntroduce2() {
print("학생타입입니다. class 키워드 사용")
}
func instanceMathod(){
print("인스턴스 메서드 입니다. 저는 \(self.class)반 \(name)입니다")
}
}
var a: Student = Student()
a.name = "wansik"
a.class = "스위프트"
a.instanceMathod() // 인스턴스 메서드 입니다. 저는 스위프트반 wansik입니다
Student.selfIntroduce() // 학생타입입니다. static 키워드 사용
Student.selfIntroduce2() // 학생타입입니다. class 키워드 사용
class HighStudent: Student {
override class func selfIntroduce2() {
print("상속 받은 학생타입입니다. class 키워드 사용")
}
override func instanceMathod() {
print("상속 받은 인스턴스 메서드 입니다. 저는 \(self.class)반 \(name)입니다")
}
}
var b: HighStudent = HighStudent()
b.name = "jungmoo"
b.class = "iOS"
b.instanceMathod() // 상속 받은 인스턴스 메서드 입니다. 저는 iOS반 jungmoo입니다
// 슈퍼클래스의 함수는 다형성 때문에 사용가능
HighStudent.selfIntroduce() // 학생타입입니다. static 키워드 사용
// 재정의된 타입 메서드
HighStudent.selfIntroduce2() // 상속 받은 학생타입입니다. class 키워드 사용
열거형(enum)
- Swift의 열거형은 강력하다!
- 유사한 종류의 여러 값을 한 곳에 모아서 정의한 것 (요일, 월, 계절)
- enum 자체가 하나의 데이터 타입
- 대문자 Camel-case
- 각 case는 소문자 카멜 케이스로 정의
- 각 case는 그 자체로 고유한 값
- C에서는 열거형 사용하면 고유의 정수값이 할당
- 얘는 아니야.
- case를 나열해서 나타낼 수도 있다.
enum Weekday {
case mon
case tue
case wed
case thu, fri, sat, sun
}
// 열거형은 열거형에 정의된 case중의 하나를 변수로 매길수 있음
var day: Weekday = Weekday.mon // var day = Weekday.mon 이것도 되긴 함
day = .tue // day라는 것이 Weekday라는 열거형 타입이 명확하므로 케이스로 표현해도 가능
print(day) // tue
- case를 나열해도 된다는 것이 위의 예같은 것임
switch day {
case .mon, .tue, .wed, .thu:
print("평일입니다. 일하세요.")
case Weekday.fri: // 이렇게 명시적으로 enum타입과 case를 적어도 됨
print("금요일은 좀 쉬어야죠")
default:
print("시체가 되는 주말입니다.")
}
- 모든 열거형에 대해 다 작성한다면, default를 작성할 필요는 없음
switch day {
case .mon, .tue, .wed, .thu:
print("평일입니다. 일하세요.")
case Weekday.fri: // 이렇게 명시적으로 enum타입과 case를 적어도 됨
print("금요일은 좀 쉬어야죠")
case .sat, .sun:
print("시체가 되는 주말입니다.")
}
원시값(rawValue)
enum Fruit: Int {
case apple
case grape = 3
case peach // 굳이 설정하지 않을 경우 자동으로 1이 증가한 값이 할당됨
// case mango = 0 // 불가능, 이미 순서대로 0, 1, 2가 들어간 상태
}
print(Fruit.apple) // apple
print(Fruit.apple.rawValue) // 0
print(Fruit.grape.rawValue) // 3
print(Fruit.peach.rawValue) // 4
- 기존의 C 언어처럼 각 case마다 정수값을 가질 수 있음
- 이 때, 할당해주는 값을 원시값이라 함
- 원시값을 할당하기 위해서는 각 케이스가 가지는 원시값의 타입을 명시해야 함
- 이 원시값은 각각 다른 값을 가져야 한다.
enum School: String {
case elementry = "초등"
case middle = "중등"
case high = "고등"
case university
}
print(School.elementry, School.elementry.rawValue) // elementry 초등
print(School.middle, School.middle.rawValue) // middle 중등
print(School.high, School.high.rawValue) // high 고등
print(School.university, School.university.rawValue) // university university
- 신기하게도 원시값을 정수말고 다른 타입으로 설정할 수도 있다.
- 설정하지 않으면 case의 이름을 원시값으로 사용한다.
원시값으로 초기화
- 그러면 때로는 이 원시값의 의미를 기반으로 초기화하고 싶을 수 있다.
let schoolLevel: School? = School(rawValue: "중등")
print(schoolLevel) // Optional(__lldb_expr_1.School.middle)
- 값이 Optional을 씌워서 나오게 된다.
- 이는 굉장히 당연한데, rawValue를 기반으로 검색을 하는 것이기 때문에 값이 없을 수 있기 때문
- 사실 저렇게 쓰면 print 문에서 경고가 난다.
- 이 경고는 schoolLevel이라는 변수가 지금 School의 옵셔널로 정의가 되었는데 이 때 발생할 수 있는 경우의 수가 두가지다.
- rawValue에 해당하는 값이 있으면 print 문을 수행하는데 있어서 Optional로 값이 래핑되어 있기 때문에 이를 해제해주는 것이 좋다.
!
를 써서 강제로 언래핑을 하든, 나온 값을 형변환을 해주는 것이 좋다.print(schoolLevel2!)
또는print(schoolLevel2! as School)
- 만약 rawValue에 해당하는 값이 없으면 nil일텐데, nil인 경우 보통하는 일이 default 값으로 대체를 하는 것이 좋다.
메서드
// 메서드도 추가가 가능하다.
enum Month {
case dec, jan, feb
case mar, apr, may
case jun, jul, aug
case sep, oct, nov
func printMessage() {
switch self {
case .mar, .apr, .may:
print("따스한 봄~")
case .jun, .jul, .aug:
print("여름 더워요~")
case .sep, .oct, .nov:
print("가을은 독서의 계절!")
case .dec, .jan, .feb:
print("추운 겨울입니다")
}
}
}
Month.mar.printMessage()
enum 안에 메서드를 추가하여 사용할 수 있다.
class, struct, enum 구분
class, struct, enum
- Class
- 전통적인 OOP 관점에서의 클래스
- 단일 상속
- 인스턴스/타입 메서드
- 인스턴스/타입 프로퍼티
- 참조타입
- Apple 프레임워크의 대부분의 큰 뼈대는 모두 클래스
- Struct
- C언어 등의 구조체보다 다양한 기능
- 상속 불가능
- 인스턴스/타입 메서드
- 인스턴스/타입 프로퍼티
- 값타입
- Swift의 대부분의 큰 뼈대는 모두 구조체
- enum
- 다른 언어의 열거형과는 많이 다름
- 상속 불가능
- 인스턴스/타입 메서드
- 인스턴스/타입 프로퍼티
- 값타입
- 유사한 종류의 여러 값을 유의미한 이름으로 한 곳에 모아 정의
- 열거형 자체가 하나의 데이터 타입
- case하나하나가 전부 하나의 유의미한 값으로 취급
- 이전의 정수값으로 매핑되는 것과 상이
클래스와 구조체
- 구조체
- 값타입이기 때문에, 전달할 때 복사가 필요한 경우
- 상속할 필요가 없는 경우에 클래스 말고 구조체를 사용하는 것이 용이
struct ValueType{
var property = 1
}
let firstStructInstance = ValueType()
var secondStructInstance = firstStructInstance
secondStructInstance.property = 2
print(firstStructInstance.property) // 1
print(secondStructInstance.property) // 2
secondStructInstance
가firstStructInstance
를 받을 대, value로 받아 값이 변경된 경우에 기존에 배정해준 struct의 값이 변경되지 않았다.
class ReferenceType {
var property = 1
}
let firstClassInstance = ReferenceType()
var secondClassInstance = firstClassInstance
secondClassInstance.property = 2
print(firstClassInstance.property) // 2
print(secondClassInstance.property) // 2
- reference 타입은 기존 객체의 값이 변경된 것을 확인할 수 있다.