Swift_문법.3

week2. 열거형, 클래스와 구조체, 옵셔널 체이닝

6. 열거형 (Enumeration)

-열거형: 관련된 값으로 이루어진 그룹을 공통의 형type으로 선언해 형 안정성type-safety을 보장.
(같은 주제로 연관된 데이터들을 멤버로 구성하여 나타내는 자료형), 정해놓은 입력 값만 선택해서 받고 싶을 때 사용(추가/수정 불가!)

>열거형은 1급 클래스 형(first-class types) -> 계산된 프로퍼티(computed properties) 제공 / 초기화 지정 / 초기 선언을 확장해 사용할 수 O

-열거형 문법(Enumertation Syntax) : enum 키워드로 열거형 정의

enum SomeEnumeration {
	//enumeration definition goes here
}
>C와 다르게 Swift에서 열거형은 생성될 때 각 case 별로 기본 integer 값을 할당하지 Xx,,
(암시적으로 정수값을 갖지 않는다는 의미)
>여러 case를 콤마(,)로 구분해서 한줄에 적을 수 O
ex1) ```
enum Planet {
	case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune
}
	ex2) ```
	enum Weather {
    	case sunny
        case windy(speed: Int)
        case rainy(chance: Int, amount: Int)
    }

각 열거형 정의는 완전 새로운 type을 정의
형의 이름은 대문자로 시작해야 함

ex) ```
enum CompassPoint {
case north, south, east, west
}

var directionToHead = CompassPoint.west
//directionToHead의 형은 초기화 될 때 타입추론이 돼서 CompassPoint 형을 갖게 됨
//->이렇게 한번 정의되면 다음에 값을 할당할 때 형을 생략한 dot syntax를 이용해 값을 할당할 수 있음!(축약형 문법)
directionToHead = .east


-Switch 구문에서 열거형 값 매칭 -> 반드시 열거형의 모든 경우cases를 완전히 포함해야 함!
(생략된게 존재할 경우 컴파일 되지 X)

	> ex) ```
		let somePlanet = Planet.earth
        switch somePlanet {
        case .earth:
        	print("Mostly harmless")
        default:
        	print("Not a safe place for humans")
        }
        // default case로 처리되지 않는 case를 피할 수 O
-관련 값(Associated Values): 열거형의 각 case에 custom type의 추가적인 정보를 저장할 수 O
	>```
	enum Barcode {
    	case upc(Int, Int, Int, Int)
        case qrCode(String)
    }
    
    var productBarcode = Barcode.upc(8, 85909, 51226, 3)
    //방법1
    
    productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
    //방법2
>관련 값은 switch case 문에서 사용할 때 상수 혹은 변수로 선언할 수 O
switch productBarcode {
case .upc(let numberSystem, let manufacturer):
..

>case 안의 관련 값이 전부 상수 or 변수이면 공통된 값을 case 뒤에 선언해서 보다 간결하게 작성 O
switch productBarcode {
case let .upc(numberSystem, manufacturer):
..
-Raw 값(Raw Values): C와 같이 case에 raw 값을 지정할 수 oo
		>ex)```
	enum ASCIIControlCharacter: Character {
	    case tab = "\t"
	    case lineFeed = "\n"
	    case carriageReturn = "\r"
        }
-> 위 예제에서 Character 형의 raw값으로 정의함
but!!  String, Character, Integer, Float 등의 형을 사용할 수도 O
->각 raw값은 열거형 선언에서 유일한 값으로 중복되면 Xx!

>Raw값은 관계 값(associated value)과는 다름
Raw값은 코드에서 열거형을 처음 선언할 때 정의되서 특정 열거형의 raw값은 항상 같은 값을 가짐.
But 관계 값은 같은 case라도 생성될 때 달라질 수 O

