[swift] 왜 수업보다 구조를 선택해야합니까?

Java 배경에서 나온 Swift를 가지고 놀면서 왜 Class 대신 Struct를 선택하고 싶습니까? 더 적은 기능을 제공하는 Struct와 동일한 기능인 것 같습니다. 왜 그것을 선택해야합니까?



답변

Swift에서 널리 사용되는 WWDC 2015 토크 프로토콜 지향 프로그래밍 ( video , transcript )에 따르면 Swift는 여러 상황에서 클래스보다 구조를 개선하는 여러 기능을 제공합니다.

클래스에서와 같이 동일한 인스턴스에 대한 여러 참조를 갖는 것보다 복사가 훨씬 안전하기 때문에 Structs가 비교적 작고 복사 가능한 경우 선호됩니다. 변수를 여러 클래스 및 / 또는 멀티 스레드 환경으로 전달할 때 특히 중요합니다. 변수 사본을 항상 다른 장소로 보낼 수 있다면 다른 장소가 변수 값을 변경하는 것에 대해 걱정할 필요가 없습니다.

Structs를 사용하면 변수의 단일 인스턴스에 액세스 / 수정하기 위해 메모리 누수 또는 여러 스레드 경주에 대해 걱정할 필요가 없습니다. (기술적으로 생각하면 클로저 내부의 구조체를 캡처 할 때 예외적으로 명시 적으로 복사하도록 표시하지 않으면 인스턴스에 대한 참조를 캡처하기 때문에 예외입니다.)

클래스는 단일 수퍼 클래스에서만 상속 할 수 있기 때문에 클래스가 부풀어 질 수 있습니다. 그것은 우리가 느슨하게 관련된 많은 다른 능력들을 포함하는 거대한 수퍼 클래스를 만들도록 권장합니다. 특히 프로토콜에 구현을 제공 할 수있는 프로토콜 확장과 함께 프로토콜을 사용하면 이러한 종류의 동작을 달성하기 위해 클래스가 필요하지 않습니다.

이 강의는 수업이 선호되는 다음 시나리오를 설명합니다.

  • 인스턴스 복사 또는 비교가 의미가 없습니다 (예 : Window)
  • 인스턴스 수명은 외부 효과 (예 : TemporaryFile)와 연결되어 있습니다.
  • 인스턴스는 “싱크”일뿐입니다. 쓰기 전용 도관을 외부 상태 (예 : CGContext)로

구조체가 기본값이어야하고 클래스는 폴백이어야 함을 의미합니다.

반면에 Swift Programming Language 문서는 다소 모순됩니다.

구조 인스턴스는 항상 값으로 전달되고 클래스 인스턴스는 항상 참조로 전달됩니다. 이것은 그들이 다른 종류의 작업에 적합하다는 것을 의미합니다. 프로젝트에 필요한 데이터 구조 및 기능을 고려할 때 각 데이터 구조를 클래스 또는 구조로 정의해야하는지 결정하십시오.

일반적인 지침으로 다음 조건 중 하나 이상이 적용될 때 구조를 만드는 것이 좋습니다.

  • 구조의 주요 목적은 비교적 간단한 몇 가지 데이터 값을 캡슐화하는 것입니다.
  • 해당 구조의 인스턴스를 할당하거나 전달할 때 캡슐화 된 값이 참조되지 않고 복사 될 것으로 예상하는 것이 합리적입니다.
  • 구조에 의해 저장된 모든 속성은 자체적으로 값 형식이며 참조가 아닌 복사 될 것으로 예상됩니다.
  • 구조는 기존의 다른 유형에서 속성이나 동작을 상속 할 필요가 없습니다.

구조에 적합한 후보의 예는 다음과 같습니다.

  • 기하학적 유형의 크기로, Double 유형의 너비 속성과 높이 속성을 캡슐화 할 수 있습니다.
  • Int 유형의 시작 속성과 길이 속성을 캡슐화하여 계열 내의 범위를 참조하는 방법입니다.
  • 3D 좌표 시스템의 점으로, 각각 Double 유형의 x, y 및 z 속성을 캡슐화 할 수 있습니다.

다른 모든 경우에는 클래스를 정의하고 참조로 관리 및 전달할 해당 클래스의 인스턴스를 작성하십시오. 실제로 이것은 대부분의 사용자 정의 데이터 구조가 구조가 아닌 클래스 여야 함을 의미합니다.

