ABOUT ME

Today
Yesterday
Total
  • Swift 학습기 - 1
    기술/Swift 2023. 2. 8. 17:01

    오늘의 학습 컨텐츠

    오늘의 학습 목표

    • Swift 기본 문법에 대한 이해

    정리

    네이밍 룰

    • 기본적으로 Camel Case 를 따른다.
    • Lower Camel Case : function, method, variable, constant
    • Upper Camel Case : type (class, struct, enum, extension ...)
    • 대소문자를 구분한다

    Console Log

    • Print
      • 단순 문자열 출력
    • Dump
      • 인스턴스에 대한 자세한 설명 (description 프로퍼티) 까지 출력

    문자열 보간법

    • String interpolation
    • 프로그램 실행 중 문자열 내에 변수 또는 상수의 실질적인 값을 표현하기 위해 사용
    • \()
    let age : Int = 10
    
    print("안녕하세요 저는 \(age + 5)살입니다.")

    상수와 변수

    • Swift 는 함수형 프로그래밍 페러다임을 채용했기 때문에 불변객체에 대한 내용이 굉장히 많다.
    • 상수 선언 키워드 -> let
    • 변수 선언 키워드 -> var
    • 스위프트는 띄워쓰기에도 굉장히 민감한 언어기 때문에 띄워쓰기도 신경써줘야 한다.
    // 상수 선언
    let 이름: 타입 = 값
    
    // 변수 선언
    var 이름: 타입 = 값
    
    // 나중에 할당하려고 하는 상수나 변수는 타입을 꼭 명시해주어야 한다.
    let sum: Int
    let inputA: Int = 100
    let inputB: Int = 200
    
    // 선언 후 첫 할당
    sum = inputA + inputB
    sum = 1 // error
    • 타입 추론이 있긴 하지만 타입은 항상 명확히 쓰도록 하자.
    • 초기화가 되지 않은 상태에서 사용하려고 하면 컴파일러가 미리 에러를 잡아준다 

    스위프트의 기본 데이터 타입

    // Bool
    var someBool: Bool = true
    someBool = false			// 가능
    someBool = 0				// C언어 같은 이딴거 안됨
    
    // Int
    var someInt: Int = -100
    someInt = 100.1				// Error
    
    // UInt
    var someUInt: UInt = 100
    someUInt = -100				// Error
    someUInt = someInt			// Error
    
    // Float (32bit 부동 소수점)
    var someFloat: Float = 3.14
    someFloat = 3				// 가능
    
    // Double
    var someDouble: Double = 3.14
    someDouble = someFloat		// Error
    someDouble = 3				// 가능
    
    // Character  (UniCode)
    var someChar: Character = "ㅁ"  // 가능
    someChar = "123"			// Error (String type)
    
    // String
    var someString: String = "ggg"
    someString = someString + "aaa" // 가능
    someString = someChar			// Error
    • 스위프트는 Type 에 굉장히 민감한 언어이다.
    • 암시적으로 Data Type 을 변경하는 것은 거의 불가능하다고 생각하면 된다. (또한 해서 좋을게 하나도 없다)

    Any, AnyObject, nil

    • Any - Swift 의 모든 타입을 지칭하는 키워드
    • AnyObject - 모든 클레스타입을 지칭하는 프로토콜
    • nil - 없음을 의미하는 키워드
    var someAny: Any = 100
    someAny = "어떤 타입도 가능합니다."
    someAny = 123.12
    
    let someDouble: Double = someAny			// Error -> Cannot convert value of type
    
    ---
    class SomeClass {}
    
    var someAnyObject: AnyObject = SomeClass()
    someAnyObject = 123.12						// Error
    
    ---
    someAny = nil								// Error (어떤 데이터 타입도 가능하지만 없음은 안된다.)
    someObject = nil							// Error
    
    // nil 은 다른 언어의 null 과 매우 유사하다

    컬렉션 타입 (Array, Dictionary, Set)

    // Array - 순서가 있는 리스트 컬렉션
    // Dictionary - 키와 값의 쌍으로 이루어진 컬렉션 (해쉬맵과 흡사)
    // Set - 순서가 없고, 맴버가 유일한 컬렉션
    
    // 빈 Int Array 생성
    var integers: Array<Int> = Array<Int>()
    integers.append(1)
    integers.append(100)
    integers.append(101.1)		// Error
    
    integers.contains(100)		// true
    integers.contains(99)		// false
    
    integers.remove(at: 0)		// remove value ( 1 )
    integers.removeLast()
    integers.removeAll()
    
    integers.count				// 0
    
    // Array 를 표현하는 다양한 방법들
    var doubles: Array<Double> = [Double]()
    
    var strings: [String] = [String]()
    
    var chars: [Charactor] = []
    
    let immutableArray = [1, 2, 3]
    immutableArray.append(4)		// Error
    
    ---
    
    //
    var anyDictionary: Dictionary<String, Any> = [String: Any]()
    anyDictionary["someKey"] = "value"
    anyDictionary["anotherKey"] = 100
    
    anyDictionary.removeValue(forKey: "anotherKey")
    andDictionary["someKey"] = nil
    // 뒤 두개는 같은 표현
    
    let emptyDicionary: [String: String] = [:]
    let initializedDictionary: [String: String] = ["name": "yagom", "gender": "male"]
    
    let someValue: String = initializedDictionary["name"]	// Error -> name = String?
    
    
    ---
    // Set 는 축약문법이 없다.
    
    var integerSet: Set<Int> = Set<int>()
    integerSet.insert(1)
    integerSet.insert(100)
    integerSet.insert(99)
    integerSet.insert(99)
    integerSet.insert(99)
    
    integerSet.count			// 3
    
    let setA: Set<Int> = [1, 2, 3, 4, 5]
    let setB: Set<Int> = [3, 4, 5, 6, 7]
    
    let union: Set<Int> = setA.union(setB)	// 2, 3, 5, 6, 7, 4, 1
    let sortedUnion: [Int] = union.sorted()	// 1, 2, 3, 4, 5, 6, 7
    
    let intersection: Set<Int> = setA.intersection(setB)	// 5, 4, 3
    let subtracting: Set<Int> = setA.subtracting(setB)		// 2, 1
    • 생각보다 Set의 활용이 자주 있을듯.

    함수 기본

    // 함수 선언 기본 형태
    func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입 ...) -> 반환타입 {
    	함수 구현부
        return 반환값
    }
    
    func sum(a: Int, b: Int) -> Int {
    	return a + b
    }
    
    // 반환값이 없는 경우
    func printMyName(name: String) -> Void {
    	print(name)
    }
    
    func printYourName(name: String) {
    	print(name)
    }
    
    // 매개변수가 없는 함수
    
    func maxIntValue() -> Int {
    	return Int.max
    }
    
    // 매개변수와 반환값이 없는 함수
    
    func hello() -> Void { print("hello") }
    • 뭔가 typeScript 와 비슷하다...

    함수 고급

    // 함수의 다양한 모습
    
    // 기본값을 갖는 매개변수는 매개변수 목록 중 뒤쪽에 위치하는 것이 좋다.
    func 함수이름(매개변수1이름: 매개변수1타입, 매개변수2이름: 매개변수2타입 = 매개변수기본값 ...) -> 반환타입 {
    	함수 구현부
        return 반환값
    }
    
    func greeting(friend: String, me: String = "yagom") {
    	print("Hello \(friend)! I'm \(me)")
    }
    
    // 매개변수 기본값을 가지는 매개변수는 생략할 수 있습니다.
    greeting(friend: "hana") // Hello hana! I'm yagom
    greeting(friend: "john", me: "eric") // Hello john! I'm eric
    
    // 전달인자 레이블
    // 전달인자 레이블은 함수를 호출 할 때
    // 매개변수의 역할을 좀더 명확하게 하거나
    // 함수 사용자의 입장에서 표현하고자 할 때 사용합니다.
    
    // 함수 내부에서 전달인자를 사용 할 때에는 매개변수 이름을 사용합니다.
    func greeting(to friend: String, from me: String) {
    	print("Hello \(friend)! I'm \(me)")	
    }
    
    // 함수를 호출 할 때에는 전달인자 레이블을 사용해야 합니다.
    greeting(to: "hana", from: "yagom")
    
    // 가변 매개변수
    // 전달 받을 값의 개수를 알기 어려울 때 사용할 수 있다.
    // 가변 매개변수는 함수당 하나만 가질 수 있다.
    
    func sayHelloToFriends(me: String, friends: String...) -> String {
    	return "Hello \(friends)! I'm \(me)!"
    }
    
    
    // 함수의 타입 표현
    var someFunc: (String, String) -> Void = greeting(to: from:)
    someFunc("eric", "yagom")
    someFunc = sayHelloToFriends(me: friends:)  	// Error
    
    
    // 함수타입을 매개변수 타입으로 지정도 가능
    func runAnother(function: (String, String) -> Void) {
    	function("jenny", "mike")
    }
    
    runAnother(function: greeting(friend:me:))
    runAnother(function: someFunction)
    • 함수형 프로그래밍 페러다임을 채용했으니 당연히 함수는 일급객체이다.

    조건문

    // if - else
    
    if someInteger < 100 {
    	print("100 미만")
    } else if someInteger > 100 {
    	print("100 초과")
    } else {
    	print("100")
    }
    
    // 스위프트는 조건에 항상 Bool 타입이 들어와야 합니다
    
    
    // switch
    // 범위 연산자를 활용하면 더욱 쉽고 유용합니다.
    switch someInteger {
    	case 0:
        	print("zero")
        case 1..<100:
        	print("1 ~ 99")
        default:
        	print("unknown")
    }
    
    // 정수 외 대부분의 기본 타입을 사용 할 수 있습니다.
    switch "yagom" {
    	case "jake":
        	print("jake")
        case "mina":
        	print("mina")
        case "yagom":
        	print("yagom!!")
        default:
        	print("unknown")
    }
    • 스위프트의 if 문은 조건절에 () 를 생략 할 수 있다.
    • 스위프트의 switch 문은 break 가 없어도 암시적으로 break 를 한다. 
    • 스위프트의 조건문은 범위 연산자를 활용할 수 있다.
    • 스위프트의 switch 문은 정수 외 대부분의 기본타입을 다 사용 할 수 있다.

    반복문

    var integers = [1, 2, 3]
    let people = ["yagom": 10, "eric": 15, "mike": 12]
    
    for integer in integers {
    	print(integer)
    }
    
    // Dictionary 의 item 은 key와 value 로 구성된 튜플타입 입니다.
    for {name, age} in people {
    	print("\(name): \(age)")
    }

    옵셔널

    • nil 의 가능성을 문서화 하지 않아도, 코드만으로 충분히 표현 가능
    • 전달 받은 값이 옵셔널이 아니라면 nil 체크를 하지 않더라도 안심하고 사용
    // 스위프트 핵심개념중 하나
    // Optional => 값이 있을수도 있고, 없을 수도 있다.
    
    func someFunc(someOptionalParam: Int?) {
    	//
    }
    
    func someFunc(someParam: Int) {
    
    }
    
    someFunc(someOptionalParam: nil)
    someFunc(someParam: nil) 			// Error
    
    ---
    // 옵셔널은 열거형과 제너럴의 합작이다.
    enum Optional<Wrapped> : ExpressibleByNilLiteral {
    	case none
        case some(Wrapped)
    }
    
    let optionalValue: Optional<Int> = nil
    let optionalValue: Int? = nil
    
    ---
    // 옵셔널에서 암시적 추출 (!)
    var optionalValue: Int! = 100
    
    switch optionalValue {
    	case .none:
        	print("nil")
        case .some(let value):
        	print("Value is \(value)")
    }
    
    ---
    /// ?
    
    var optionalValue: Int? = 100
    
    // nil 할당가능
    optionalValue = nil
    
    // 기존 변수처럼 사용은 불가능함. 옵셔널과 일반값은 다른 타입이므로 연산불가

    옵셔널 값 추출

    • 옵셔널 바인딩
      • nil 체크 + 추출
    // if - let
    
    var myName: String? = "yagom"
    var yourName: String? = nil
    
    if let name = myName, let friend = yourName {
    	print("...")
    }
    // yourName 이 nil 이기 때문에 실행되지 않습니다.
    
    yourName = "hana"
    
    if let name = myName, let friend = yourName {
    	print("...")
    }
    // ...
    • 옵셔널 강제추출
      • 사용하지 말자.

    구조체

    // 스위프트에서는 대부분의 타입이 구조체이다.
    
    struct 이름 {
    	구현부
    }
    
    struct Sample {
    	var mutableProperty: Int = 100		// 가변
        let immutableProperty: Int = 100 	// 불변
        
        static var typeProperty: Int = 100 	// 타입 프로퍼티
        
        // 인스턴스 메서드
        func instanceMethod() {
    		print("instance method")
    	}
        
        // 타입 메서드
        static func typeMethod() {
    		print("type method")
    	}
    }
    
    // 구조체 사용
    // 가변 인스턴스
    var mutable: Sample = Sample()
    mutable.mutableProperty = 200		// 가능
    mutable.immutableProperty = 200		// Error
    
    // 불변 인스턴스
    let immutable: Sample = Sample()
    immutable.mutableProperty = 200			// Error
    immutable.immutableProperty = 200		// Error
    
    // 타입 프로퍼티 및 메서드
    Sample.typeProperty = 300
    Sample.typeMethod() 		// type method
    
    mutable.typeProperty = 300				// Error
    • 다른 언어의 Class 와 굉장히 흡사한데 Class 가 또 있네.. 왜그럴까

    클레스

    // 구조체는 값타입인 반면에 Class 는 참조 타입
    
    class 이름 {
    	// 구현부
    }
    
    class Sample {
    	var mutableProperty: Int = 100
        var immutableProperty: Int = 100
        
        // 타입 메서드 
        // 재정의 불가 타입 메서드 - static
        static func typeMethod() {
    		print("type method - static")
    	}
        
        // 재정의 가능 타입 메서드 - class
        class func typeMethod() {
    		print("type method - class")
    	}
    }
    
    var mutableReferrence: Sample = Sample()
    mutableReference.mutableProperty = 200
    
    let immutableReference: Sample = Sample()
    immutableReference.mutableProperty = 200		// 가능!
    • Struct 은 값타입, Class 는 참조타입이다.

    열거형

    // enum 은 타입이므로 대문자 카멜케이스를 사용
    // 각 case 는 소문자 카멜케이스로 정의
    // 각 case 는 자체가 고유 값
    
    // enum 이름 {
    //	case 이름1
    //  case 이름2
    //  case 이름3, 이름4, 이름5
    //  ...
    // }
    
    
    enum Weekday {
    	case mon
        case tue
        case wed
        case thu, fri, sat, sun
    }
    
    var day: Weekday = Weekday.mon
    day = .tue			// 축약표현
    
    switch day {
    	case .mon, .tue, .wed, .thu:
        	print("평일")
        case Weekday.fri:
        	print("불금!")
        case .sat, .sun
        	print("주말!")
    }
    
    
    // 원시값
    // C언어의 enum 처럼 정수값을 가질수도 있다.
    // rawValue 를 사용
    // case 별로 각각 다른 값을 가져야 한다.
    
    enum Fruit: Int {
    	case apple = 0
        case grape = 1
        case peach			// 자동으로 2가 들어감
    }
    
    print(Fruit.apple.rawValue)
    
    // 원시값을 통한 초기화
    // rawValue 를 통해 초기화 할 수 있다.
    // rawValue가 case에 해당 하지 않을 수 있다.
    // rawValue를 통해 초기화한 인스턴스는 옵셔널이다.
    
    let apple: Fruit? = Fruit(rawValue : 0)
    
    if let orange: Fruit = Fruit(rawValue: 5) {
    	print("rawValue 5에 해당하는 케이스는 \(orange)입니다")
    }
    
    
    // enum method
    
    enum Month {
    	case dec, jan, feb
        case mar, apr, may
        case jun, jul, aug
        case sep, oct, nov
        
        func printMessage() {
    		switch self {
    			case .mar, .apr, .may:
                	print("봄")
                default:
                	print("not 봄")
            }
    	}
    }
    • 다른 언어의 열거형보다 훨씬 많은 기능들이 있다.

    값타입과 참조타입

    • Class
      • 전통적인 OOP 관점에서의 클레스
      • 단일 상속
      • (인스턴스/타입) 메서드
      • (인스턴스/타입) 프로퍼티
      • 참조타입!
      • Apple 프레임워크의 대부분의 큰 뼈대는 모두 클레스로 구성
    • Struct
      • C언어 등의 구조체보다 다양한 기능
      • 상속 불가
      • (인스턴스/타입) 메서드
      • (인스턴스/타입) 프러퍼티
      • 값 타입!
      • Swift 의 대부분의 큰 뼈대는 모두 구조체로 구성
    • Enum
      • 다른 언어의 열거형과는 많이 다른 존재
      • 상속 불가
      • (인스턴스/타입) 메서드
      • (인스턴스/타입) 프러퍼티
      • 값타입
      • 유사한 종류의 여러 값을 유의미한 일므으로 한 곳에 모아 정의
      • 열거형 자체가 하나의 데이터 타입
      • 열거형의 case 하나하나 전부 하나의 유의미한 값으로 취급
    • 구조체는 언제 사용하나?
      • 참조가 아닌, 복사를 원할 때!! (값타입이니까)
      • 상속 관계가 필요 없을 때
      • Apple 프레임워크에서 개발할땐 주로 클레스를 사용함
    • Value vs Reference
      • Value : 데이터를 전달할때 값을 복사하여 전달
      • Reference : 데이터를 전달할때 참조 메모리 주소를 전달
    struct ValueType {
    	var property = 1
    }
    
    class ReferenceType {
    	var property = 1
    }
    
    let firstStructInstacne = ValueType()
    var secondStructInstance = firstStructInstacne
    secondStructInstance.property = 2
    
    firstStructInstacne.property		// 1
    secondStructInstance.property		// 2
    
    
    let firstClassReference = ReferenceType()
    var secondClassReference = firstClassReference
    
    secondClassReference.property = 2
    
    firstClassReference.property		// 1
    secondClassReference.property		// 2
    • 스위프트는 구조체, 열거형 사용을 선호
    • Apple 프레임워크는 대부분 클레스 사용
    • Apple 프레임워크는 사용 시 구조체/클래스 선택은 우리의 몫

    클로저

    // 클로저 - 코드의 블럭
    // 일급시민 (first-citizen)
    
    { (매개변수 목록) -> 반환 타입 in
    	실행 코드
    }
    
    // 함수를 사용한다면
    func sumFunc(a: Int, b: Int) -> Int {
    	return a + b
    }
    
    var sumResult: Int = sumFunc(a: 1, b: 2)
    
    // 클로저 사용
    var sum: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    	return a + b
    }
    
    // 함수는 클로저의 일종이므로
    // sum 변수에는 당연히 함수도 할당 할 수 있다.
    sum = sumFunc(a:b:)
    
    sumResult = sum(1, 2)
    
    // 함수의 전달인자로서 클로저
    let add: (Int, Int) -> Int
    add = { (a: Int, b: Int) -> Int in 
    	return a + b
    }
    
    func calc(a: Int, b: Int, method (Int, Int) -> Int) -> Int {
    	return method(a, b)
    }
    
    var calculated: Int = calc(a: 50, b: 10, method: add)
    • 함수도 클로저의 일종이다

    클로저 고급

    // 후행 클로저
    // 반환타입 생략
    // 단축 인자이름
    // 암시적 반환 표현
    
    func calculate(a: Int, b: Int, method: (Int, Int) -> Int) -> Int {
    	return method(a, b)
    }
    
    var result: Int
    
    // 후행 클로저
    // 클로저가 함수의 마지막 전달인자라면,
    // 마지막 매개변수 이름을 생략한 후
    // 함수 소괄호 외부에 클로저를 구현 할 수 있다.
    result = calculate(a: 10, b: 10) { (left: Int, right: Int) -> Int in
        return left + right
    }
    
    // 반환타입 생략
    // calculate 함수의 method 매개변수는 Int 타입을 반환할 것이라는 사실을 컴파일러도 알기 떄문에
    // 굳이 클로저에서 반환타입을 명시해 주지 않아도 됩니다. 대신 in 키워드는 생략할 수 없습니다.
    
    result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) in
    	return left + right
    })
    
    // 후행 클로저와 함께 사용 가능
    result = calculate(a: 10, b: 10) { (left: Int, right: Int) in
    	return left + right
    }
    
    // 단축 인자 이름
    // 클로저의 매개변수 이름이 굳이 불필요하다면 단축인자 이름을 활용할 수 있습니다.
    // 단축인자 이름은 클로저의 매개변수의 순서대로 $0, $1... 처럼 표현합니다.
    
    result = calculate(a: 10, b: 10, method: {
    	return $0 + $1
    }
    
    // 당연히 후행 클로저와 함께 사용 할 수 있ㅅ브니다.
    result = calculate(a: 10, b: 10) {
    	return $0 + $1
    }
    
    // 암시적 반환
    // 클로저가 반환 값이 있다면
    // 클로저의 마지막 줄의 결과값은 암시적으로 반환값으로 취급합니다.
    
    result = calculate(a: 10, b: 10) {
    	$0 + $1
    }
    
    // 간결하게 표현
    result = calculate(a: 10, b: 10) {$0 + $1}
    
    // 축약하지 않은 표현
    result = calculate(a: 10, b: 10, method: { (left: Int, right: Int) -> Int in
    	return left + right
    })

    프로퍼티

    // 프로퍼티
    
    // 저장 프로퍼티
    // 연산 프로퍼티
    // 인스턴스 프로퍼티
    // 타입 프로퍼티
    
    // 프로퍼티는 구조체, 클레스, 열거형 내부에 구현 할 수 있습니다.
    // 다만 열거형 내부에는 연산 프로퍼티만 구현 할수 있습니다.
    // 연산 프로퍼티는 var 로만 선언할 수 있습니다.
    
    struct Student {
    	// 인스턴스 저장 프로퍼티
        var name: String = ""
        var `class`: String = "swift"
        var koreanAge: Int = 0
        
        // 인스턴스 연산 프로퍼티
        var westernAge: Int {
    		get {
    			return koreanAge - 1
            }
            
            set(inputValue) {
    			koreanAge = inputValue + 1
            }
    	}
        
        // 타입 저장 프로퍼티
        static var typeDescription: String = "학생"
        
        // 읽기 전용 인스턴스 연산 프로퍼티
        var selfIntroduction: String {
    		get {
    			return "저는 \(self.class)반 \(name)입니다"
            }
    	}
    }

    프로퍼티 감시자

    // 프로퍼티 감시자를 사용하면 프로퍼티 값이 변경될 때 원하는 동작을 수행 할 수 있습니다.
    
    struct Money {
    	var currencyRate: Double = 1100 {
    		willSet(newRate) {
    			// 바뀌기 직전 호출	
                print("\(currencyRate)에서 \(newRate)로 변경될 예정입니다")
            }
            
            didSet(oldRate) {
    			// 바뀐 다음 호출
                print("\(oldRate)에서 \(currencyRate)로 변경되었습니다.")
            }
        }
    }
    
    // 연산 프로퍼티와 프로퍼티 감시자는 함께 사용 할 수 없습니다.

    상속

    // 상속
    // 스위프트의 상속은 클래스, 프로토콜 등에서 가능합니다.
    // 열거형, 구조체는 상속이 불가능
    // 스위프트는 다중상속을 지원하지 않습니다.
    
    class 이름: 상속받을 클래스 이름 {
    	// 구현부
    }
    
    class Person {
    	var name: String = ""
        
        func selfIntroduce() {
    		print("저는 \(name) 입니다")
        }
        
        // final 키워드를 사용하여 재정의를 방지 할 수 있습니다.
        final func sayHello() {
    		print("hello")
        }
        
        // 타입 메서드
        // 재정의 불가 타입 메서드 - static
        static func typeMethod() {
    		print("type method)
        }
        
        // 클레스 메서드
        // 재정의 가능!
        class func classMethod() {
    		print("class method)
        }
        
        // 재정의 가능한 class 메서드라도 final 키워드가 붙으면 재정의 불가능합니다.
        // final class = static
    }
    
    class Student: Person {
    	var name: String = ""		// Error (이미 상속받은 프로퍼티 저장 프로퍼티는 오버라이딩 불가)
       	var major: String = ""
        
        override func selfIntroduce() {
    		print("저는 \(name)이고, 전공은 \(major)입니다")
            super.selfIntroduce()
        }
        
        override class func classMethod() {
    		print("...")
        }
        
        override func sayHello() {} // Error 재정의 불가
    }

    인스턴스 생성과 소멸

    // 이니셜라이저와 디이니셜라이저
    // init, deinit
    
    // 스위프트의 모든 인스턴스는 초기화와 동시에
    // 모든 프로퍼티에 유효한 값이 할당되어 있어야 합니다.
    // 프로퍼티에 미리 기본값을 할당해두면
    // 인스턴스가 생성됨과 동시에 초기값을 지니게 됩니다.
    
    class PersonA {
    	// 모든 저장 프로퍼티에 기본 값 할당
        var name: String = "unknown"
        var age: Int = 0
        var nickName: String = "nick"
    }
    
    let jason: PersonA = PersonA()
    
    
    // 프로퍼티 기본값을 지정하기 어려운 경우에는
    // 이니셜라이저를 통해
    // 인스턴스가 가져야할 초기값을 전달 할 수 있습니다.
    
    class PersonB {
    	var name: String
        var age: Int
        var nickName: String
        
        init(name: String, age: Int, nickName: String) {
    		self.name = name
            self.age = age
            self.nickName = nickName
        }
    }
    
    let hana: PersonB = PersonB(name: "hana", age: 20, nickName: "하나")
    
    
    // 프로퍼티의 초기값이 꼭 필요 없을 때
    // 옵셔널을 사용
    
    class PersonC {
    	var name: String
        var age: Int
        var nickName: String?
        
        // 자신의 initializer 를 사용하려면 앞에 convenience 키워드를 넣어줘야함.
        convenience init(name: String, age: Int, nickName: String) {
    		self.init(name: name, age: age)
            self.nickName = nickName
        }
    
    	init(name: String, age: Int) {
    		self.name = name
            self.age = age
        }
    }
    
    
    // 실패가능한 이니셜라이저
    // 이니셜라이저 매개변수로 전달되는 초기값이 잘못된 경우
    
    class PersonD {
    	var name: String
        var age: Int
        var nickName: String?
    
    	init?(name: String, age: Int) {
    		if (0...120).contains(age) == false {
    			return nil
            }
            
            if name.characters.count == 0 {
    			return nil
            }
            self.name = name
            self.age = age
        }
    }
    
    let john: PersonD = personD(name: "john", age: 23)	// Error
    let john: PersonD? = personD(name: "john", age: 23)
    
    
    // 디이니셜라이저
    // deinit은 클레스의 인스턴스가 메모리에서 해제된느 시점에 호출됩니다
    
    class PersonE {
    	var name: String
        var pet: Puppy?
        var child: PersonC
        
        
        // deinit 은 매개변수를 가질 수 없다.
        deinit {
        	if let petName = pet?.name {
            	print("\(name)가 \(child.name)에게 \(petName)를 인도합니다.")
                self.pet?.owner = child
            }
        }
    }

    옵셔널 체이닝과 nil 병합 연산자

    // 옵셔널 체이닝은 옵셔널 요소 내부의 프로퍼티로
    // 또 다시 옵셔널이 연속적으로 연결되는 경우 유용하게 사용 할 수 있습ㄴ디ㅏ.
    
    class Person {
    	var name: String
        var job: String?
        var home: Apartment?
        
        init(name: String) {
        	self.name = name
        }
    }
    
    class Apartment {
    	var buildingNumber: String
        var roomeNumber: String
        var `guard`: Person?
        var owner: Person?
        
        init(dong: String, ho: String) {
    		buildingNumber = dong
            roomNumber = ho
        }
    }
    
    let yagom: Person? = Person(name: "yagom")
    let apart: Apartment? = Apartment(dong: "101", ho: "1")
    let superman: Person? = Person(name: "superman")
    
    // 만약 우리집의 경비원의 직업이 궁금하다면?
    // 옵셔널 체이닝을 사용하지 않는다면
    func guardJob(owner: Person?) {
    	if let owner = owner {
    		if let home = owner.home {
    			if let ...
            }
        }
    }
    
    // 옵셔널 체이닝을 사용하면
    func guardJob(owner: Person?) {
    	if let guardJob = owner?.home?.guard?.job {
    		print("우리집 경비원의 직업은 \(guardJob) 입니다")
        } else {
    		print("우리집 경비원은 직업이 없어요")
        }
    }
    
    // nil 병합 연산자
    var guardJob : String
    
    guardJob = yagom?.home?.guard?.job ?? "슈퍼맨"
    print(guardJob) // 슈퍼맨
    • 다른 언어에서의 옵셔널체이닝과 동일하다

    타입캐스팅

    // 스위프트의 타입캐스팅은
    // 인스턴스의 타입을 확인하는 용도
    // 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용 할 수 있는지
    // 확인하는 용도로 사용합니다.
    // is, as 를 사용합니다.
    
    // 기존의 다른언어에서 타입 캐스팅은 아래와 같다
    let someInt: Int = 100
    let someDouble: Double = Double(someInt) // 이것은 실질적으로 타입캐스팅이 아니라, Double type 의 인스턴스가 하나 더 생성된다.
    
    
    class Person {
    	var name: String = ""
        func breath() {
    		print("하 하")
        }
    }
    
    class Student: Person {
    	var school: String = ""
        func goToSchool() {
    		print("등교")
        }
    }
    
    class UniversityStudent: Student {
    	var major: String = ""
        func goToMT() {
        	print("멤버쉽 트레이닝을 갑니다!")
        }
    }
    
    var yagom: Person = Person()
    var hana: Student = Student()
    var jason: UniversityStudent = UniversitiyStudent()
    
    var result: Bool
    
    result = yagom is Person // true
    result = yagom is Student // false
    result = yagom is UniversityStudent // false
    
    result = hana is Person // true
    result = hana is Student // true
    result = hana is UniversityStudent // false
    
    result = jason is Person // true
    result = jason is Student // true
    result = jason is UniversityStudent // true
    
    if yagom is UniversityStudent {
    	print("yagom은 대학생입니다")
    } else if yagom is Student {
    	print("yagom은 학생입니다")
    } else if yagom is Person {
    	print("yagom은 사람입니다")
    }
    
    switch jason {
    	case is UniversityStudent:
        	...
        case is Student:
        	...
        case is Person:
        	...
        default:
        	...
    }
    
    // 업캐스팅
    // as를 사용해서 부모클래스의 인스턴스로 사용 할 수 있도록 컴파일러에게 가르쳐 주는 것
    var mike: Person = UniversityStudent() as Person
    
    // 다운캐스팅
    // as? 또는 as! 를 사용하여
    // 자식 클래스의 인스턴스로서 사용 할 수 있도록 컴파일러에게 가르쳐 주는 것
    
    // 다른 언어들과 마찬가지로 사실상 잘 사용되지 않는다.....

    Assert 와 Guard

    // 어플리케이션의 동작 도중 생성하는 다양한 결과값을
    // 동적으로 확인하고 안전하게 처리 할 수 있도록
    // 확인하고 처리 할 수 있다.
    
    // assert(_:_:file:line:) 함수를 사용합니다.
    // assert 함수는 디버깅 모드에서만 동작합니다.
    // 배포하는 어플리케이션에서는 제외됩니다.
    // 주로 디버깅 중 조건을 검증하기 위해 사용합니다.
    
    var someInt: Int = 0
    
    assert(someInt == 0, "someInt != 0") // 참이면 지나감, 거짓이면 메시지를 출력하고 정지함
    
    someInt = 1
    assert(someInt == 0) // 동작 중지, 검증 실패
    
    
    func functionWithAssert(age: Int?) {
    	assert(age != nil, "age == nil)
        assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못되었습니다")
        print("당신의 나이는 \(age!)세 입니다")
    }
    
    functionWithAssert(age: 50)
    functionWithAssert(age: -1)			// 동작 중지
    functionWithAssert(age: nil)		// 동작 중지
    
    
    
    ---
    // guard 를 사용하여 잘못된 값의 전달 시
    // 특정 실행 구문을 빠르게 종료합니다
    // 디버깅 모드 뿐만 아니라 어떤 조건에서도 동작합니다
    // guard 의 else 블럭 내부에는
    // 특정 코드블럭을 종료하는 지시어 (return, break) 가 꼭 있어야 합니다
    // 타입 캐스팅, 옵셔널과도 자주 사용됩니다.
    // 그 외 단순 조건 판ㄷ나 후 빠르게 종료할때도 용이합니다.
    
    func functionWithGuard(age: Int?) {
    	guard let unwrappedAge = age,
        	unwrappedAge < 130,
            unwrappedAge >= 0 else {
    		print("나이값 입력이 잘못되었습니다.)
            return 
        }
        guard unwrappedAge < 100 else {
    		return
        }
        
        // guard 는 블럭을 넘어서도 unwrappedAge 를 사용 할 수 있게 해준다.
        print("당신의 나이는 \(unwrappedAge)세 입니다")
    }
    
    var count = 1
    
    while true {
    	guard count < 3 else {
    		break
        }
        print(count)
        count += 1
    }
    
    func someFunc(info: [String: Any]) {
    	guard let name = info["name"] as? String else {
    		return
        }
        
        guard let age = info["age"] as? Int, age >= 0 else {
    		return
        }
        print("\n(name): \(age)")
    }
    
    someFunc(info: ["name": "jenny", "age": "10"])	// guard - return
    someFunc(info: ["name": "mike"])				// guard - return
    someFunc(info: ["name": "jenny", "age": 10])    // yagom : 10
    • Guard 를 잘써야 스위프트를 잘할수있을것 같은 느낌이 든다...

    프로토콜

    // 프로토콜
    
    // 프로토콜은 특정 역할을 수행하기 위한
    // 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의합니다.
    
    // 구조체, 클레스, 열거형은 프로토콜을 채택(Adopted) 해서
    // 프로토콜의 요구사항을 실제로 구현 할 수 있습니다.
    // 어떤 프로토콜의 요구사항을 모두 따르는 타입은
    // 그 '프로토콜을 준수한다(Conform)' 고 표현합니다.
    
    protocol 프로토콜 이름 {
    	// 정의부
    }
    
    protocol Talkable {
    	// 프로퍼티 요구
        // 프로퍼티 요구는 항상 var 키워드를 사용합니다.
        // get 은 읽기만 가능해도 산과없다는 뜻이며
        // get과 set을 모두 명시하면
        // 읽기 쓰기 모두 가능한 프로퍼티여야 합니다.
        var topic: String {get set}
    	var language: String {get}
        
        // 메서드 요구
        func talk()
        
        // 이니셜라이저 요구
        init(topic: String, language: String)
    }
    
    // Person 구조체는 Talkable 프로토콜을 채택했습니다
    struct Person: Talkable {
    	var topic: String
        let language: String
        
        func talk() {
    		...
        }
        
        init(topic: String, language: String) {
    		...
        }
    }
    
    // 읽기 전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능합니다
    // var language: String { get { return "한국어" } } 
    // 물론 읽기, 쓰기 프로퍼티도 연산 프로퍼티로 대체 할 수 있습니다.
    // 
    
    ---
    // 프로토콜은 다중상속이 가능하다
    // 프로토콜도 프로토콜의 상속이 가능하다
    
    // 프로토콜을 채택하면 반드시 모두 구현해야한다.
    
    
    // 클레스 상속과 프로토콜
    // 클래스에서 상속과 프로토콜 채택을 동시에 하려면
    // 상속받으려는 클래스를 먼저 명시하고,
    // 그 뒤에 채택할 프로토콜 목록을 작성하면 된다.
    
    // 인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있습니다.
    // is, as 연산자 이용
    
    let sup: SuperClass = SuperClass()
    var someAny: Any = sup
    
    someAny is Readable // true or false ...
    
    if let someReadable: Readable = someAny as? Readable {
    	someReadable.read()
    }
    • 다른언어의 Interface 와 매우 흡사하다.
    • 사용하는 사람 입장에서는 이 타입은 이 프로토콜을 준수하고 있기 때문에, ~기능을 수행 할 수 있구나 라는 것을 쉽게 파악 할 수 있다.

    익스텐션

    // 익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에
    // 새로운 기능을 추가할수 있는 기능입니다.
    // 기능을 추가하려는 타입의 구현된 소스 코드를
    // 알지 못하거나 볼 수 없다 해도, 타입만 알고 있다면 그 타입의 기능을 확장 할 수 도 있습니다.
    
    // 익스텐션으로 추가 할 수 있는 기능
    // 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
    // 타입 메서드 / 인스턴스 메서드
    // 이니셜라이저
    // 서브스크립트
    // 중첩타입
    // 특정 프로토콜을 준수 할 수 있도록 기능 추가
    
    // 기존에 존재하는 기능을 재정의할 수는 없습니다.
    
    extension 확장할타입이름 {
    	// 타입에 추가될 새로운 기능 구현
    }
    
    
    // 익스텐션은 기존에 존재하는 타입이 
    // 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있습니다.
    
    extension String: 프로토콜1, 프로토콜2, 프로토콜3 ... {
    	// 프로토콜 요구사항 구현
    }
    
    extension Int {
    	var isEven: Bool {
        	return self % 2 == 0
        }
        var isOdd: Bool {
    		return self % 2 == 1
        }
    }
    
    print(1.isEven) // false
    print(2.isEven) // true
    
    var number: Int = 3
    print(number.isOdd) // true
    
    
    extension Int {
    	func multiply(by n: Int) -> {
    		return self * n
        }
    }
    print(3.multiply(by: 2)) // 6
    
    // 이니셜라이저 추가
    extension String {
    	init(intTypeNumber: Int){
        	self = "\(intTypeNumber)"
        }
    }
    
    let stringFromInt: String = String(intTypeNumber: 100)
    • Dart 의 extension 과 완전히 동일하다 ....

    오류 처리

    // Error 프로토콜과 (주로) 열거형을 통해서 오류를 표현합니다.
    
    enum 오류종류이름: Error {
    	case 종류1
        case 종류2
        case 종류3
        // ...
    }
    
    // 자판기 동작 오류를 표현
    enum VendingMachineError: Error {
    	case invalidInput
        case insufficientFuncs(moneyNeeded: Int)
        case outOfStock
    }
    
    // 자판기 동작 도중 발생한 오류 던지기
    // 오류 발생의 여지가 있는 메서드는 throws를 사용하여
    // 오류를 내포하는 함수임을  표현합니다.
    
    class VendingMachine {
    	let itemPrice: Int = 100
        var itemCount: Int = 5
        var deposited: Int = 0
        
        // 돈 받기 메서드
        func receiveMoney(_ money: Int) throws {
    		// 입력한 돈이 0이하면 오류를 던집니다
            guard money > 0 else {
    			throw VendingMachineError.invalidInput
            }
            
            // 오류가 없으면 정상처리를 합니다.
            self.deposited += money
            print("\(money)원 받음")
        }
        
        // 물건을 파는 메서드
        func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
    		...
        }
    }
    
    
    // 자판기 인스턴스
    let machine: VendingMachine = VendingMachine()
    
    // 판매 결과를 전달받을 변수
    var result: String?
    
    
    
    // 오류발생의 여지가 있는 throws 함수는 
    // try를 사용하여 호출해야 합니다.
    // try, try?, try!
    
    // 오류 발생의 여지가 있는 함수는 do - catch 구문을 활용하여 try 와 함께 사용
    
    do {
    	try machine.receiveMoney(0)
    } catch VendingMachineError.invalidInput {
    	print("입력이 잘못되었습니다.")
    } catch ... {
    	...
    } catch ... {
    	...
    }
    
    // switch 로도 표현함
    do {
    	try machine.receiveMoney(300)
    } catch (let error) {                        // } catch { 이렇게 생략해도 됨. 암시적으로 표현됨
    	switch error {
    		case ...
        }
    }
    
    
    do {
    	result = try machine.vend(numberOfItems: 4)
    } catch {
    	print(error)
    }
    
    do {
    	result = try machine.vend(numberOfItems: 2)
    }
    
    // try?
    // 별도의 오류처리 결과를 통보받지 않고
    // 오류가 발생했으면 결과값을 nil로 받는다.
    // 정상동작 후에는 옵셔널 타입으로 정상반환값을 돌려받습니다.
    result = try? machine.vend(numberOfItems: 2)
    result // Optional or nil
    
    
    // try!
    // 오류가 발생하지 않을 것이라는 강력한 확신을 가질 때
    // try!를 사용하면 정상동작 후에 바로 결과값을 돌려받습니다
    // 오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지됩ㄴ디ㅏ
    
    result = try! machine.vend(numberOfItems: 1)
    result // 1개 제공함
    • try - catch 가 아닌 do - try - catch 라니

    고차함수

    // 전달이낮로 함수를 전달받거나
    // 함수실행의 결과를 함수로 반환하는 함수
    
    // map. filter, reduce
    
    // 컨테이너 내부의 기존 데이터를 변형(transform) 하여 새로운 컨테이너 생성
    
    let numbers: [Int] = [0, 1, 2, 3, 4]
    var doubledNumbers: [Int]
    var strings: [String]
    
    // for구문 사용
    doubledNumbers = [Int]()
    strings = [String]()
    
    for number in numbers {
    	doubledNumbers.append(number * 2)
        strings.append("\(number)")
    }
    
    print(doubledNumbers) // [0, 2, 4, 6, 8]
    print(strings) // ["0", "1", "2", "3", "4"]
    
    
    // map 메서드 사용
    // numbers 의 각 요소를 2배 하여 새로운 배열 반환
    doubledNumbers = numbers.map({ (number: Int) -> Int in
    	return number * 2
    })
    
    // numbers 의 각 요소를 문자열로 변환하여 새로운 배열 반환
    strings = numbers.map({ (number: Int) -> String in
    	return "\(number)"
    })
    
    doubledNumbers = numbers.map { $0 * 2 }
    
    var filtered: [Int] = [Int]()
    
    let evenNumbers: [Int] = numbers.filter {
    	(number: Int) -> Bool in
        	return number % 2 == 0
    }
    
    // 매개변수, 반환타입, 반환키워드 생략, 후행 클로저
    let oddNumbers: [Int] = numbers.filter { $0 % 2 != 0 }
    
    
    // reduce
    let sumFormThree = someNumbers.reduce(3) { $0 + $1 }
    • 확실히 함수형 프로그래밍 부분은 Javascript 의 향이 난다

    다음에 알아 볼 것

    • 제네릭(Generics)
    • 서브스크립트(Subscript)
    • 접근수준(Access Control)
    • ARC(Automatic Reference Counting)
    • 중첩타입(Nested Types)
    • 사용자정의 연산자(Custom Operators)

    '기술 > Swift' 카테고리의 다른 글

    Swift 학습기 - 3, SwiftUI  (0) 2023.02.14
    Swift 학습기 - 2, SwiftUI, MVVM  (0) 2023.02.13
Designed by Tistory.