-암시적으로 할당된 Raw값(Implicitly Assigned Raw Values): raw값으로 Integer나 String값을 사용할 수 O, 각 case별로 명시적으로 raw값을 할당할 필요는 Xx,, (할당 안하면 > Swift에서 자동으로 값을 할당해줌)

  1. Number Type을 가지는 열거형
enum Position: Int {
	case top	// 0
    case mid	// 1
    case jug	// 2
    case adc	// 3
    case sup	// 4
}

//직접 RawValue 지정할 경우
enum Position: Int {
	case top = 0	//0
    case mid = 10	//10
    case jug		//11
    case adc = 20	//20
    case sup		//21
}
	// Raw Value가 없는 case는 바로 이전 case의 Raw Value에서 +1(정수값)한 값으로 세팅됨

Int형이 아닌 Double/Float와 같은 다른 자료형으로 했을 경우, 모든 case에 대해 값을 지정해주는 것이 아니면 에러 발생!

만약 Raw Value를 생략하고 싶다면, 바로 이전 case의 Raw Value를 정수 값으로 해주어야 함 (정수 값으로 해도 Double형으로 형변환 되어 들어감)

>ex) ```
enum Position: Doulbe {
case top = 1.0	// 1.0
case mid = 2.0	// 2.0
case jug = 3.0	// 3.0
case adc = 4	// 4.0 <-double형으로 형변환!
case sup		// 5.0
}
```
  1. Character Type을 가지는 열거형
enum Positiom: Character {
	case top = "t"		// t
    case mid = "m"		// m
    ..
}

이 경우 모든 case에 대한 Raw Value를 직접 선언해주어야 함!

  1. String Type을 가지는 열거형
enum Position: String {
	case top			// top
    case mid			// mid
    case jug = "slave"	// slave
	case adc 			// adc
    case sup			// sup
}

String은 Character와 달리 Raw Value를 지정하지 않으면, case 이름과 동일한 Raw Value가 자동으로 만들어진다.

-Raw 값을 이용한 초기화(Initializing from a Raw Value) : raw 값을 이용해 열거형 변수 초기화 가능, 만약 Raw값과 함께 열거형을 정의하였다면 열거형은 rawValue 파라미터로 Raw값을 받아서 자동적으로 초기화하고, 해당 case 혹은 nil을 반환한다.

ex) ```
let possiblePlanet = Planet(rawValue: 7)
	//raw값 7을 갖는 값을 열거형 변수의 초기 값으로 지정
```

raw 값 초기자는 모든 raw값에 대해 열거형 case 반환이 보장되지 X -> 실패할 수 있는 초기자(failable initializer)

= Raw Value 파라미터로 전달된 Raw 값과 일치하는 case가 없을 수 있기 때문에, 언제나 옵셔널 타입(값이 존재할 수도, 존재하지 않을 수도 있음)의 case가 반환됨.

ex) ```
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
    switch somePlanet {
    case .earth:
        print("Mostly harmless")
    default:
        print("Not a safe place for humans")
    }
} else {
    print("There isn't a planet at position \(positionToFind)")
}
	// Prints "There isn't a planet at position 11"
```

7. 클래스와 구조체 (Classes and Structures)

-클래스와 구조체 -> 프로그램의 코드를 조직화하기 위해 일반적으로 사용, OOP(Object-Oriented Programming, 객체지향 프로그래밍)를 위한 필요 요소

> 클래스와 구조체 모두 많은 공통점 존재
(값을 저장하기 위한 프로퍼티 정의, 기능을 제공하기 위한 메소드 정의, 특정 값을 접근할 수 있는 subscript 정의, 초기 상태를 설정할 수 있는 initializer 정의, 기본 구현에서 기능 확장, 특정 종류의 표준 기능을 제공하기 위한 프로토콜 순응conform)

