iOS 개발자/swift 기초

스위프트 기초7 (class, struct, override, upercast, downcast, init, convenience init)

FDEE 2021. 2. 21. 17:08

강의 - www.fastcampus.co.kr/dev_online_iosapp

 

iOS 앱 개발 올인원 패키지 Online. | 패스트캠퍼스

차근차근 따라하다 보면 어느새 나만의 iOS 앱을 개발할 수 있게 됩니다.

www.fastcampus.co.kr

깃허브 - github.com/FreeDeveloper97/iOS-Study-FastCompus

 

FreeDeveloper97/iOS-Study-FastCompus

Contribute to FreeDeveloper97/iOS-Study-FastCompus development by creating an account on GitHub.

github.com

 

08. 스위프트 class

 

" class "

reference 타입을 저장하는 용도로 사용된다

저장영역 : Heap 영역에 저장된다 Stack에 비해 (속도가 느리다)

시스템에서 동적으로 할당이 가능하다.

다만, 자동으로 데이터를 제거하지 않는다.

class를 저장하는 변수는 stack에서 저장된다

 

" class vs struct 차이점 "

 

" class vs struct 구조 "

struct와 class의 구조는 거의 비슷하다

1. struct 구조

 -> struct의 경우 property를 수정하는 method의 경우 mutating 키워드가 필요하다

//struct 구조
struct PersonStruct {
    //properties
    var firstName: String
    var lastName: String
    var fullName: String {
        return "\(firstName) \(lastName)"
    }
    //method
    mutating func uppercaseName() {
        //String - uppercased() : 대문자로 변환하는 메소드
        firstName = firstName.uppercased()
        lastName = lastName.uppercased()
    }
    //struct의 경우 init이 없어도 된다는 특징이 있다
    init(firstN: String, lastN: String) {
        self.firstName = firstN
        self.lastName = lastN
    }
}

2. class 구조

-> class의 경우 init 함수가 필수이다

//class 구조
class PersonClass {
    //properties
    var firstName: String
    var lastName: String
    var fullName: String {
        return "\(firstName) \(lastName)"
    }
    //class의 경우 mutating을 사용하지 않는다
    func uppercaseName() {
        //String - uppercased() : 대문자로 변환하는 메소드
        firstName = firstName.uppercased()
        lastName = lastName.uppercased()
    }
    //class의 경우 init이 필수로 있어야 한다
    init(firstN: String, lastN: String) {
        self.firstName = firstN
        self.lastName = lastN
    }
}

 

" class vs struct 값 비교 "

//struct vs class 값 비교
var personStruct1 = PersonStruct(firstN: "Jason", lastN: "Lee")
var personStruct2 = personStruct1

var personClass1 = PersonClass(firstN: "Jason", lastN: "Lee")
var personClass2 = personClass1

personStruct2.firstName = "Jay"
personStruct1.fullName			//Jason Lee
personStruct2.fullName			//Jay Lee

personClass2.firstName = "Jay"
personClass1.fullName			//Jay Lee
personClass2.fullName			//Jay Lee
//동적으로 새로운 클래스 생성 후 비교
personClass2 = PersonClass(firstN: "Bob", lastN: "Lee")
personClass1.fullName			//Jay Lee
personClass2.fullName			//Bob Lee
//참조값을 동일하게 설정
personClass1 = personClass2
personClass1.fullName			//Bob Lee
personClass2.fullName			//Bob Lee

class의 경우 참조값이기 때문에 수정하면 같이 수정되는 모습을 볼 수 있다

struct의 경우 독립적인 값이기 때문에 수정하면 따로 수정되는 모습을 볼 수 있다

" struct를 사용하는 경우 "

1. 두 object가 "같음" 또는 "다름"을 비교하는 경우

let point1 = Point(x: 3, y: 5)
let point2 = Point(x: 3, y: 5)
print(point1 == point2)

2. copy된 각 객체들이 독립적인 상태를 가져야 하는 경우

var myMac = Mac(owner: "Jason")
var yourMac = myMac
yourMac.owner = "Jay"

myMac.owner
yourMac.owner

3. 코드에서 object의 데이터를 여러 스레드 걸쳐 사용할 경우 사용된다