여기서 우리는 기본적으로 클래스를 사용하고 특정 상황에서만 구조를 사용해야한다고 주장합니다. 궁극적으로 가치 유형과 참조 유형의 실제 의미를 이해해야하며, 언제 구조체 또는 클래스를 사용할 지에 대한 정보에 근거한 결정을 내릴 수 있습니다. 또한 이러한 개념은 항상 발전하고 있으며 Swift Programming Language 문서는 Protocol Oriented Programming 강연 전에 작성되었습니다.


답변

구조체 인스턴스는 스택에 할당되고 클래스 인스턴스는 힙에 할당되므로 구조체가 때로는 더 빠를 수 있습니다.

그러나 항상 직접 측정하고 고유 한 사용 사례에 따라 결정해야합니다.

and를 Int사용하여 데이터 유형 을 래핑하는 두 가지 전략을 보여주는 다음 예를 고려하십시오 . 나는 여러 개의 필드가있는 실제 세계를 더 잘 반영하기 위해 10 개의 반복 값을 사용하고 있습니다.structclass

class Int10Class {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

struct Int10Struct {
    let value1, value2, value3, value4, value5, value6, value7, value8, value9, value10: Int
    init(_ val: Int) {
        self.value1 = val
        self.value2 = val
        self.value3 = val
        self.value4 = val
        self.value5 = val
        self.value6 = val
        self.value7 = val
        self.value8 = val
        self.value9 = val
        self.value10 = val
    }
}

func + (x: Int10Class, y: Int10Class) -> Int10Class {
    return IntClass(x.value + y.value)
}

func + (x: Int10Struct, y: Int10Struct) -> Int10Struct {
    return IntStruct(x.value + y.value)
}

성능은 다음을 사용하여 측정됩니다.

// Measure Int10Class
measure("class (10 fields)") {
    var x = Int10Class(0)
    for _ in 1...10000000 {
        x = x + Int10Class(1)
    }
}

// Measure Int10Struct
measure("struct (10 fields)") {
    var y = Int10Struct(0)
    for _ in 1...10000000 {
        y = y + Int10Struct(1)
    }
}

func measure(name: String, @noescape block: () -> ()) {
    let t0 = CACurrentMediaTime()

    block()

    let dt = CACurrentMediaTime() - t0
    print("\(name) -> \(dt)")
}

코드는 https://github.com/knguyen2708/StructVsClassPerformance 에서 찾을 수 있습니다.

업데이트 (2018 년 3 월 27 일) :

Swift 4.0, Xcode 9.2에서 iPhone 6S, iOS 11.2.6에서 릴리스 빌드를 실행하면 Swift Compiler 설정은 -O -whole-module-optimization다음과 같습니다.

  • class 버전은 2.06 초 걸렸다
  • struct 버전은 4.17e-08 초가 걸렸습니다 (50,000,000 배 더 빠름)

(분산이 5 % 미만으로 매우 작기 때문에 더 이상 여러 번의 런을 수행하지 않습니다)

참고 : 전체 모듈 최적화가 없으면 차이가 훨씬 덜 극적입니다. 누군가가 깃발이 실제로하는 일을 지적 할 수 있다면 기쁠 것입니다.


업데이트 (2016 년 5 월 7 일) :

Swift 2.2.1, Xcode 7.3에서 iPhone 6S, iOS 9.3.1에서 릴리스 빌드를 실행하면 평균 5 회 이상 실행되며 Swift Compiler 설정은 -O -whole-module-optimization다음과 같습니다.

  • class 버전은 2.159942142s 걸렸다
  • struct 버전은 5.83E-08s (37,000,000 배 더 빠름)

참고 : 누군가가 실제 시나리오에서 구조체에 필드가 두 개 이상있을 것이라고 언급 했으므로 1 대신 10 필드로 구조체 / 클래스에 대한 테스트를 추가했습니다. 놀랍게도 결과는 크게 다르지 않습니다.


원래 결과 (2014 년 6 월 1 일) :

(10이 아닌 1 필드의 구조체 / 클래스에 의존)

Swift 1.2부터 Xcode 6.3.2, iPhone 5S, iOS 8.3에서 릴리스 빌드 실행 중 평균 5 회 이상 실행

  • class 버전 9.788332333s 소요
  • struct 버전은 0.010532942 초 (900 배 빨라짐)

이전 결과 (알 수없는 시간부터)

(10이 아닌 1 필드의 구조체 / 클래스에 의존)

MacBook Pro에 릴리스 빌드가있는 경우 :

