Skip to content

Latest commit

 

History

History
executable file
·
189 lines (122 loc) · 8.03 KB

[스위프트 대충보기] 8. 클래스(class)와 구조체(structure).md

File metadata and controls

executable file
·
189 lines (122 loc) · 8.03 KB
Error in user YAML: (<unknown>): did not find expected key while parsing a block mapping at line 1 column 1
---
layout: post
title: [스위프트 대충보기] 8. 클래스(class)와 구조체(structure)
tags: 스위프트, swift, 클래스, 구조체, class, struct
---

[스위프트 대충보기] 8. 클래스(class)와 구조체(structure)

  • 클래스/구조체: 프로그램을 구축할 때 범용으로 유연하게 사용할 수 있는 프로그램 구성 요소

클래스와 구조체 공통점

  • 안에 값을 저정할 프로퍼티(property)를 정의할 수 있다

  • 안에 메서드를 정의할 수 있다

  • []를 사용해 첨자(subscript) 문법으로 내부의 값을 액세스할 수 있는 첨자를 정의할 수 있다

  • 초기 상태 설정을 위한 초기화 함수(initializer)를 정의할 수 있다

  • 클래스/구조체 구현을 확장할 수 있다

  • 어떤 조건의 기능을 제공하기 위한 프로토콜을 (conform) 만족시킬(conform) 수 있다

  • 클래스만 더 가지는 특징

    • 상속을 통해 다른 클래스의 특성을 이어받을 수 있다

    • 타입 캐스팅을 통해 실행 시점에 클래스 인스턴스의 타입을 해석하고 검사할 수 있다

    • 해제함수(deinitializer)를 사용해 사용한 자원을 반환할 수 있다

    • 참조 카운팅을 통해 한 클래스 인스턴스를 여러 군데에서 참조(사용)할 수 있다

클래스/구조체 정의하기

  • 클래스 정의하기: class 이름 { ... }

  • 구조체 정의하기: struct 이름 { ... }

  • 클래스/구조체를 정의하면 새로운 타입을 정의하는 것이다

  • 관습적으로 타입 이름에는 대문자 낙타등표기법(UpperCamelCase)를 사용한다. 클래스/구조체 안의 프로퍼티나 메서드는 소문자 낙타등표기법(lowerCamelCase)를 사용한다.

          struct Resolution {
              var width = 0
              var height = 0
          }
          class VideoMode {
              var resolution = Resolution()
              var interlaced = false
              var frameRate = 0.0
              var name: String?
          }
    