-> 각 스레드에서 유니크한 struct 값을 사용하기 때문에 꼬일 위험이 없어 안전하다

 

" class를 사용하는 경우 "

1. 두 object의 인스턴스 자체가 같음을 확인해야 할 때 사용된다

-> 독립적인 값의 상반되는 개념으로 동일대상인지 여부를 확인해야 할 때 사용된다

 

2. 하나의 객체가 필요하고, 여러 대상에 의해 접근되고 변경이 필요한 경우 사용된다

 

" class - property로 struct를 지니는 경우 "

1. struct

//성적 struct
struct Grade {
    var letter: Character
    var points: Double
    var credits: Double
}

2. class 내의 struct를 넣을 수 있다

//학생 class
class Student {
    var grades: [Grade] = [] //struct를 넣을 수 있다
    var firstName: String
    var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    func printMyName() {
        print("My name is \(firstName) \(lastName)")
    }
}

 

" class - 상속 "

super class(부모)의 속성 + 추가적인 property, method를 추가 -> child class(자식) 클래스가 된다

-> 동일속성은 부모를 통해 사용함으로써 코드 중복을 줄일 수 있다

//사람 class
class Person {
    var firstName: String
    var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    func printMyName() {
        print("My name is \(firstName) \(lastName)")
    }
}

1. 독립적인 class의 경우 구조

//학생 class
class Student {
    var grades: [Grade] = []
    //중복되는 property가 보인다
    var firstName: String
    var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    func printMyName() {
        print("My name is \(firstName) \(lastName)")
    }
}

 

 

2. 상속을 통해 class를 만든 경우 구조

-> class 클래스명 : 부모클래스명 { } 구조를 통해 상속이 가능하다

-> 중복된 property, method 들은 부모의 속성을 사용하면 된다

//학생 class
class Student_after: Person {
    var grades: [Grade] = []
    //중복되는 property들은 상속받았기 때문에 그대로 사용이 가능하다!
}

 

3. 부모의 property, method 사용예시

//상속받았기 때문에 property, method를 그대로 사용할 수 있다
//상속받은 class는 부모의 init을 사용할 수 있다
let jay = Person(firstName: "Jay", lastName: "Lee")
let jason = Student(firstName: "Jason", lastName: "Lee")

jay.firstName
jason.firstName

jay.printMyName()
jason.printMyName()

 

" class - 상속 of 상속 "

상속은 무한으로 할 수 있다

1. Person

//Person
class Person {
    var firstName: String
    var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    func printMyName() {
        print("My name is \(firstName) \(lastName)")
    }
}

2. Person -> Student

//Person -> Student
class Student: Person {
    var grades: [Grade] = []
    //중복되는 property들은 상속받았기 때문에 그대로 사용이 가능하다!
}

3. Person -> Student -> StudentAthlete

//Person -> Student -> StudentAthlete
class StudentAthlete: Student {
    var minimumTraningTime: Int = 2
    var trainedTime: Int = 0
    
    func train() {
        trainedTime += 1
    }
}

4. Person -> Student -> StudentAthlete -> FootballPlayer

//Person -> Student -> StudentAthlete -> FootballPlayer
class FootballPlayer: StudentAthlete {
    var footballTeam = "FC Swift"
    //같은 메소드를 자식에서 수정할 때 override를 사용한다
    override func train() {
        trainedTime += 2
    }
}

 

" class - 상속 - override "

부모의 method를 그대로 사용하지 않고 수정해야 하는 경우

-> override를 통해 재정의 할 수 있다

1. super class : train() method

class StudentAthlete: Student {
    var minimumTraningTime: Int = 2
    var trainedTime: Int = 0
    
    func train() {
        trainedTime += 1
    }
}

2. child class : train() method : override (재정의)

class FootballPlayer: StudentAthlete {
    var footballTeam = "FC Swift"
    //같은 메소드를 자식에서 수정할 때 override를 사용한다
    override func train() {
        trainedTime += 2
    }
}

3. override 결과 확인

var athelete1 = StudentAthlete(firstName: "Yuna", lastName: "Kim")
var athelete2 = FootballPlayer(firstName: "Heung", lastName: "Son")

//override 확인
athelete1.train()
athelete2.train()