  • class버전은 1.10082 초 걸렸다
  • struct버전 (50 배 빠른) 0.02324 초를했다

답변

구조체와 클래스의 유사점

간단한 예제로 이것에 대한 요점을 만들었습니다.
https://github.com/objc-swift/swift-classes-vs-structures

그리고 차이점

1. 상속.

구조는 신속하게 상속받을 수 없습니다. 네가 원한다면

class Vehicle{
}

class Car : Vehicle{
}

수업에 가십시오.

2. 통과

스위프트 구조는 값으로 전달되고 클래스 인스턴스는 참조로 전달됩니다.

상황에 따른 차이

구조 상수 및 변수

예 (WWDC 2014에서 사용)

struct Point{

   var x = 0.0;
   var y = 0.0;

} 

Point라는 구조체를 정의합니다.

var point = Point(x:0.0,y:2.0)

이제 x를 변경하려고하면 유효한 표현입니다.

point.x = 5

그러나 점을 상수로 정의하면.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

이 경우 전체 포인트는 불변 상수입니다.

클래스 클래스를 대신 사용하면 유효한 표현식입니다. 클래스 불변 상수는 인스턴스 변수가 아닌 클래스 자체에 대한 참조이기 때문에 (변수가 상수로 정의되지 않은 경우)


답변

고려해야 할 다른 이유는 다음과 같습니다.

  1. 구조체는 코드에서 전혀 유지 관리 할 필요가없는 자동 이니셜 라이저를 얻습니다.

    struct MorphProperty {
       var type : MorphPropertyValueType
       var key : String
       var value : AnyObject
    
       enum MorphPropertyValueType {
           case String, Int, Double
       }
     }
    
     var m = MorphProperty(type: .Int, key: "what", value: "blah")

클래스에서 이것을 얻으려면 초기화 프로그램을 추가하고 초기화 기를 유지해야 합니다 …

  1. 기본 컬렉션 유형 Array은 구조체입니다. 자신의 코드에서 더 많이 사용할수록 참조가 아닌 값으로 전달하는 데 더 익숙해집니다. 예를 들어 :

    func removeLast(var array:[String]) {
       array.removeLast()
       println(array) // [one, two]
    }
    
    var someArray = ["one", "two", "three"]
    removeLast(someArray)
    println(someArray) // [one, two, three]
  2. 명백하게 불변성 vs. 가변성은 큰 주제이지만, 많은 똑똑한 사람들은 불변성 (이 경우 구조체)이 바람직하다고 생각합니다. 가변 대 불변 개체


답변

우리가 Struct가치 유형 이고 Class참조 유형 이라고 가정하면 .

값 유형과 참조 유형이 무엇인지 모르는 경우 참조 별 전달과 값별 전달의 차이점무엇입니까? 를 참조하십시오 .

mikeash의 게시물을 기반으로 :

… 극단적이고 명백한 예를 먼저 살펴 보겠습니다. 정수는 분명히 복사 가능합니다. 값 유형이어야합니다. 네트워크 소켓은 현명하게 복사 할 수 없습니다. 참조 유형이어야합니다. x, y 쌍과 같이 포인트를 복사 할 수 있습니다. 값 유형이어야합니다. 디스크를 나타내는 컨트롤러는 현명하게 복사 할 수 없습니다. 이것이 참조 유형이어야합니다.

일부 유형은 복사 할 수 있지만 항상 발생하지 않을 수도 있습니다. 이는 참조 유형이어야 함을 나타냅니다. 예를 들어, 화면의 버튼을 개념적으로 복사 할 수 있습니다. 사본은 원본과 동일하지 않습니다. 사본을 클릭하면 원본이 활성화되지 않습니다. 사본은 화면에서 동일한 위치를 차지하지 않습니다. 버튼을 전달하거나 새 변수에 넣으면 원래 버튼을 참조하고 명시 적으로 요청했을 때만 사본을 만들고 싶을 것입니다. 즉, 버튼 유형은 참조 유형이어야합니다.

보기 및 창 컨트롤러도 비슷한 예입니다. 아마도 복사가 가능할 수도 있지만, 당신이하고 싶은 일은 거의 없습니다. 참조 유형이어야합니다.

모델 유형은 어떻습니까? 시스템의 사용자를 나타내는 사용자 유형 또는 사용자가 수행 한 조치를 나타내는 범죄 유형이있을 수 있습니다. 이것들은 꽤 복사 가능하기 때문에 아마도 가치 유형이어야합니다. 그러나 프로그램의 한 위치에서 작성된 사용자 범죄에 대한 업데이트가 프로그램의 다른 부분에 표시되도록 할 수 있습니다.
이것은 사용자가 일종의 사용자 컨트롤러로 참조 유형을 관리해야 함을 나타냅니다. 냅니다. 예 :

struct User {}
class UserController {
    var users: [User]