>Only 클래스만 가능한 기능
-상속(Ingeritance): 클래스의 여러 속성을 다른 클래스에 물려 줌
-타입 캐스팅(Type casting): 런타임에 클래스 인스턴스의 타입을 확인
-소멸자(Deinitializers): 할당된 자원을 해제free up 시킴(해당 클래스 인스턴스의 메모리 해제)
-참조 카운트(Reference counting): 클래스 인스턴스에 하나 이상의 참조 가능
(구조체: 다른 코드로 전달될 때 항상 복사돼서 전달되고, 참조 카운트를 사용하지 Xx)

-선언: 클래스는 class 키워드를, 구조체는 struct 키워드 사용

class SomeClass {
..
}
struct SomeStructure {
..
}

새로운 클래스나 구조체를 선언할 때마다 Swift에서 완전 새로운 타입을 선언하는 것!

이름을 다른 표준 Swift 타입(String, Int, Bool)과 같이 UpperCamelCase로 선언,,
+프로퍼티/메소드 -> lowerCamelCase로 선언,,

ex) ```
//구조체 선언 예시
struct Resolution {
	var width = 0
    var height = 0
}
	//이때 구조체 Resolution의 프로퍼티 width와 height는 초기 값으로 0 할당 -> Swift의 타입 추론에 의해 자동으로 Int 형을 갖게 됨!

//클래스 선언 예시
class VideoMode {
	var resolution = Resolution()
		//위 Resulution 구조체를 값으로 사용
    var interlaced = false
    var frameRate = 0.0
    var name: String?
}
```

-클래스와 구조체 인스턴스(Class and Structure Instances): 클래스와 구조체 이름 뒤에 빈 괄호를 적으면 각각의 인스턴스를 생성할 수 O

-인스턴스(instance) : 클래스/구조체/열거형에서 생성된 객체
-프로퍼티(Property) : 클래스, 구조체, 열거형과 연관되어 있는 정보 / 값

클래스가 설계도면,,, 인스턴스는 클래스에 적혀 있는 대로 만들어낸 '실제',,,'쓸 수 있는 무언가' 의 느낌,,!

```
//구조체 인스턴스 생성
let someResolution = Resolution()

//클래스 인스턴스 생성
let someVideoMode = VideoMode()
```

-프로퍼티 접근(Accessing Properties): 점dot 문법을 통해 클래스/구조체 인스턴스의 프로퍼티에 접근할 수 O

>ex)```
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
	// "The width of someVideoMode is now 1280" 이 출력
```

>하위레벨의 구조체 프로퍼티도 직접 설정할 수 O

-구조체형의 멤버 초기화(Memberwise Initializers for Structure Types) : 모든 구조체는 초기화시 프로퍼티를 선언할 수 있는 초기자를 자동 생성하여 제공.

```
let vga = Resolution(width: 640, height: 480)
//이와 같은 멤버의 초기화는 구조체 안에 width와 height 프로퍼티만 정의했다면 자동으로 사용 가능하다는 의미
```

-구조체와 열거형은 값 타입(Structures and Enumerations Are Value Types):
->값 타입 = 이것이 함수에서 상수나 변수에 전달될 때 그 값이 복사돼서 전달된다는 의미

let hd = Resolution(width: 1920, height: 1080)		//Resolution 구조체의 인스턴스 hd 선언
var cinema = hd	   //hd를 cinema라는 변수에 할당
>이때 할당하는 순간 복사copy되기 때문에 cinema와 hd는 같지 Xxx,, 완전히 다른 인스턴스!
완전히 다른개체,, 다른 주소 공간에 저장되어 사용됨
cinema 인스턴스의 프로퍼티 값을 변경해도 hd의 해당 프로퍼티 값는 변경되지 X
>>열거형도 마찬가지!

-클래스는 참조 타입(Classes Are Reference Types) :
->참조 타입 = 변수나 상수에 값을 할당하거나 함수에 인자로 전달할 때 그 값이 복사되지 않고 참조됨 (참조된다 = 그 값을 갖고 있는 메모리를 바라보고 있다)

