swift는 다른 언어들 처럼 프로퍼티와 메서드를 갖는 구조체와 클래스를 갖는다, 객체지향 언어이기 때문
다른 객체지향 언어들의 경우(java, C++, C#, Python 등) 클래스를 많이 사용한다
하지만 swift의 경우 iOS에서 사용하는 경우 구조체 사용을 많이한다, 실제로 swiftUI에서 View는 구조체로 되어있다.
이번 페이지에서는 이 클래스와 구조체에 대해서 알아보고자 한다.
구조체와 클래스에 대해 알아보기 전에 인스턴스와 값타입/참조타입에 대해 어느정도 알아야 한다.
구조체와 클래스는 형태를 정의하고 인스턴스를 생성한다.
이 구조를 붕어빵을 예를 들어 설명을 많이 하는데 다음과 같다.
붕어빵의 경우 붕어빵틀 반죽과 팥을 넣고 결과물인 붕어빵을 만든다.
붕어빵틀이 클래스/구조체 라고 할 수 있다.
그리고 붕어빵이 인스턴스이다.
인스턴스를 하나 만들게 되면 붕어빵이 하나 생겨나는데 여기서 이 붕어빵을 사용하는 방법에서 값타입과 참조타입의 차이점이 나타나는데
참조타입의 경우 하나의 붕어빵을 만들면 여러곳에서 접근해서 사용이 가능하다.
예시 코드를 보며 살펴보자
class Bread(){
var taste: String = "RedBean"
}
var fishBread1 = Bread()
var fishBread2 = fishBread1
이 경우 fishBread1이라는 인스턴스가 생겨나고 fishBread2에 fishBread1을 복사하면 fishBread1 과 fishBread2는 서로 동일한 붕어빵을 바라본다.
fishBread1.taste = "cream"
print(fishBread1.taste)
print(fishBread2.taste)
//결과: cream
//결과: cream
하나의 인스턴스를 여러곳에서 같이 사용하는것이다.
값타입의 경우 생성된 인스턴스는 유일하다. 공유되지 않는다.
struct Bread(){
var taste: String
var size: String
}
var fishBread1 = Bread()
var fishBread2 = fishBread1 //fishBread1과 동일한 내부 데이터를 갖는 인스턴스
이 경우 fishBread1과 fishBread2는 서로 다른 붕어빵(인스턴스)이다.
클래스 였다면 지금까지 1개의 붕어빵(인스턴스)였겠지만
지금은 2개의 붕어빵(인스턴스)이 메모리에 올라가 있다.
이전처럼 해보면
fishBread1.taste = "cream"
print(fishBread1.taste)
print(fishBread2.taste)
//결과: cream
//결과: RedBean
서로 장단점이 있다.
참조타입의 경우 메모리 공간을 적게 사용하는 장점이 있지만 여러곳에서 사용이 가능한 만큼 코드가 복잡해질 경우 내부 프로퍼티가 보장이 안된다.
값타입의 경우 인스턴스가 생성/복사할 때마다 생겨나기에 메모리 관리 측면에서는 불리 할 수 있으나 내부 데이터는 생성했던 인스턴스만 접근이 가능하기에 코드 관리에 용이 하다는 장점이 있다.
struct SomeStructure {
}
class SomeClass {
}
코드의 형태는 다음과 같다.
swift에서 클래스와 구조체는 타입이 된다. SomeStructure, SomeClass타입이 만들어 졌다고 볼 수 있다.
struct PrivateInfo {
var address = "부산시"
var phoneNumber = "010-1234-5678"
var identificationNumber = "012345-678910"
}
class Human {
var name = "Gil Dong"
var age = "32"
var privateInfo = PrivateInfo()
}
개인정보를 저장하는 구조체와 사람을 정의하는 클래스를 만들었다.
각각 struct와 class 키워드를 사용하여 만든다
구조체와 클래스의 인스턴스 생성 방법을 살펴보자
//별도의 생성자가 없다면 ()빈 괄호만 사용해도 된다
let gildong = Human()
다음과 같이 인스턴스를 생성하면 되고, 위 코드는 타입이 생략되어 있는 코드이다.
(원래 형태는 var gildong:Human = Human())
왠만하면 변수나 상수를 생성할때 타입은 명시하는것을 추천한다.
코드 가독성에 도움이 되는것도 있고, 타입을 명시하지 않으면 컴파일러가 타입을 추론하는데 타입추론하는 코드가 많아지면 컴파일 시간 초과 오류가 발생하는 경우가 생긴다.
프로퍼티란 구조체/클래스 내부의 변수/상수를 말한다.
이들에 접근방법은 다음과 같다.
print("my name is \(gildong.name)")
print("my home address is \(gildong.privateInfo.address)")
//result: my name is
gildong.privateInfo.address = "Seoul"
print("my home address is \(gildong.privateInfo.address)")
이런식으로 인스턴스이름.프로퍼티이름 의 형태로 사용한다.
인스턴스의 프로퍼티에서 데이터를 가져오는것, 수정하는것 둘다 가능하다.
(이를 get/set이라고 하는데 추후 따로 다룬다.)
struct PrivateInfo {
var address
var phoneNumber
let identificationNumber = "012345-678910"
}
class Human {
var name
var age
var privateInfo = PrivateInfo()
}
클래스와 구조체의 경우 이전처럼 프로퍼티에 미리 값이 있을 수도 없을수도 있다.
그런경우 우리가 사용하기 위해선 생성자라는 것을 사용해 주어야 한다.
struct PrivateInfo {
var address
var phoneNumber
let identificationNumber = "012345-678910"
}
class Human {
var name
var age
var privateInfo = PrivateInfo()
}
클래스는 init()이라는 키워드를 직접 사용해서 동작을 처리하는 코드를 넣어야 하고 구조체의 경우 생성자를 만들지 않아도 프로퍼티에 값을 넣어주는 생성자를 자동으로 만들어 준다.
var gildong:Human = Human(name: "길동", age: 32) //오류: 클래스는 생성자가 반드시 필요함
var privateGildong:PrivateInfo = PrivateInfo(address: "부산시", phoneNumber: "010-1234-5678", identificationNumber: "00000-123456")
gildong 인스턴스는 생성되지 못하고 컴파일 단계에서 오류가 발생한다. 클래스 내부에 생성자가 없기 때문이다
반면에 구조체인 privateGildong의 경우 프로퍼티의 이름에 맞는 생성자를 자동으로 생성해서 생성자 없이도 인스턴스의 초기화가 가능하다.
//수정된 클래스 Human
class Human {
var name
var age
var privateInfo = PrivateInfo()
init(){
self.name = "gindong"
self.age = 35
}
init(name: String, age: Int){
self.name = name
self.age = age
}
}
init() 생성자는 함수이다 init()의 경우 생성자라고 예약되어 있기 떄문에 func를 생략 할 수 있다.(물론 생성자는 반환도 하지 않는다.)
생성자는 여러개 일 수 있다, 함수처럼 이름이 동일하다 하더라도 매개변수만 다르다면 중복으로 생성이 가능하다,
(하지만 생성자는 인스턴스 생성하는 그 순간에만 1회 사용할 수 있으므르 여러개 있다고 해서 여러번 사용이 가능한 것은 아니다.)