Java 배경에서 나온 Swift를 가지고 놀면서 왜 Class 대신 Struct를 선택하고 싶습니까? 더 적은 기능을 제공하는 Struct와 동일한 기능인 것 같습니다. 왜 그것을 선택해야합니까?
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 강연 전에 작성되었습니다.
In practice, this means that most custom data constructs should be classes, not structures.
읽은 후에 대부분의 데이터 세트가 클래스가 아닌 구조가되는 방법을 설명해 줄 수 있습니까? 그들은 무언가가 구조체가되어야 할 때 특정한 규칙을 제시했고 "클래스가 더 나은 다른 모든 시나리오"라고 거의 말 했어요.
구조체 인스턴스는 스택에 할당되고 클래스 인스턴스는 힙에 할당되므로 구조체가 때로는 더 빠를 수 있습니다.
그러나 항상 직접 측정하고 고유 한 사용 사례에 따라 결정해야합니다.
and를 Int
사용하여 데이터 유형 을 래핑하는 두 가지 전략을 보여주는 다음 예를 고려하십시오 . 나는 여러 개의 필드가있는 실제 세계를 더 잘 반영하기 위해 10 개의 반복 값을 사용하고 있습니다.struct
class
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
구조는 신속하게 상속받을 수 없습니다. 네가 원한다면
class Vehicle{
}
class Car : Vehicle{
}
수업에 가십시오.
스위프트 구조는 값으로 전달되고 클래스 인스턴스는 참조로 전달됩니다.
구조 상수 및 변수
예 (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.
이 경우 전체 포인트는 불변 상수입니다.
클래스 클래스를 대신 사용하면 유효한 표현식입니다. 클래스 불변 상수는 인스턴스 변수가 아닌 클래스 자체에 대한 참조이기 때문에 (변수가 상수로 정의되지 않은 경우)
고려해야 할 다른 이유는 다음과 같습니다.
구조체는 코드에서 전혀 유지 관리 할 필요가없는 자동 이니셜 라이저를 얻습니다.
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")
클래스에서 이것을 얻으려면 초기화 프로그램을 추가하고 초기화 기를 유지해야 합니다 ...
기본 컬렉션 유형 Array
은 구조체입니다. 자신의 코드에서 더 많이 사용할수록 참조가 아닌 값으로 전달하는 데 더 익숙해집니다. 예를 들어 :
func removeLast(var array:[String]) {
array.removeLast()
println(array) // [one, two]
}
var someArray = ["one", "two", "three"]
removeLast(someArray)
println(someArray) // [one, two, three]
명백하게 불변성 vs. 가변성은 큰 주제이지만, 많은 똑똑한 사람들은 불변성 (이 경우 구조체)이 바람직하다고 생각합니다. 가변 대 불변 개체
internal
범위 밖에서 사용할 수 있으려면 실제로 초기자를 직접 작성해야합니다 .
mutating
어떤 함수가 상태를 변경하는지에 대해 명시 적으로 표시해야합니다 . 그러나 가치 유형 으로서의 본질 이 중요합니다. 구조체를 선언하면 let
변형 함수를 호출 할 수 없습니다. 가치 유형을 통한 더 나은 프로그래밍 에 대한 WWDC 15 비디오 는 이에 대한 훌륭한 리소스입니다.
우리가 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 를 명명 하지만 아이디어는 동일합니다.
또한 함수의 모든 인스턴스를 재정의해야 할 때 클래스를 사용하지 마십시오. 공유 기능 .
따라서 클래스의 여러 하위 클래스를 갖는 대신. 프로토콜에 맞는 여러 구조체를 사용하십시오.
구조체에 대한 또 다른 합리적인 사례는 이전 모델과 새 모델의 델타 / 차이를 원할 때입니다. 참조 유형을 사용하면 즉시 사용할 수 없습니다. 값 유형을 사용하면 돌연변이가 공유되지 않습니다.
몇 가지 장점 :
구조는 클래스보다 훨씬 빠릅니다. 또한 상속이 필요한 경우 클래스를 사용해야합니다. 가장 중요한 점은 클래스는 참조 유형이고 구조는 값 유형입니다. 예를 들어
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 인스턴스가 변경되지 않았 음을 알 수 있습니다.
Here we can see that the FlightB instance is not changed
Structs
있습니다 value type
및 Classes
입니다reference type
다음과 value
같은 경우 유형을 사용하십시오 .
다음과 reference
같은 경우 유형을 사용하십시오 .
추가 정보는 Apple 문서에서도 찾을 수 있습니다
https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html
추가 정보
신속한 가치 유형은 스택에 유지됩니다. 프로세스에서 각 스레드에는 고유 한 스택 공간이 있으므로 다른 스레드는 값 유형에 직접 액세스 할 수 없습니다. 따라서 경쟁 조건, 잠금, 교착 상태 또는 관련 스레드 동기화 복잡성이 없습니다.
값 유형은 동적 메모리 할당 또는 참조 카운팅이 필요하지 않으며 둘 다 비싼 작업입니다. 동시에 값 형식의 메서드는 정적으로 전달됩니다. 이는 성능 측면에서 가치 유형에 유리하게 큰 이점을 제공합니다.
여기에 스위프트 목록이 있습니다.
값 유형 :
참조 유형 :
이 Apple 블로그 게시물 에서 값 유형과 참조 유형의 관점에서 질문에 대답하면 매우 간단하게 나타납니다.
다음과 같은 경우에 값 유형 [예 : struct, enum]을 사용하십시오.
- ==로 인스턴스 데이터를 비교하는 것이 합리적입니다.
- 사본이 독립 상태를 갖기를 원합니다
- 데이터는 여러 스레드에서 코드로 사용됩니다
다음과 같은 경우 참조 유형 [예 : 클래스]을 사용하십시오.
- ===와 인스턴스 ID를 비교하는 것이 합리적입니다.
- 공유 가능한 변경 가능한 상태를 만들려고합니다.
이 기사에서 언급했듯이 쓰기 가능한 속성이없는 클래스는 구조체와 동일하게 작동하지만 한 가지주의 사항이 있습니다. 구조는 스레드 안전 모델에 가장 적합 합니다 .
클래스를 사용하면 상속을 받고 참조로 전달되며, 구조체에는 상속이 없으며 값으로 전달됩니다.
Swift에는 훌륭한 WWDC 세션이 있으며이 특정 질문은 그 중 하나에서 자세히 설명됩니다. 언어 가이드 나 iBook보다 훨씬 빠르게 속도를 높일 수 있으므로 시청하십시오.
구조체가 기능을 덜 제공한다고 말하고 싶지는 않습니다.
물론, 돌연변이 기능을 제외하고는 자기 자신은 변하지 않지만 그게 전부입니다.
상속은 모든 클래스가 추상적이거나 최종적이어야한다는 오래된 아이디어를 고수하는 한 잘 작동합니다.
프로토콜로 추상 클래스를 구현하고 구조체로 최종 클래스를 구현하십시오.
structs의 좋은 점은 copy on write가 처리하기 때문에 공유 가능한 변경 가능한 상태를 만들지 않고 필드를 변경할 수 있다는 것입니다. :)
그렇기 때문에 다음 예제의 속성 / 필드가 모두 변경 가능한 이유는 Java 또는 C # 또는 빠른 클래스 에서는 불가능 합니다.
"example"이라는 함수의 맨 아래에 약간 더럽고 간단한 사용법이있는 상속 구조 예제 :
protocol EventVisitor
{
func visit(event: TimeEvent)
func visit(event: StatusEvent)
}
protocol Event
{
var ts: Int64 { get set }
func accept(visitor: EventVisitor)
}
struct TimeEvent : Event
{
var ts: Int64
var time: Int64
func accept(visitor: EventVisitor)
{
visitor.visit(self)
}
}
protocol StatusEventVisitor
{
func visit(event: StatusLostStatusEvent)
func visit(event: StatusChangedStatusEvent)
}
protocol StatusEvent : Event
{
var deviceId: Int64 { get set }
func accept(visitor: StatusEventVisitor)
}
struct StatusLostStatusEvent : StatusEvent
{
var ts: Int64
var deviceId: Int64
var reason: String
func accept(visitor: EventVisitor)
{
visitor.visit(self)
}
func accept(visitor: StatusEventVisitor)
{
visitor.visit(self)
}
}
struct StatusChangedStatusEvent : StatusEvent
{
var ts: Int64
var deviceId: Int64
var newStatus: UInt32
var oldStatus: UInt32
func accept(visitor: EventVisitor)
{
visitor.visit(self)
}
func accept(visitor: StatusEventVisitor)
{
visitor.visit(self)
}
}
func readEvent(fd: Int) -> Event
{
return TimeEvent(ts: 123, time: 56789)
}
func example()
{
class Visitor : EventVisitor
{
var status: UInt32 = 3;
func visit(event: TimeEvent)
{
print("A time event: \(event)")
}
func visit(event: StatusEvent)
{
print("A status event: \(event)")
if let change = event as? StatusChangedStatusEvent
{
status = change.newStatus
}
}
}
let visitor = Visitor()
readEvent(1).accept(visitor)
print("status: \(visitor.status)")
}
Swift에서는 프로토콜 지향 프로그래밍이라는 새로운 프로그래밍 패턴이 도입되었습니다.
창조 패턴 :
신속하게 Struct는 가치 유형입니다. 자동으로 복제되는 입니다. 따라서 프로토 타입 패턴을 무료로 구현하는 데 필요한 동작을 얻습니다.
반면 클래스를 자동으로 할당하는 동안 복제되지 않은 참조 형이다. 프로토 타입 패턴을 구현하려면 클래스가 NSCopying
프로토콜을 채택해야 합니다.
얕은 사본 은 참조 만 복제하고, 해당 오브젝트를 가리키는 반면, 깊은 사본은 오브젝트의 참조를 복제합니다.
각 참조 유형 에 대해 딥 카피 를 구현 하는 것은 지루한 작업이되었습니다. 클래스에 추가 참조 유형이 포함 된 경우 각 참조 특성에 대해 프로토 타입 패턴을 구현해야합니다. 그런 다음 프로토콜 을 구현하여 전체 객체 그래프를 실제로 복사해야 합니다.NSCopying
class Contact{
var firstName:String
var lastName:String
var workAddress:Address // Reference type
}
class Address{
var street:String
...
}
structs와 enums 를 사용 하면 복사 로직을 구현할 필요가 없으므로 코드가 더 단순 해졌습니다.
많은 Cocoa API에는 NSObject 서브 클래스가 필요하므로 클래스를 사용해야합니다. 그러나 그 외에 Apple의 Swift 블로그에서 다음 사례를 사용하여 struct / enum 값 유형을 사용할지 클래스 참조 유형을 사용할지 결정할 수 있습니다.