athelete1.trainedTime	//1
athelete2.trainedTime	//2

 

" class - upperCast "

※ super class (부모) <- child class (자식)이 가능하다! ※

-> 부모 class의 그릇에 해당되는 자식 class 값들을 담을 수 있기 때문이다

-> 자식 class에 추가적으로 정의된 property, method는 사용할 수 없게 된다 (데이터 누락 발생)

-> override 된 method가 있는 경우 재정의된 method가 동작된다!

var athelete1 = StudentAthlete(firstName: "Yuna", lastName: "Kim")
var athelete2 = FootballPlayer(firstName: "Heung", lastName: "Son")

//upperCast를 통해 부모 <- 자식 저장이 가능하다
athelete1 = athelete2 as StudentAthlete

//다만 부모의 method 대신 자식의 override된 method가 작동된다
athelete1.train()		//+2가 된다
athelete1.trainedTime	//1이 아닌 2가 된다

 

" class - downCast "

super class (부모) -> child class (자식)으로 변경하는 경우

※ child class에 해당되는 property값이 없는경우 에러가 발생하므로 optional,  binding을 사용하여야 한다 ※

//downCast를 통해 FootBall로 바꿀 수 있다
if let son = athelete1 as? FootballPlayer {
    print("--> team: \(son.footballTeam)")
}

 

" class - 상속 - init "

1. 별다른 명시가 없는 경우 부모class의 init을 사용할 수 있다 (init)

var athelete1 = StudentAthlete(firstName: "Yuna", lastName: "Kim")
var athelete2 = FootballPlayer(firstName: "Heung", lastName: "Son")

2. 명시적으로 부모class의 init을 사용할수도 있다 (super.init)

struct Grade {
    var letter: Character
    var points: Double
    var credits: Double
}

class Person {
    var firstName: String
    var lastName: String

    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }

    func printMyName() {
        print("My name is \(firstName) \(lastName)")
    }
}
class Student: Person {
    var grades: [Grade] = []
    
    override init(firstName: String, lastName: String) {
        super.init(firstName: firstName, lastName: lastName)
    }
}

3. 새로운 property를 초기화해야할 경우 새로운 init을 작성할 수 있다 (self.init)

※ 상속된 class에서 init을 할 경우 부모 class의 super.init 까지 완료된 후에 부모 class의 property, method 사용이 가능하다 ※

// 학생인데 운동선수
class StudentAthlete: Student {
    var minimumTrainingTime: Int = 2
    var trainedTime: Int = 0
    var sports: [String]	//새로운 property
    
    //새로운 property를 초기화하기 위한 새로운 init이 필요하다
    init(firstName: String, lastName: String, sports: [String]) {
        //자식의 init 실행 -> 부모의 init 실행
        self.sports = sports
        super.init(firstName: firstName, lastName: lastName)
        
        //super.init를 완료한 후에 method를 사용이 가능하다
        self.train()
    }
}

4. 추가적인 init을 만들 수 있다 (conveience init)

※ 단, 꼭 self.init이 있어야만 사용이 가능하다 ※

class StudentAthlete: Student {
    var minimumTrainingTime: Int = 2
    var trainedTime: Int = 0
    var sports: [String]
    
    init(firstName: String, lastName: String, sports: [String]) {
        self.sports = sports
        super.init(firstName: firstName, lastName: lastName)
    }
    
    //두번째의 init
    convenience init(name: String) {
        self.init(firstName: name, lastName: "", sports: [])
    }
}

 

" class - designated init vs convenience init "

designated init : 일반적인 init()

convenience init : 추가적인 init()

 

1. designated init은 super의 designated init을 호출해야 한다

2. convenience init은 같은 class 내의 init을 꼭 호출해야 한다

3. 결국 convenience init은 designated init을 호출해야 한다

 

" class - 상속을 사용하는 경우 "

1. single responsibility (단일책임)

-> 한 클래스는 하나의 책임을 지니면 된다. 여러개의 책임을 지닐 필요가 없다!

-> 즉 최대한 한 기능위주로 작성되어야 한다

 

2. type safety (타입이 분명해야 할 때)

3. shared base classes (다자녀가 있다)

4. extensibility (확장성이 필요한 경우)

5. identity (정체를 파악하기 위해서)