-식별 연산자(Identify Operators): 클래스는 참조 타입이기 때문에 여러 상수와 변수에서 같은 인스턴스를 참조할 수 O, 상수와 변수가 같은 인스턴스를 참조하고 있는지 비교하기 위해 식별 연산자 사용

=== : 두 상수나 변수가 같은 인스턴스를 참조하고 있는 경우 참
!== : 두 상수나 변수가 다른 인스턴스를 참조하고 있는 경우 참

식별 연산자(===)는 비교 연산자(==)와 같지 X,
식별 연산자는 참조 비교, 비교 연산자는 값 비교

>C, 등에서 포인터는 실제 메모리를 직접 가르키고 있고 키워드로 표시, Swift는 참조를 가르키기 위해 사용하지 X, 대신 다른 상수와 변수처럼 정의해 사용

-클래스와 구조체의 선택(Choosing Between Classes and Structures): 클래스와 구조체 모두 프로그램의 코드 조직화 & 특정 타입 선언에 사용,
클래스 인스턴스가 인자로 사용될 때 -> 참조
구조체 -> 값

1. 구조체의 주 목적이 관계된 간단한 값을 캡슐화encapsulate하기 위한 것인 경우
2. 구조체의 인스턴스가 참조되기 보다 복사되기를 기대하는 경우
3. 구조체에 의해 저장된 어떠한 프로퍼티가 참조되기 보다 복사되기를 기대하는 경우
4. 구조체가 프로퍼티나 메소드 등을 상속할 필요가 없는 경우
>>이러한 경우 구조체 사용 고려,, 나머지는 클래스 사용!

-String, Array, Dictionary의 할당과 복사 동작(Assignment and Copy Behavior for Strings, Arrays, and Dictionaries): 앞과 같은 기본 데이터 타입이 구조체로 구현되어 있음 ->> 이 값을 다른 상수나 변수에 할당하거나 함수나 메소드에 인자로 넘길 때 이 값이 복사된다는 것

8. 옵셔널 체이닝 (Optional Chaining)

-옵셔널 체이닝 : nil일 수도 있는 프로퍼티나, 메소드 그리고 서브스크립트에 질의query를 하는 과정

>if 옵셔널이 프로퍼티나 메소드 혹은 서브스크립트에 대한 값을 갖고 있다면 -> 그 값 반환 
 if 값이 nil -> nil 반환
 (여러 질의 연결할 경우, 연결된 것에서 어느 하나라도 nil이면 nil 반환)
 

-강제 언래핑 대체로서의 옵셔널 체이닝(Optional Chaining as an Alternative to Forced Unwrapping): 옵셔널 체이닝은 옵셔널 값 뒤에 물음표(?)를 붙여서 표현 (옵셔널을 사용할 수 있는 값에는 프로퍼티, 메소드, 서브스크립트가 포함)
강제 언래핑: 뒤에 느낌표(!)를 붙임
-> 차이점-강제 언래핑을 했는데 만약 그 값이 없으면 런타임 에러 발생! 옵셔널 체이닝의 경우 런타임 에러 대신 nil 반환,,

옵셔널 체이닝에 의해 nil 값이 호출될 수 있기 때문에 옵셔널 체이닝의 리턴값은 항상 옵셔널 값

>옵셔널 체이닝에 의해 호출되면 반환값과 같은 타입에 옵셔널이 붙어 반환됨!
ex) Int를 반환하는 메소드의 경우..
    ->옵셔널 체이닝이 성공적으로 실행되면 Int?를 반환
    
    
