이 포스팅은 Swift 시리즈 20 편 중 2 번째 글 입니다.

  • Part 1 - 01: Optional, Any, AnyObject, nil
  • Part 2 - This Post
  • Part 3 - 03: Closure
  • Part 4 - 04: Property
  • Part 5 - 05: 상속, 생성자, 소멸자
  • Part 6 - 06: 옵셔널 체이닝과 nil 병합
  • Part 7 - 07: 타입 캐스팅
  • Part 8 - 08: assert, guard
  • Part 9 - 09: Protocol
  • Part 10 - 10: Extention
  • Part 11 - 11: 오류처리
  • Part 12 - 12: 고차함수
  • Part 13 - 13: ARC(Automatic Reference Counting)
  • Part 14 - 14: Access control, Access modifier
  • Part 15 - 15: Generics
  • Part 16 - 16: Optional에 대한 깊은 이해
  • Part 17 - 17: Lazy Variables
  • Part 18 - 18: Enumeration
  • Part 19 - 19: Initialization
  • Part 20 - 20: Concurrency
▼ 목록 보기

구조체(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 구분

imageclass, 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
  • secondStructInstancefirstStructInstance를 받을 대, 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 타입은 기존 객체의 값이 변경된 것을 확인할 수 있다.