    func add(user: User) { ... }
    func remove(userNamed: String) { ... }
    func ...
}

컬렉션은 흥미로운 경우입니다. 여기에는 문자열뿐만 아니라 배열 및 사전과 같은 것들이 포함됩니다. 복사 할 수 있습니까? 명백하게. 원하는 일을 쉽고 자주 복사하고 있습니까? 덜 명확합니다.

대부분의 언어는 이에 대해 “아니오”라고 말하고 컬렉션을 참조 유형으로 만듭니다. Objective-C 및 Java, Python 및 JavaScript 및 거의 모든 다른 언어에서 생각할 수 있습니다. (STL 콜렉션 유형의 C ++은 한 가지 주요 예외이지만, C ++은 모든 것을 이상하게 수행하는 언어 세계의 미치광이입니다.)

스위프트는 “예”라고 말했다. 이는 Array와 Dictionary, String과 같은 타입이 클래스가 아닌 구조체라는 것을 의미한다. 그들은 할당 및 매개 변수로 전달시 복사됩니다. 사본이 싸기 만하면 Swift가 달성하기 위해 매우 열심히 노력하는 것이 전적으로 합리적인 선택입니다. …

나는 개인적으로 그런 수업을하지 않습니다. 나는 보통 UserController 대신 내 UserManager 를 명명 하지만 아이디어는 동일합니다.

또한 함수의 모든 인스턴스를 재정의해야 할 때 클래스를 사용하지 마십시오. 공유 기능 .

따라서 클래스의 여러 하위 클래스를 갖는 대신. 프로토콜에 맞는 여러 구조체를 사용하십시오.


구조체에 대한 또 다른 합리적인 사례는 이전 모델과 새 모델의 델타 / 차이를 원할 때입니다. 참조 유형을 사용하면 즉시 사용할 수 없습니다. 값 유형을 사용하면 돌연변이가 공유되지 않습니다.


답변

몇 가지 장점 :

  • 공유 할 수 없어서 자동으로 스레드 세이프
  • isa 및 refcount가 없기 때문에 적은 메모리를 사용합니다 (실제로 스택이 일반적으로 할당 됨)
  • 메소드는 항상 정적으로 전달되므로 인라인 될 수 있습니다 (@final은 클래스에 대해이 작업을 수행 할 수 있지만)
  • 스레드 안전성과 같은 이유로 추론하기 쉬움

답변

구조는 클래스보다 훨씬 빠릅니다. 또한 상속이 필요한 경우 클래스를 사용해야합니다. 가장 중요한 점은 클래스는 참조 유형이고 구조는 값 유형입니다. 예를 들어

class Flight {
    var id:Int?
    var description:String?
    var destination:String?
    var airlines:String?
    init(){
        id = 100
        description = "first ever flight of Virgin Airlines"
        destination = "london"
        airlines = "Virgin Airlines"
    }
}

struct Flight2 {
    var id:Int
    var description:String
    var destination:String
    var airlines:String
}

이제 둘 다의 인스턴스를 만들 수 있습니다.

var flightA = Flight()

var flightB = Flight2.init(id: 100, description:"first ever flight of Virgin Airlines", destination:"london" , airlines:"Virgin Airlines" )

이제이 인스턴스를 id, description, destination 등을 수정하는 두 함수에 전달할 수 있습니다.

func modifyFlight(flight:Flight) -> Void {
    flight.id = 200
    flight.description = "second flight of Virgin Airlines"
    flight.destination = "new york"
    flight.airlines = "Virgin Airlines"
}

또한,

func modifyFlight2(flight2: Flight2) -> Void {
    var passedFlight = flight2
    passedFlight.id = 200
    passedFlight.description = "second flight from virgin airlines"
}

그래서,

modifyFlight(flight: flightA)
modifyFlight2(flight2: flightB)

이제 flightA의 ID와 설명을 인쇄하면

id = 200
description = "second flight of Virgin Airlines"

여기서, modify 메소드에 전달 된 매개 변수가 실제로 flightA 객체 (참조 유형)의 메모리 주소를 가리 키기 때문에 FlightA의 id와 설명이 변경되었음을 알 수 있습니다.

이제 FLightB 인스턴스의 ID와 설명을 인쇄하면

id = 100
description = "first ever flight of Virgin Airlines"

여기서는 modifyFlight2 메소드에서 Flight2의 실제 인스턴스가 참조 (값 유형)가 아닌 전달이기 때문에 FlightB 인스턴스가 변경되지 않았 음을 알 수 있습니다.