ex) ```
class Person {
	var residence: Residence?
    //Person 인스턴스는 옵셔널 residence 프로퍼티를 Residence?로 소유
}

class Residence {
	var numberOfRooms = 1
    //Residence 인스턴스는 numberOfRooms라는 Int 프로퍼티 하나 소유
}

//Person이라는 인스턴스를 생성하면,, residence 프로퍼티는 옵셔널의 기본값인 nil로 초기화됨
let john = Person() //john은 residence 프로퍼티가 nil인 값 소유

//이 person의 residence에 numberOfRooms프로퍼티에 접근하기 위해 느낌표를 이용해 강제 언래핑을 한다면 
let roomCount = john.residence!.numberOfRooms
	// this triggers a runtime error
	//residence가 언래핑할 값이 없기 때문에,,
    
//옵셔널 체이닝 사용할 경우
if let roomCount = john.residence?.numberOfRooms {
print("John's residence has \(roomCount) room(s).")
} else {
print("Unable to retrieve the number of rooms.")
}
	// Prints "Unable to retrieve the number of rooms."    
```

-옵셔널 체이닝을 위한 모델 클래스 정의(Defining Model Classes for Optional Chaining): 옵셔널 체이닝을 여러 level로도 사용할 수 O(multilevel optional chaining)

```
class Person {
	var residence: Residence?
}

class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
    return rooms.count
}
subscript(i: Int) -> Room {
    get {
        return rooms[i]
    }
    set {
        rooms[i] = newValue
    }
}
	//rooms 배열을 접근하기 위해 subscript 선언
    
func printNumberOfRooms() {
    print("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}

class Room {
let name: String
init(name: String) { self.name = name }
}

class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
    if let buildingNumber = buildingNumber, let street = street {
        return "\(buildingNumber) \(street)"
    } else if buildingName != nil {
        return buildingName
    } else {
        return nil
    }
}
}
//Address 클래스는 buildingIdentifier()라는 String? 타입의 메소드 지원
//-> buildingNumber와 street을 확인해 값이 있으면 buildingNumber와 결합된 street 값을 반환
//-> 값이 없는 경우 nil을 반환
```

-옵셔널 체이닝을 통한 프로퍼티의 접근(Accessing Properties Through Optional Chaining)

>ex)```
//Address() 인스턴스를 생성해 john.residence의 address로 할당

let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
```

-옵셔널 체이닝을 통한 메소드 호출(Calling Methods Through Optional Chaining): 옵셔널 체이닝을 이용해 메소드를 호출할 수 O

ex) ```
func printNumberOfRooms() {
	print("The number of rooms is \(numberOfRooms)")
}
```
>이 메소드가 옵셔널 체이닝에서 호출되면 반환값은 Void가 아니라 Void? 타입이다

-옵셔널 체이닝을 통한 서브스크립트 접근 (Accessing Subscripts Through Optional Chaining): 만약 서브스크립트의 결과로 옵셔널을 반환 한다면 그 뒤에 물음표(?) 붙임
-> (사전 타입은 key-value로 동작하기 때문에) 항상 그 사전에 key가 존재한다는 보장X -> 옵셔널 값 반환

-체이닝의 다중 레벨 연결 (Linking Multiple Levels of Chaining): 여러 단계에 걸쳐 연결 될 수도 O

> 옵셔널 체이닝의 상위 레벨 값이 옵셔널인 경우 -> (현재값이 옵셔널이 아니더라도) 그 값은 옵셔널값이 됨!
> 옵셔널 체이닝의 상위 레벨 값이 옵셔널이고 현재 값이 옵셔널 -> 더 옵셔널해지지는 X 그저 옵셔널은 옵셔널.
> 옵셔널 체이닝을 통해 값을 검생하거나 메소드를 호출하면 -> 몇 단계를 거치는지 상관 X, 옵셔널 반환

-체이닝에서 옵셔널 값을 반환하는 메소드 (Chaining on Methods with Optional Return Values)

ex)```
if let beginsWithThe =
john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
if beginsWithThe {
    print("John's building identifier begins with \"The\".")
} else {
    print("John's building identifier does not begin with \"The\".")
}
}
	// Prints "John's building identifier begins with "The"."
	>옵셔널이 반환 값에 걸려있고 메소드 자체에 걸려 있는 것이 X
     --> 메소드 괄호 뒤에 ?를 붙여줌

좋은 웹페이지 즐겨찾기