[iOS] 패스트캠퍼스 - Class
Class
Class는 Reference type이다. 그리고 heap 메모리 영역에 생성된다.
Stack | Heap |
Fast: 시스템에서 당장 실행해야하거나, tight 하게 컨트롤하고 관리되어야 하는 경우. 함수 내에 선언된 지역변수. 효율적이고 빠르다 | Slow: reference type을 저장한다. 큰 메모리 풀을 가지고 있어서 시스템에 동적으로 메모리 할당을 요청할 수 있다. 대신 스택처럼 자동으로 데이터를 제거하지 않기 때문에, 개발자가 신경써서 쓰지 않는 데이터는 제거해줘야 한다. |
Structure를 Class로 바꿔보자.
다음과 같이 PersonStruct 구조체가 있다.
struct PersonStruct {
var firstName: String
var lastName: String
var fullName: String {
return "\(firstName) \(lastName)"
}
mutating func uppercaseName() {
firstName = firstName.uppercased()
lastName = lastName.uppercased()
}
}
이 구조체를 Class로 바꾸면 다음과 같다.
class PersonClass {
var firstName: String
var lastName: String
var fullName: String {
return "\(firstName) \(lastName)"
}
func uppercaseName() {
firstName = firstName.uppercased()
lastName = lastName.uppercased()
}
init(firstName: String, lastName: String){
self.firstName = firstName
self.lastName = lastName
}
}
class는 structure와 코드가 비슷하지만, 다른 부분이 2가지 있다.
- stored property를 fucntion 내에서 바꿀 때 func 앞에 mutating 키워드를 붙이지 않는다.
- init()이라는 생성자를 통해 선언된 stored property의 값을 초기화 해줘야 한다.
Struct vs. Class 언제, 무엇을 쓸까?
이럴때 Struct를 쓰자!
- 두 object를 "같다, 다르다"로 비교해야 하는 경우
- Copy된 각 객체들이 독립적인 상태를 가져야 하는 경우
- 코드에서 오브젝트의 데이터를 여러 스레드 걸쳐 사용할 경우
- 각 쓰레드가 유니크한 인스턴스를 가지고 있어서, 한 객체에 여러 쓰레드가 접근했을 때 발생할 수 있는 잠재적인 위험을 피할 수 있다.
이럴 때 Class를 쓰자
- 두 object의 인스턴스 자체가 같음을 확인해야 할 때
- 하나의 객체가 필요하고, 여러 대상에 의해 접근되고 변경이 필요한 경우
단순하게 이야기 해보자
- 일단 Struct로 쓰자
Swift는 structure를 많이 사용한다. Swift에서는 앞서 배웠던 string, array, dictionary 모두 struct 타입이다.
2. StackOverflow보다 apple 공식 문서를 먼저 확인하자. 습관을 들이자. 그러면 개발에 대한 이해도를 높일 수 있다.
상속
A is B(A는 B에 포함)
해당 명제가 논리적으로 맞으면 A는 B를 상속받을 수 있다.
예를 들면, Student is Person (Student는 Person에 포함) 는 논리적으로 맞는 명제이므로, Student는 Person을 상속받을 수 있다.
이런 경우 Person의 속성을 Student에게 물려주는 것이므로 Person을 Super class(Parent class)라 부른다. 그리고 Student는 Parent의 속성을 물려받는 입장이기 때문에 SubClass (Child Class)라고 부른다.
Person: Super class (Parent Class)
Student: SubClass (Child Class)
상속의 규칙
- 자식은 한개의 super class만 상속받을 수 있다.
- 부모는 여러 자식들을 가질 수 있다.
- 상속의 깊이는 상관이 없다.
Student is Person이므로 Student 는 Person class를 상속받을 수 있다. 이를 Swift 코드로 구현해보면 다음과 같다.
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] = []
}
Student class는 Person 클래스를 상속받으므로, Person class의 firstName, lastName, printMyName() 에 접근할 수 있다.
하지만 Person Class는 Student Class의 grades 속성에는 접근할 수 없다.
이를 좀 더 확장시켜보자. 아까 상속은 얼마든지 깊어질 수 있다고 했다.
// 학생인데 운동선수
class StudentAthlete: Student {
var minimumTrainingTime: Int = 2
var trainedTime: Int = 0
func train() {
trainedTime += 1
}
}
// 학생이자 운동선인데 축구선수
class FootballPlayer: StudentAthlete {
var footballTeam = "FC Swift"
override func train() {
trainedTime += 2
}
}
위의 코드를 보면 정말 상속은 깊이가 없다는 걸 알 수 있다. Person > Student > StudentAthlete > FootballPlayer로 상속이 깊어지고 있다.
그럼 이를 활용하여 객체를 생성해보자
var athelete1 = StudentAthlete(firstName: "Yuna", lastName: "Kim")
var athelete2 = FootballPlayer(firstName: "Heung", lastName: "Son")
athelete1은 StudentAthlete의 인스턴스이고, athelete2는 FootballPlayer의 인스턴스이다. FootballPlayer는 StudentAthelete의 SubClass이다.
이렇게 부모-자식 관계의 클래스는 서로의 클래스 타입으로 캐스팅 해줄 수 있다.
캐스팅 방법엔 2가지가 있다.
- Upper Casting
- Down Casting
Upper casting은 SubClass를 SuperClass의 타입으로 캐스팅하는 것이다.
즉 서브 클래스의 인스턴스를 슈퍼 클래스의 타입으로 참조하는 것을 의미한다.
athelete1 = athelete2 as StudentAthlete
athelete1.train()
athelete1.trainedTime
Down casting은 슈퍼 클래스의 인스턴스를 서브 클래스의 타입으로 참조하는 것이다.
if let son = athelete1 as? FootballPlayer {
print("--> team:\(son.footballTeam)")
}
여기서 못보던 문자가 나온다. as? 는 뭔데 물음표가 붙는 걸까?
Swift는 안전한 언어이다. 슈퍼 클래스에서 서브 클래스로 캐스팅하는 것은 실패할 수 있기 때문에, optional 이 사용되는 것이다!
as? : "런타임 시점"에 타입 캐스팅(다운 캐스팅)을 하며, 실패할 경우 nil을 리턴
상속은 언제 하면 좋을까?
장점: 중복되는 코드를 제거할 수 있다.
단점: 상속의 깊이가 깊어지면 유지보수가 어려워진다.
짬에서 나오는 바이브가 필요한 영역인 것 같다.
즉 개발 철학이 필요하다.
- Single Responsibility(단일 책임)
- 클래스는 단 하나의 책임만 지면 된다. 한가지 일에 집중해야한다. 클래스가 너무 많은 일들을 책임지게 되면 정체성도 모호해지고 유지하기도 어려워진다.
- Type Safety (타입이 분명해야할 때)
- 학생이더라도 이 학생이 어떤 역할을 하는지 그 타입이 분명해야할 때 사용할 수 있다.
- 예를 들어 학생 중에서도 미술을 하는 학생, 운동을 하는 학생 등이 있는데 이들이 무엇을 하는지 그 타입을 알고 싶을 때 상속을 통해 그 타입을 구분할 수 있다.
- 학생이더라도 이 학생이 어떤 역할을 하는지 그 타입이 분명해야할 때 사용할 수 있다.
- Shared Base Clases(다자녀가 있다!)
- 학생이라는 클래스가 학습한다()라는 행위를 구현하고 있다면, 운동선수, 미술을 하는 학생은 그 각각의 학습에 대한 행위가 다르다. 운동선수는 운동을 학습한다, 미술하는 학생은 미술을 학습한다로 학습의 행위가 각각 다르다.(다형성) 이렇게 내용 자체가 다를 때 상속을 고려해볼 수 있다.
- Extensibility (확장성이 필요한 경우)
- 캔이라는 클래스를 콜라캔, 사이다캔 등으로 확장이 필요할 때
- Identify (정체를 파악하기 위해)
- 어떤 객체인가, 클래스인가를 상속을 통해 검증할 수 있다.
- 체대생인지, 미대생인지 어떤 학생을 하는지 타입을 통해 정체를 검증할 수 있다.
- 어떤 객체인가, 클래스인가를 상속을 통해 검증할 수 있다.
생성자 이해하기
자식 클래스는 부모클래스와는 다른 형태의 생성자를 만들 수 있다.
stored property를 선언하면 객체를 생성하는 시점에 프로퍼티는 초기화가 되어있어야 한다.
따라서 선언할 때 초기화를 해주거나, 생성자를 통해서 초기화를 해줘야 한다.
생성자의 규칙이 있는데, 자식 클래스의 stored property를 초기화해주고 난 뒤에 부모 클래스의 init()을 호출하여 부모 클래스의 stored Property를 초기화 해줘야 한다는 것이다.