ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Swift] Swift 문법 - (3) Class(클래스) & Struct(구조체)
    Front-End(Mobile)/iOS & Swift 2022. 1. 13. 03:32
    반응형

    이번 포스팅은 이전 Swift의 데이터 타입을 통해 알아본 자료형들(컬렉션, 튜플 등)보다 좀 더 복잡한 자료형이다.

    바로, Class(클래스)와 Struct(구조체) 이다.

     

    사실 Javascript를 사용하면서 Class(완벽히 일치하지 않음) 문법을 체험해 볼 수 있었다.

    하지만, Struct(구조체)라는 개념은 생소하며, 정적언어인 Swift에선 이 둘이 왜 존재하고 어떻게 쓰이는지 이번 포스팅에서 학습해보겠다.


    📙 들어가면서..

    Struct(구조체)와 Class(클래스)는 프로그래머가 데이터를 용도에 맞게 묶어 사용할 때 용이한 기능이다.

    변수(프로퍼티)와 함수(메서드)를 하나의 패키지에 구조화시켜 사용할 수 있으며, 이를 새로운 사용자 정의 타입으로 추상화하는 개념이다.

     

    특히, 클래스는 객체지향(OOP) 프로그래밍 패러다임에 직결하는 개념 중 하나로 자주 언급된다.

    두 자료형은 문법이 비슷하며, 사용하기 위해선 인스턴스를 생성해야한다는 공통점이 있다.

    단, 클래스는 참조 타입(call by reference), 구조체는 값 타입(call by value) 라는 차이가 있다.

     

    JS에서도 느끼는 바이지만, Class를 능숙하게 사용하면 좀 더 깔끔하고 확장성 좋은 코드가 작성되는 것 같다.

    이렇듯, 성능개선의 핵심인 구조체와 클래스에 대해 알아보고, 둘의 공통점과 차이점을 정확히 짚어서 각각 어느 경우에 사용되는 것이 적합한지 정리해보자!

     

     

    📙 Class (클래스)

    클래스는 새로운 데이터 타입을 생성하는 개념이다.

    클래스를 선언한 뒤, 내부에 변수(속성, property)와 함수(메서드, method)를 등록해서 사용하면 된다.

    그리고, 이를 활용하기 위해 변수에 이 클래스를 할당하는데, 이 때 클래스 자체가 아닌 복사본(인스턴스)이 초기화되게 된다.

    class 키워드로 선언하며, 클래스명은 타입명과 같이 대문자 카멜케이스, 내부의 프로퍼티와 메서드는 소문자 카멜케이스로 명명한다.

    class Car {
        var car = "BMW"
        
        func my_car() {
            print("my car is \(car)")
        }
    }
    
    let dreamCar : Car = Car()
    
    print(dreamCar.car)		// BMW
    dreamCar.my_car()			// my car is BMW
    
    dreamCar.car = "Benz"
    dreamCar.my_car()			// my name is Benz

    이처럼, Name 이라는 클래스를 만들고, 안에 name 프로퍼티와 my_name() 메서드를 각각 등록한 모습이다.

    다음으로, song이라는 상수변수를 선언한 뒤 여기에 Name() 클래스의 인스턴스를 할당한다. (이를, 초기화 라고 한다)

    [인스턴스].[프로퍼티 or 메서드] 를 통해 이를 활용할 수 있으며, 위처럼 수정도 가능한 모습이다.

     

     

    1. 클래스 상속

    상속은 클래스의 재사용성과 확장성을 용이하게 해주는 기능이라고 할 수 있다. 쉽게 말해, 기존의 클래스를 이어받은 새로운 클래스를 정의할 수 있는 것이다.

    // Super Class
    class Vehicle {
      var bus = "bus"
      func ride() {
        print("ride \(bus)")
      }
    }
    
    // Inheritance
    class Car: Vehicle {
      var car = "toyota"
      func own() {
        print("ride my \(car), not \(bus)!")
      }
    }
    
    let myCar: Car = Car()
    
    print(myCar.bus)		// bus
    print(myCar.car)		// car
    
    myCar.ride()			// ride bus
    myCar.own()			// ride my toyota, not bus!

    class [클래스명]: [슈퍼 클래스명] { ... } 문법으로 특정 클래스에 다른 클래스를 상속시킬 수 있으며, 상속되는 클래스를 "슈퍼 클래스", 상속받은 클래스는 "서브 클래스" 라고 한다.

     

    서브클래스는, 슈퍼클래스의 프로퍼티와 메서드를 상속받아 마찬가지로 기능을 활용할 수 있는 모습이다.

     

    2. 클래스 초기화

    초기화를 하는 가장 큰 이유는, 인스턴스를 생성할 때마다 프로퍼티(인수)에 값을 동적으로 할당하고자 하는 목적이 크다.

    클래스는 내부적으로 init() 이라는 초기화 처리 전용 메서드를 가지고 있다.

    class Fruit {
      var name: String
      var count: Int
      
      init(name: String, count: Int) {		// 초기화
        self.name = name
        self.count = count
      }
      
      func whatFruit() {
        print("here are \(count) \(name)s")
      }
    }
    
    let fruit1: Fruit = Fruit(name: "apple", count: 2)
    let fruit2: Fruit = Fruit(name: "grape", count: 4)
    
    fruit1.whatFruit()		// here are 2 apples
    fruit2.whatFruit()		// here are 4 grapes

    이처럼, 클래스 내 init() 함수는 (변수명: 값...) 형태의 튜플로 특정 값들을 받아서 프로퍼티에 할당할 수 있다. 

    이 때, 프로퍼티에 접근하기 위해 self라는 키워드를 사용하면 된다. (Class 문법의 this와 매우 유사)

     

    Fruit() 라는 클래스를 통해 각각 초기화된 fruit1, fruit2 인스턴스들은 각기 다른 값들이 초기화되었고, 그렇기에 whatFruit() 메서드 출력값이 다르게 나타나는 것이다.

     

    3. Override(오버라이드)

    기본적으로 서브 클래스는 슈퍼 클래스의 기능(프로퍼티, 메서드)를 상속받는다.

    그런데, 서브 클래스에서 상황에 따라 슈퍼 클래스에 정의된 메서드를 수정해야 할 필요가 있을 수 있다.

    이를, Method Overrride(메서드 오버라이드) 라고 하며, override func [기존 메서드명] 문법으로 구현할 수 있다.

    class Helo { 
      var name:String = "Taro"; 
      func say() { 
        print("Hello, " + name + "!"); 
      } 
    } 
    
    class Hello:Helo { 
      var name2:String = "YAMADA"; 
      override func say() { 		// say() 메서드를 오버라이딩!
        print("Hi," + name + "-" + name2 + "!"); 
      } 
    }
    
    var obj:Hello = Hello(); 
    obj.say();		// Hi, Taro-YAMADA!
    
    obj.name = "Hanako"; 
    obj.name2 = "TANAKA"; 
    obj.say();		// Hi, Hanako-TANAKA!

    위는, 슈퍼 클래스인 Helo의 say() 메서드를, 서브 클래스인 Hello에서 오버라이딩 한 예시이다.

    오버라이딩 시 주의할 점은, 메서드명뿐만 아니라 매개변수(인자) 및 반환값의 형태가 동일해야 한다.

     

    4. 클래스 소멸 (Deinitializer)

    인스턴스는 클래스를 참조하므로 필요가 없을때는 메모리에서 해제된다. 이 과정을 Deinitialize(소멸)이라고 한다.

    소멸 직전에 deinit() 메서드를 내부적으로 호출하게 되며, 이는 클래스 내 유일하다는 특성이 있다.

    또, 매개변수와 이를 위한 괄호, 반환값이 없으며, 직접 deinit() 메서드를 호출할 수도 없다. (통상, print(문구) 활용)

    class Person {
        var height: Float = 0.0
        var weight: Float = 0.0
        deinit {
            print("Person 클래스의 인스턴스가 소멸됩니다.")
        }
    }
    var man: Person? = Person()
    man = nil // Person 클래스의 인스턴스가 소멸됩니다.

     

     

    📙 Struct (구조체)

    구조체는 클래스와 매우 유사하나, 각각의 장단점이 있기 때문에 둘을 구분해서 적절한 경우에 사용하는 게 중요하다.

    Struct(구조체)는 인스턴스 값(프로퍼티)과 기능(메서드)를 제공하고 캡슐화하기 위한 Swift 타입의 일종이다.

    클래스와 마찬가지로 인스턴스를 만들어서 활용하면 된다.  struct 키워드로 선언할 수 있으며, 기본적인 문법은 클래스와 동일하다.

    struct Car {
        var car = "BMW"
        
        func my_car() {
            print("my car is \(car)")
        }
    }
    
    let dreamCar : Car = Car()
    
    print(dreamCar.car)			// BMW
    dreamCar.my_car()			// my car is BMW
    
    dreamCar.car = "Benz"
    dreamCar.my_car()			// my name is Benz

    클래스 예시였던 Car를 struct 키워드로만 수정해서 활용한 부분이다.

     

     

    1. 구조체 초기화 (자동)

    이제 클래스와 구분되는 점을 알아보자!

    가장 먼저, 클래스는 init() 메서드로 초기화했다면, 구조체는 인스턴스 생성시에 값을 넣어주면 자동으로 초기화를 한다.

    struct Car {
        var car: String
        
        func my_car() {
            print("my car is \(car)")
        }
    }
    
    let dreamCar : Car = Car(car: "BMW")
    
    print(dreamCar.car)			// BMW
    dreamCar.my_car()			// my car is BMW

     

    struct 내부에도 init() 메서드로 초기화를 할 수 있지만, 상수값으로 초기화된 경우 아래 예시처럼 변경이 불가능해진다.

    struct Car {
        var car: String
        var count: Int
        
        func have_car() {
            print("I have \(count) \(car)s")
        }
        
        init(car: String, count: Int) {
          self.car = car
          self.count = 3
        }
    }
    
    let dreamCar : Car = Car(car: "TOYOTA", count: 5)	// count 반영안됨
    dreamCar.have_car()			// I have 3 TOYOTAs

     

    2. 구조체 상속 불가

    클래스와 달리 구조체는 상속할 수 없다.

     

     

    📙 클래스와 구조체의 공통점 / 차이점

    위에서 봤듯이, 클래스와 구조체는 문법이 비슷하면서도, 몇몇 부분들에서 차이가 있다.

    가장 큰 차이는, Struct는 새로운 값 타입이며, Class는 참조타입이라는 컨셉의 차이를 우선적으로 각인하길 바란다.

    이를 확실히 정리해서, 어떤 경우에 어느 타입을 쓰는 것이 더 적절한지 구분할 필요성을 느꼈기에 이를 정리해보았다!

     

    - 공통점

    • 복수의 변수(프로퍼티)와 함수(메서드)를 담을 수 있는 하나의 집합 타입. 용도에 맞는 데이터들을 묶은 새로운 데이터 타입.
    • 키워드(struct, class) 문법으로 인스턴스를 생성 및 초기화하며, 점(.) 문법으로 프로퍼티와 메서드를 활용할 수 있다. (subscript)
    • 내부의 init() 메서드가 있어, 여기서 초기화에 대한 설정이 가능하다.
    • Protocol(Swift 타입 내부규정)과 Extension(복수의 프로토콜)의 사용이 가능하다.

     

    - 차이점

    구분 Struct(구조체) Class(클래스)
    타입 값 타입(call by value),
    각 인스턴스가 독립적
    참조 타입(call by reference),
    객체의 메모리 주소만 복사하며 수정이 모든 인스턴스에 영향
    메모리 Stack 저장(LIFO), 복사된 값을 참조하므로 빠름 Heap 저장(FIFO), 참조 객체를 참조하므로 효율적이지만 느림
    초기화 자동 적용 init() 메서드로만 초기화
    상속 불가능 가능 (class [서브 클래스]: [슈퍼 클래스] { ... })
    메모리 해제 디이니셜라이저 없음 deinit() 메서드로 디이니셜라이징 / 메모리 할당 해제
    참조 횟수 참조 횟수 저장하지 않음 ARC(Auto Reference Counting) 참조 카운트 저장 및 0이 되면 자동 메모리 해제

    구조체는 값 타입이기에 독립성이 있으며 속도가 빠르지만, 클래스는 참조 타입으로 메모리 효율성이 우수하다.

    이러한 차이점으로 어느 경우에 어떤 타입을 적용하는게 더 적절한지 정리해보았다.

    * ARC와 이전 Objective-C의 관리방식인 MRC에 대해 잘 정리된 링크도 추천한다!

     

     

    Struct가 더 좋은 경우? (애플 가이드라인)

    • 기본적으로 Struct가 권장된다. Swift의 기본적인 데이터 자료형들은 Struct 이기도 하다. 자료형들의 캡슐화가 목적이라면 충분.
    • 멀티 쓰레드(Multi-Thread) 환경에 유리. 타입을 참조하는 것보다 복사하는 것이 적절한 경우.
    • 구조체 내 프로퍼티가 값 타입이며, 이 값들이 참조되는 것보다 복사되는 것이 적절한 경우.
    • 타입 간의 상속관계가 필요하지 않은 경우.

     

    Class가 더 좋은 경우?

    사실 위의 경우가 아니라면 Class를 사용하는 것이 좋을 수 있다.

    참조 타입을 통해 다양한 인스턴스가 중앙 프로퍼티 값을 공유할 수 있으며, 상속을 통한 유연한 확장이 가능하다.

    무엇보다, deinit() 혹은 참조 횟수 관리를 통한 효율적인 메모리 활용이 가능한 것이 클래스의 장점이리라.


    이번 주제가 생각보다 생각보다 오래걸렸다. 사실, Struct와 Class의 사용법 자체는 크게 차이가 나지 않기에 2배 미만의 노력이 들었다.

    강조해야 할 부분은 역시 구조체는 값 타입, 클래스는 참조 타입이기 때문에 각각 독립성 / 효율성의 장점을 가지는 것이다.

    또한, JS에서 모든 자료형은 객체 타입이었던 것처럼, Swift는 모든 자료형의 타입을 struct로 정의하고 있다.

    public struct String {
        /// An empty 'String'.
        public init()
    }

     

    다음 포스팅은, 클래스 및 구조체 내 메서드에서 볼 수 있었던 함수(func)에 대해 정리해보겠다!

     

     

    📎 출처

    - [Swift5 강의] iOS Academy(Part 5 - Classes & Structs) : https://www.youtube.com/watch?v=48v8FH46mQs&list=PL5PR3UyfTWvfacnfUsvNcxIiKIgidNRoW&index=5 

     

    - [클래스, 구조체] Dev_pingu 님의 블로그 : https://icksw.tistory.com/256

    - [클래스, 구조체] 야곰 님의 블로그 : https://blog.yagom.net/530/  

    - [클래스] 프린스송 님의 블로그 : https://velog.io/@wook4506/iOS-Swift-Swift-%EB%AC%B8%EB%B2%95%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-7%ED%8E%B8-%ED%81%B4%EB%9E%98%EC%8A%A4-Class  

    - [구조체] 프린스송 님의 블로그 : https://velog.io/@wook4506/iOS-Swift-Swift-%EB%AC%B8%EB%B2%95%EC%9D%84-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-8%ED%8E%B8-%EA%B5%AC%EC%A1%B0%EC%B2%B4-Struct  

     

    - [클래스 vs 구조체] letmecompile 블로그 : https://www.letmecompile.com/swift-struct-vs-class-%EC%B0%A8%EC%9D%B4%EC%A0%90-%EB%B9%84%EA%B5%90-%EB%B6%84%EC%84%9D/
    - [클래스 vs 구조체] woongsios 님의 블로그 : https://woongsios.tistory.com/46  

    반응형
Designed by Tistory.