인스턴스 생성 및 프로퍼티 액세스

  • 인스턴스 생성: 타입이름() 형태로 생성한다

      	let someResolution = Resolution()
          let someVideoMode = VideoMode()
    
  • 프로퍼티 액세스: 인스턴스이름.프로퍼티이름

      	println("resolution=\(someResolution.width)")
      	someVideoMode.resolution.width = 1280
      	println("The width of someVideoMode is now \(someVideoMode.resolution.width)")
    
  • 구조체의 경우 디폴트 초기화 함수로 멤버별 초기화(memberwise initializer)를 자동 제공한다. 클래스의 경우 그런거 없다.

      	let vga = Resolution(width: 640, height: 480)
    
  • 구조체나 열겨형은 값 타입(value type)이다. 값 타입은 호출, 대입 등이 일어날 때 복사를 한다는 뜻이다.

      let hd = Resolution(width: 1920, height: 1080)
      var cinema = hd
    
    • 위 예에서, cinema는 hd와 같은 인스턴스를 가리키는 것이 아니고, 내용을 복사한 새 인스턴스를 만들어 가리킨다. 따라서 cinema의 프로퍼티를 변경해도 hd에는 영향을 끼치지 못한다.
  • 클래스는 참조 타입(reference type)이다. 따라서, 대입시 참조(주소라고 생각하면 됨)가 복사된다.

      let tenEighty = VideoMode()
      tenEighty.resolution = hd
      tenEighty.interlaced = true
      tenEighty.name = "1080i"
      tenEighty.frameRate = 25.0
      // alsoTenEighty는 tenEighty랑 같은 인스턴스를 가리킨다
      let alsoTenEighty = tenEighty
      alsoTenEighty.frameRate = 30.0
      // 따라서 tenEighty의 프로퍼티도 값이 30.0으로 바뀐다
      println("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
    

식별 연산

  • 식별(identity) 연산: 두 변수가 같은 인스턴스를 가리키는지 비교하는 연산

    • ===, !===
  • 두 변수/상수가 동일한 것(identity)과 같은 것(equality)은 다르다. 동일한 것은 대상 객체(참조 대상 또는 번지)가 같다는 뜻이고, 같은 것은 의미적으로 둘이 같다는 뜻이다.

언제 사용할 것인가?

  • 언제 클래스를 쓰고 언제 구조체를 쓰나?

    • 기본적으로, 클래스는 참조타입, 구조체는 값 타입이란 것에 착안할 것

      • 구조체는 간단한 데이터 값들을 한데 묶어서 사용하는 경우를 주로 다룬다.

      • 전체 덩어리 크기가 작은 경우, 복사를 통해 전달해도 좋은 경우 구조체를 쓴다.

    • 구조체는 기존 타입의 특성을 상속할 필요가 없다.

  • 구조체를 쓰는게 좋은 경우(예제): "The C++ 프로그래밍 언어"의 10장 3절(3판기준)에서 이야기하듯, 어떤 프로그래밍 언어의 원시 타입(primitive type)과 다름없이 사용 가능한 작은 구체적 타입(small concrete type)을 목적으로 할 때 사용하면 좋음(물론 상속할 필요가 없는 경우에 구조체를 사용)

    • 너비, 높이를 표현하는 기하학적 모양을 처리할 경우

    • 시작값, 증분, 길이 등으로 순열을 표현할 경우

    • 3차원 좌표 시스템의 각 좌표

컬렉션 타입의 대입과 복사

  • Array와 Dictionary는 구조체임

  • 하지만 배열은 딕셔너리나 다른 구조체와 다름

  • 또한, Array와 NSArray, NSDictionary도 다름. NSArray, NSDictionary는 참조 타입임

  • 의미상으로는 복사연산이지만 스위프트는 내부적으로는 복사가 꼭 필요할 때만 복사를 수행함(사족: copy on write등을 사용?)

  • 딕셔너리 복사:

    • 딕셔너리를 변수나 상수에 대입하거나 인자로 함수/메서드에 넘기면 그 딕셔너리를 복사함. 키/값들도 복사함. 물론 키나 값이 참조타입이면 참조만 복사함.

        var ages = ["Peter": 23, "Wei": 35, "Anish": 65, "Katya": 19]
        var copiedAges = ages
        copiedAges["Peter"] = 24
        println(ages["Peter"]) // "23" 출력
      
  • 배열 복사: 특이함!

    • 기존 배열을 다른 변수나 상수에 대입하면 참조를 복사하는 셈임. 한쪽의 변경사항(원소 변경)을 다른 참조에서도 관찰 가능

    • 배열의 길이를 바꾸는 연산을 수행할 때 배열을 두 벌로 분리함. 즉, 원소를 삭제하거나, 일부분을 치환하거나 하면 그때 복사가 일어남. 이때는 딕셔너리와 마찬가지로 원소가 값 타입이면 원소를 전체 복사, 원소가 참조 타입이면 참조를 복사함

        var a = [1, 2, 3]
        var b = a
        var c = a
        println(a[0]) // 1
        println(b[0]) // 1
        println(c[0]) // 1
        a[0] = 42
        println(a[0]) // 42
        println(b[0]) // 42
        println(c[0]) // 42
        a.append(4)
        a[0] = 777
        println(a[0]) // 777
        println(b[0]) // 42
        println(c[0]) // 42
      
  • 별도의 배열 복사본을 확보한 다음 무언가를 실행하고 싶은 경우가 있을 것임. 그럴때는 unshare()를 사용

      	// 위의 배열 예제에서 이어서...
          b.unshare()
          b[0] = -105
          println(a[0]) // 777
          println(b[0]) // -105
          println(c[0]) // 42
    
  • 배열 비교시 ===를 사용하면 두 배열이 메모리상에서 동일한 인스턴스인지를 비교

  • 원소가 같은지 알고 싶다면, 첨자에 ...나 ..를 써서 부분배열을 비교 하면 됨

          if b[0...1] === b[0...1] {
              println("These two subarrays share the same elements.")
          } else {
              println("These two subarrays do not share the same elements.")
          }
    
  • 강제복사: copy() 메서드 사용. 얕은 복사(shallow copy)를 수행함.

          var names = ["Mohsen", "Hilary", "Justyn", "Amy", "Rich", "Graham", "Vic"]
          var copiedNames = names.copy()
          copiedNames[0] = "Mo"
          println(names[0]) // names와 copiedNames는 무관하기 때문에, Mohsen을 출력
    
  • copy()는 무조건 복사를 수행하고, unshare()는 꼭 필요할 때까지 최대한 복사를 미룬다.