레이블이 Swift인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Swift인 게시물을 표시합니다. 모든 게시물 표시

Swift 파운데이션 프레임워크

  1. 파운데이션(Foundation) 프레임워크란? MacOS, iOS 애플리케이션 개발의 기초적인 기능을 제공하는 프레임워크로 클래스 이름 앞에 NS~로 시작되는 클래스로 가장 최상위 부모 클래스는 NSObject 클래스이다.
    1. 파운데이션 프레임워크에 있는 주요 기능
      1. 데이터 다루기
      2. 네트워크
      3. 파일 다루기
      4. 멀티 쓰레드
    2. Swift와 Foundation 프레임워크 : Swift2까지는 Objective-C 기반의 클래스와 API를 유지하여 NSString, NSMutableString, NSArray, NSMutableArray 등으로 제공했지만, Swift3부터 Swift 용으로 포팅하여 API Guideline을 적용하여 NS를 붙이지 않아도 사용 가능해졌다. NSString -> String 으로도 사용 가능하다.
    3. 파운데이션 프레임워크에서 가장 기본 클래스 : NSObject
      1. 제공되는 기능
        1. 객체 비교
        2. 메모리 관리
        3. 타입 체크
        4. 셀렉터
      2. NSObject 클래스를 상속하여 사용할지 아니면 그냥 Swift에서 제공하는 클래스를 사용할지는 셀렉터를 사용하면 무조건 NSObject 클래스를 상속받아 사용하고 그렇지 않은 경우는 NSObject 클래스를 상속하지 않아도 무관하다.
  2. 셀렉터 : 셀렉터는 메소드를 식별하는 정보로 사용되며, 주로 클래스에 정의된 메소드를 검사한 후에 실행하는데 사용된다.
    1. 사용방법
      셀렉터 사용방법
      // 셀렉터 정의
      #selector(TYPE.Method_Name)

      // 셀렉터 검사
      func responds(to aSelector : Selector!) -> Bool

      // 셀렉터 실행
      // 파라미터가 없는 메소드 실행
      func perform(_ aSelector : Selector!) -> Unmanaged<AnyObject>!
      // 파라미터가 있는 메소드 실행
      func perform(_ aSelector : Selector!, with object:Any!) -> Unmanaged<AnyObject>!
    2. 파라미터가 없는 메소드에서 셀렉터 사용방법
      파라미터가 없는 메소드
      // Class 정의
      class MyClass : NSObject {
         @objc func greeting() { print("Hello") }
      }

      let obj = MyClass()
      // 셀렉터 정의
      let sel = #selector(MyClass.greeting)
      // 셀렉터 검사
      if obj.responds(to : sel) {
      // 셀렉터로 메소드 실행
         obj.perform(sel)
      }
    3. 파라미터가 있는 메소드에서 셀렉터 사용방법
      파라미터가 있는 메소드
      // Class 정의
      class MyClass : NSObject {
         @objc func sayHello(who : String) {
             print("Hello \(who)")
         }
      }

      let obj = MyClass()
      // 셀렉터 정의
      let sel = #selector(MyClass. sayHello(who : ))
      // 셀렉터 검사
      if obj.responds(to : sel) {
      // 셀렉터로 메소드 실행
         obj.perform(sel, with : "Selena")
      }
    4. 셀렉터로 사용되는 메소드 앞에 @objc 키워드는 Objective-C 메소드로 사용한다는 의미로 셀렉터로 사용하려는 메소드 앞에 반드시 붙여야 하고, 붙이지 않을 경우 에러가 발생한다.
  3. 파일 다루기
    1. FileManager : 파일 시스템을 다룬다.
    2. 사용 방법 : let filmanager = FileManager.default 로 선언하여 사용한다.
    3. 파일 존재확인
      파일 존재 확인 방법
      // FileManager 선언
      let fm = FileManager.default
      let checkFilePath = "/User/Document/Test.txt"
      // 파일 존재여부 확인
      if(fm.fileExists(atPath : checkFilePath)) {
          print("파일 존재")
      } else {
          print("파일 존재하지 않음")
      }
    4. 폴더 내 파일 목록 확인
      폴더 내 파일 목록 확인 방법
      // FileManager 선언
      let fm = FileManager.default
      let path = "/User/Document"
      // 폴더 존재여부 확인
      if(fm.fileExists(atPath : path)) {
          let fileList = try? fm.contentOfDirectory(atPath : path)
          print("파일 목록 : \(fileList)")
      } else {
          print("파일 존재하지 않음")
      }
    5. 파일 복사
      파일 복사 방법
      // FileManager 선언
      let fm = FileManager.default
      let srcPath = "/User/Document/Test.txt"
      let desPath = "/User/Test/Test.txt"
      //복사하려는 파일 존재여부 확인
      if(fm.FileExists(atPath : srcPath) == false) {
         print("복사할 파일이 없음")
      } else if(fm.FileExists(atPath : desPath) == false) {
         print("복사할 폴더에 이미 파일이 있음")
      } else {
         print("복사 중...")
         do{
            try fm.copyItem(atPath : srcPath, toPath : desPath)
         } catch let err {
            print("복사 실패 : \(err)")
         }
      }
    6. 파일 이동
      파일 이동 방법
      // FileManager 선언
      let fm = FileManager.default
      let srcPath = "/User/Document/Test.txt"
      let desPath = "/User/Test/Test.txt"
      // 이동하려는 파일 존재여부 확인
      if(fm.FileExists(atPath : srcPath) == false) {
         print("이동할 파일이 없음")
      } else if(fm.FileExists(atPath : desPath) == false) {
         print("이동할 폴더에 이미 파일이 있음")
      } else {
         print("이동 중...")
         do{
            try fm.moveItem(atPath : srcPath, toPath : desPath)
         } catch let err {
            print("이동 실패 : \(err)")
         }
      }
    7. 파일 삭제
      파일 삭제 방법
      // FileManager 선언
      let fm = FileManager.default
      let delPath = "/User/Document/Test.txt"
      // 삭제하려는 파일 존재여부 확인
      if(fm.FileExists(atPath : delPath) == false) {
         print("삭제할 파일이 없음")
      } else {
         print("삭제 중...")
         do{
            try fm.removeItem(atPath : delPath)
         } catch let err {
            print("삭제 실패 : \(err)")
         }
      }
  4. 직렬화
    1. 직렬화는 생성한 객체를 바이너리 데이터로 변경하는 것으로 객체를 바이너리 데이터로 변환하는 것을 아카이브(Archive) 즉, 직렬화이고, 바이너리 데이터를 다시 객체로 변환하는 것을 언아카이브(Unarchive) 즉, 역직렬화라고 한다.
    2. 직렬화는 네트워크 전송이나 파일에 내용을 쓸 때 주로 사용된다.
    3. 바이너리 데이터는 Data 객체로 변환된다.
    4. 데이터 직렬화 방법
      1. NSKeyedArchiver : 객체를 바이너리 데이터로 변환 (Any -> Data, File)
      2. NSKeyedUnarchiver : 바이너리 데이터를 객체로 변환 (Data, File -> Any)
      3. 사용방법
        객체 직렬화
        let num = 2020
        // 직렬화
        let data : Data = NSKeyedArchiver.archivedData(withRootObject : num)
        // 역직렬화
        if let num2 = NSKeyedUnarchiver.unarchiveObject(withData : data) as? Int {
           print("Dat에서 복원 성공 : \(num2)")
        } else {
           print("복원 실패 ")
        }
    5. 다수의 데이터 직렬화 방법
      1. NSMutableData : 키 - 값 방식으로 직렬화를 진행한다.
      2. encode : 값, 키를 파라미터로 받는 메소드로 값을 직렬화 한다.
      3. decode : 키를 파라미터로 받는 메소드로 키에 해당하는 값을 역직렬화 한다.
      4. 사용방법
        다수의 데이터 직렬화
        let mdata = NSMutableData()
        // 인코딩
        let archiver = NSKeyedArchiver(forWritingWith : mdata)
        archiver.encode(true, forKey : "BoolData")
        archiver.encode(177, forKey : "IntData")
        archiver.encode("Hello", forKey : "StrData")
        archiver.finishEncoding()

        let data = archiver.encodedData

        // 디코딩
        let unarchiver = try?NSKeyedUnarchiver(forReadingFrom : data)
        let boolData = unarchiver.decodeBool(forKey : "BoolData") // true 저장
        let intData = unarchiver.decodeInteger(forKey : "IntData") // 177 저장
        let strData = unarchiver.decodeObject(forKey : "StrData")
        // Hello 저장
    6. 커스텀 타입의 직렬화 방법
      1. NSCoding 프로토콜 : 인코딩 / 디코딩 함수를 정의하며 클래스만 가능하고 구조체는 불가능하다.
      2. 사용되는 메소드
        1. 인코딩 메소드 : func encode(withCoder aCoder : NSCoder)
        2. 디코딩 메소드 : init?(coder aDecoder : NSCoder)
      3. 사용방법
        커스텀 타입의 직렬화
        // 클래스 정의
        class Person : NSObject, NSCoding {
           var name : String
           var birthYear : Int
           // 인코딩 메소드
           func encode(with aCoder : NSCoder) {
               aCoder.encode(name, forKey : "Name")
               aCoder.encode(birthYear, forKey : "Birth")
           }
           // 디코딩 메소드
           required init(coder aDecoder : NSCoder) {
              name = aDecoder.decodeObject(forKey : "Name") as! String
              birthYear = aDecoder.decodeInteger(forKey : "Birth")
           }
           // initializer 로 정보를 받아옴
           init(name : String, birthYear : Int) {
               self.name = name
               self.birthYear = birthYear
           }
        }

        // 인코딩 / 디코딩을 사용한 객체 생성
        // 객체 생성
        var person = Person(name : "박문수", birthYear = 1355)
        // 인코딩
        let data = NSKeyedArchiver.archivedData(withRootObject : person)
        // 디코딩
        let obj = NSKeyedUnarchiver.unarchiverObject(with : data) as! Person
        print("name : \(obj.name), birthYear ; \(obj.birthYear)")
        // name : 박문수, birthYear : 1355 출력
  5. 타이머
    1. 타이머란? 수행할 동작을 특정 시간 이후에 실행하도록 하는 기능이다.
    2. Timer 클래스를 이용하여 타이머를 시작하고, 중지할 수 있다.
    3. 사용방법
      타이머 사용
      // 클래스 정의
      class Alarm : NSObject {
         // 특정 시간 이후에 실행할 동작을 정의한 메소드
         @objc func ring(timer : Timer) {
            print("Wake Up!!")
         }
      }
      // 객체 생성
      let alarm = Alarm()
      // 타이머 생성
      var timer = Timer.scheduledTimer(timeInterval : 1, target : alarm, selector:#selector(Alarm.ring), useInfo : nil, repeats : false)
      // 타이머 바로 시작
      timer.fire() // Wake Up!! 출력

      // 클로저를 이용한 Timer
      Timer.scheduledTimer(withTimerInterval : 1.0, repeats:false) {
         timer in print("클로저를 이용한 타이머")
      }
  6. 알림
    1. 알림이란? 특정 조건에 수행할 동작을 옵저버를 이용하여 동작하는 기능이다.
    2. 알림 구성
      1. 알림(Notification)
      2. 알림 센터(NotificationCenter) : 알림을 발송하고, 알림 감시자(Observer)를 등록한다.
      3. 알림 감시자(Observer) : 알림 발생 시 동작을 정의하고, 더 이상 필요하지 않다면 제거한다.
    3. 사용 방법
      알림 사용
      // 클래스 정의
      class MyClass : NSObject {
         // 특정 시간 이후에 실행할 동작을 정의한 메소드
         @objc func handleNoti(noti : Notification) {
            print("Hello Swift Custom Alarm")
         }
      }
      // 알림 센터 선언
      let notiCenter = NotificationCenter.default
      // 알림 이름 정의
      let customNoti = Notification.Name("CustomNotification")
      // 옵저버 추가
      let myClass = MyClass()
      notiCenter.addObserver(myClass, selector : #selector(MyClass. handleNoti), name: customNoti, object: nil)
      // 알림 발생
      notiCenter.post(name: customNoti, object: nil)
      // Hello Swift Custom Alarm 출력
  7. 멀티 쓰레드
    1. 쓰레드란? 오래 걸리는 작업을 사용자가 기다리지 않고 수행할 수 있도록 처리하는 기능이다.
      1. 쓰레드 사용방법
        1. 셀렉터를 이용한 쓰레드
          셀렉터를 이용한 쓰레드
          // 클래스 정의
          class MyClass : NSObject {
             // 0부터 10까지 출력하는 메소드
             @objc func countTen() {
                  for count in 0...10 {
                     print(count)
                  }
             }
          }

          // 객체 선언
          let obj = MyClass()
          // 셀렉터를 이용한 쓰레드 - 1
          obj.performSelector(inBackground : #selector(MyClass.countTen), with: nil)
          // 셀렉터를 이용한 쓰레드 - 2
          Thread.detachNewThreadSelector(#selector(MyClass.countTen), toTarget: obj, with: nil)

          sleep(10)
          // 슬립을 주지 않으면 쓰레드가 실행되기 전에 프로그램이 종료되어 쓰레드가 동작하는 것을 볼 수 없음
        2. 클로저를 이용한 쓰레드
          클로저를 이용한 쓰레드
          // 클로저를 이용한 쓰레드
          Thread.detacthNewThread {
             for count in 0...10 {
                print(count)
             }
          }

          sleep(10)
          // 슬립을 주지 않으면 쓰레드가 실행되기 전에 프로그램이 종료되어 쓰레드가 동작하는 것을 볼 수 없음
        3. Thread의 자식 클래스를 작성한 쓰레드
          Thread의 자식 클래스를 사용
          // 쓰레드 정의
          class MyThread : Thread {
             // 쓰레드가 수행할 동작 정의
             override func main() {
                 for count in 0...10 {
                     print(count)
                }
             }
          }

          let thread = MyThread()
          thread.start() // 쓰레드 동작 시작

          sleep(10)
          // 슬립을 주지 않으면 쓰레드가 실행되기 전에 프로그램이 종료되어 쓰레드가 동작하는 것을 볼 수 없음
    2. 멀티 쓰레드는 이런 쓰레드가 여러개 동작하는 것을 의미한다.
      1. 예시는 아래와 같다.
        멀티 쓰레드 - 쓰레드가 여러개 동작
        // 쓰레드 정의
        class MyThread : Thread {
           // 쓰레드가 수행할 동작 정의
           override func main() {
               for count in 0...10 {
                   print(count)
              }
           }
        }

        let thread1 = MyThread()
        thread1.start() // 쓰레드 동작 시작 - 1개
        let thread2 = MyThread()
        thread2.start() // 쓰레드 동작 시작 - 2개

        // 클로저를 이용한 쓰레드
        Thread.detacthNewThread {
           for count in 0...10 {
              print(count)
           }
        } // 쓰레드 동작 시작 - 3개

        sleep(10)
        // 슬립을 주지 않으면 쓰레드가 실행되기 전에 프로그램이 종료되어 쓰레드가 동작하는 것을 볼 수 없음
      2. 멀티 쓰레드를 조절하는 멀티 쓰레드 클래스 Operation, OperationQueue
        1. Operation은 큐를 기반으로 동작한다.
        2. 동시 동작하는 쓰레드 개수를 조절 : var maxConcurrentOperationCount : int
        3. 쓰레드 동작/취소
          1. 쓰레드 동작 : func addOperation(_ op : Operation) 함수에 추가
          2. 쓰레드 취소 : func cancelAllOperations()
        4. 큐를 이용한 쓰레드 관리 : OperationQueue 클래스
        5. 사용방법
          Operation을 이용한 멀티 쓰레드 사용
          // Operation 정의
          class MyOperation : Operation {
             // 수행할 동작 정의
             override func main() {
                 for count in 0...10 {
                     print(count)
                }
             }
          }
          // OperationQueue 선언
          let opQueue = OperationQueue()
          // 최대 실행 개수 2개까지 허용
          opQueue.maxConcurrentOperationCount = 2

          let operation1 = MyOperation()
          let operation2 = MyOperation()
          let operation3 = MyOperation()

          // 클로저를 이용한 Operation 실행
          opQueue.addOperation({
               for count in 0...10 {
                   print(count)
               }
          }) // 1개 실행
          opQueue.addOperation(operation1) // 2개 실행

          // 대기 : 한번에 최대 2개까지만 실행하게 설정
          opQueue.addOperation(operation2)
          // 위 2개 중에 하나의 실행이 끝날 때 까지 대기
          opQueue.addOperation(operation3)
          // 위 3개 중에 둘의 실행이 끝날 때까지 대기

          sleep(10)
          // 슬립을 주지 않으면 쓰레드가 실행되기 전에 프로그램이 종료되어 쓰레드가 동작하는 것을 볼 수 없음
  8. 네트워크
    1. 네트워크 사용 시 필요 정보는 URL(리소스 위치 정보), 요청(URLRequest), 응답(URLResponse) 이다.
      1. URL 정보
        1. 네트워크를 통해 접속할 위치 정보를 의미한다.
        2. URL 허용 문자는 알파벳, 숫자, 하이픈(/), 닷(.) 등으로 제한된 문자만 허용한다.
        3. 그 이외 문자는 퍼센트 인코딩 필요
          예) https://www.google.com/search?q=아이폰
          -> https://www.google.com/search?q=%EC%95%84%EC%9D%B4%ED%8F%B0
          URL을 위한 퍼센트 인코딩/디코딩
          let urlStr = "http://www.google.com?q=아이폰"
          // 인코딩
          let encoded = urlStr.addingPercentEncoding(withAllowedCharacters : .urlQueryAllowed)!
          /* 출력 : http://www.google.com?q=%EC%95%84%EC%9D%B4%ED%8F%B0 */

          // 디코딩
          let decoded = encoded.removingPercentEncoding!
          /* 출력 : http://www.google.com?q=아이폰
          */
        4. URL 분석
          1. URL 주소는 Scheme, host, path, query 로 구성된다.
            URL 구성 요소
            let url = URL(string: "http://google.com?q=iPhone&format=json")!
            // scheme
            url.scheme // http
            // host
            url.host // google.com
            // path
            url.path //
            // query
            url.query // q=iPhone&format=json
      2. 요청(URLRequest)과 응답(URLResponse)
        1. 사용 예시를 통해서 URL 주소에 요청하고 요청한 내용을 응답받는 예제는 아래와 같다.
          URL 요청과 응답 처리
          // 요청할 URL
          let url = URL(string: "http://google.com?q=iPhone&format=json")!

          // 요청 : URLRequest
          let request = URLRequest(url : url)
          let session = URLSession(configuration: URLSessionConfiguration.default)

          let task = session.dataTask(with: request) {
          // 클로저를 이용한 응답
          (data : Data?, response : URLResponse?, error : Error?) in
            let httpResponse = response as! HTTPURLResponse
            print("status code : ", httpResponse.statusCode)
            let headers = httpResponse.allHeaderFields
            for(key, value) in headers {
                print("\(key) : \(value)")
            } 
          }
          task.resume()

Swift 접근 조절


  1. 접근 조절 : 선언한 변수, 프로퍼티, 메소드, 클래스가 어느 수준까지 접근 가능한지 정의하는 것으로 Swift에서는 5단계가 있다.
    1. 접근 조절 레벨
      1. open : 다른 모듈에서 접근과 상속이 모두 가능하다.
      2. public : 다른 모듈에서 접근 가능하지만 상속은 불가능하며, 상속은 동일 모듈에서만 가능하다. 
      3. internal : 같은 모듈 내에서만 접근 및 상속이 가능하며, 기본 값이다.
      4. fileprivate : 같은 소스 파일 내에서만 접근 가능하다.
      5. private : 정의된 블록 내에서만 가능하다.
      6. 접근 조절 레벨에서 open, public은 프레임워크를 만들 경우 주로 사용된다.
    2. 접근 조절 단위 : 접근 조절 레벨을 적용할 수 있는 단위이며, 타입, 메소드, 프로퍼티가 있다.
      1. 접근 조절 단위는 프로퍼티, 메소드, 타입 순으로 포함이되며, 타입이 private이면, 메소드, 프로퍼티는 모두 private만 가능하다.

Swift 제네릭스

  1. 제네릭스 : 타입에 대한 추상화를 시키는 프로그램 기법으로 타입별로 작성할 필요없이 어떤 타입이든 적용가능하다.
  2. 사용방법
    1. 배열에서의 사용 : 배열 정의 시에 Element 키워드를 이용하여 타입 추상화를 시킴
      배열에서의 제네릭스
      // 제네릭스 프로그램 기법 이용한 배열 선언
      struct Array<Element> {
         func append(_ newElement : Element) -> Void
         func remove(at index : Int) -> Element
      }

      // 선언한 배열 사용
      // String 형 배열
      var strArray = Array<String>()
      strArray.append("Hello")
      strArray.append("Swift")

      strArray.remove(at index : 0)

      // Int 형 배열
      var intArray = Array<Int>()
      intArray.append(10)
      intArray.append(11.1) // 에러 발생
      /* Swift는 강한 타입 체크로 Int형 값을 넣으라는 에러 발생 */
    2. Dictionary에서의 사용(두 종류 타입 추상화 가능 )
      Dictionary에서의 제네릭스
      // 제네릭스 프로그램 기법 이용한 Dictionary 선언
      struct Dictionary<Key : Hashable, Value> {
         func updateDicValue(_ value : Value, forKey : Key) -> Value?
         func remoteDicValue(forKey : Key) -> Value?
      }

      // 선언한 Dictionary 사용
      // Key가 Int, Value가 String인 Dictionary
      var intAndstrDic = Dictionary<Int, String>()
      intAndstrDic.updateDicValue("Hello", 0)

      var strAndstrArrayDic = Dictionary<String, [String]>()
      strAndstrArrayDic.updateDicValue("1월", ["Jan","1M"])
    3. 함수에서의 사용
      함수에서의 제네릭스
      // 출력함수에 어떤 타입이든 출력가능하게 제네릭스로 선언
      func printValue<T> (_ value : T) {
         print("데이터 출력 : \(value)")
      }

      // 타입에 상관없이 출력 가능
      printValue("Hello") // 데이터 출력 : Hello 출력
      printValue(3) // 데이터 출력 : 3 출력
    4. 클래스에서의 사용
      클래스에서의 제네릭스
      // 타입 추상화를 이용하여 클래스 선언
      class Hospital<T> {
         func inHospital(patient : T) {
             print("병원에 입원")
         }
      }

      // Human
      class Human {}
      // Pet
      class Pet {}

      // Hospital 클래스 선언 : Human 클래스를 이용
      var hospital = Hospital<Human>()
      // Hospital 클래스 선언 : Pet 클래스를 이용
      var petHospital = Hospital<Pet>()

Swift Any와 타입체크

  1. Swift 에서의 타입 체크는 강한 타입체크로 변수/상수 선언 시에 정해진 타입을 확정하면 다른 타입의 데이터는 대입할 수 없다. (선언한 타입으로 대입하라고 에러 발생)
  2. Any를 이용하여 타입에 자유로운 가변 타입으로 선언하여 타입을 확정하지 않고, 사용 시에 필요한 타입을 사용할 수 있다.
    1. 사용 방법
    2. 위 코드를 보면 타입을 확정하여 선언 시와 다르게 다른 타입을 대입해도 에러가 발생되지 않는다.
    3. 가변 타입 Any는 타입에 대한 정보가 없기 때문에 타입별로 사용하는 메소드, 프로퍼티는 사용할 수 없고, 타입 체크 후에 타입을 확정하는 과정이 필요하다.
      1. 타입 정보 얻는 방법 : type(of : )
        타입 정보 얻기
        // Any를 이용하여 타입 선언
        var anyValue : Any = 10
        print(type(of : anyValue)) // Int를 출력
      2. 타입 체크하는 방법 : is Type명
        타입 체크
        // Any를 이용하여 타입 선언
        var anyValue : Any = 10
        if anyValue is Int { // is를 이용한 타입 체크
            print("정수 타입")
        } else {
            print("정수 이외의 타입")
        }

        // 상속과 타입 체크
        class Animal {} // Parent 클래스
        class Dog : Animal {} // Child 클래스
        class Cat : Animal{} // Child 클래스

        var bingo : Animal = Dog("빙고")
        bingo is Dog // true
        bingo is Animal // true
        /* Child 클래스는 Parent 클래스와 is a 관계가 성립하여 타입체크 시 Parent 클래스로 확인해도 true가 반환 */
        bingo is Cat // false
      3. 타입 변환하는 방법 : as
        타입 변환
        // Any를 이용하여 타입 선언
        var anyValue : Any = 10
        let intVal1 = anyValue as Int // 타입 변환이 확실한 경우
        let intVal2 = anyValue as? Int // 타입 변환 불가 시 nil
        let strVal = anyValue as! String
        /*강제 타입 변환으로 변환 실패 시 컴파일 에러 발생*/

        // 상속과 타입 변환
        class Animal {} // Parent 클래스
        class Dog : Animal {} // Child 클래스
        class Cat : Animal{} // Child 클래스

        var bingo = Dog()
        var animal = bingo as Animal // 타입 변환 성공
        /* Child 클래스에서 Parent 클래스로 타입 변환하는 업 캐스팅은 항상 성공한다. */

        var obj : Animal = Cat()
        let cat1 = obj as Cat // 컴파일
        let cat2 = obj as? Cat // 변환 성공, 옵셔널 타입
        // let cat2 : Cat? = Cat() 과 동일
        let cat3 = obj as! Cat // 변환 성공, Non-옵셔널 타입
        // let cat3 : Cat = Cat() 과 동일
        /* Parent 클래스에서 Child 클래스로 타입 변환하는 다운 캐스팅은 as 인 경우를 제외하고 옵셔널이나 강제 변환은 가능하다. */

Swift 에러처리

  1. 프로그램에서 에러 발생 : 프로그램 실행 중에 예기치 못한 상황이 발생한 상태로 프로그램이 강제 종료되어 앱이 크래쉬되는 상황 또는 제대로 동작을 수행하지 못하는 상황이 발생한 상태를 의미한다.
    예) 파일 처리 중 디스크 에러, 파일 처리 중 권한 부족으로 인한 에러
  2.  에러 발생 시 사용하는 API
    1. 메소드에 throws 키워드를 넣어 선언
      throws 로 선언
      // 메소드 선언 시에 throws 키워드 사용하여 선언
      func openFile(toFile : String) throws {
         // 파일 열기에 대한 동작 정의
      }

      /* throws로 선언한 메소드는 반드시 try 키워드를 붙여서 사용*/
      try str.openFile(toFile : sFilePath)
      /* 위 상태에서 에러 발생해도 강제 종료가 되며 3번 에러 다루기에서 강제 종료가 발생되지 않도록 처리부분 정의*/
    2. Error 프로토콜을 채택한 객체의 에러 반환
      throw 를 이용한 에러 반환
      /* Enum으로 Error 프로토콜을 채택하여 커스텀 에러 정의
      자세한 것은 4번 커스텀에러에서 다룬다.
      */
      enum CustomError : Error {
         case myFault
         case yourFault
      }

      // 정의된 에러 반환
      func inputPositive(val : Int) throws {
         guard (val > 0) else {
             throw CustomError.yourFault // 에러 반환
             // Error 프로토콜을 채택하면 반환
         }
         print("정상 수행")
      }
  3. 에러 다루기
    1. 에러가 발생하면 앱의 강제 종료를 방지하기 위해서 do-catch 문을 사용한다.
    2. do 내에 코드 실행 중 에러가 발생하게 되면 catch 내 코드를 실행하게 된다.
      do-catch 문 사용
      // 메소드 선언 시에 throws 키워드 사용하여 선언
      func openFile(toFile : String) throws {
         // 파일 열기에 대한 동작 정의
      }

      do {
          try str.openFile(toFile : sFilePath)
      } catch {
          print("파일 열기 중 에러 발생")
      }
      /* do 블록 내의 코드 실행 중에 에러 발생 시 catch 블록 내의 코드를 실행한다.*/
    3. do-catch문은 자바의 try-catch문과 유사하다.
      do-catch 문 사용 (Swift)
      try-catch 문 사용 (Java)
      do {
         // 에러 발생 가능 코드
         try obj.throwsMethod
      } catch {
         // 에러 발생 처리 코드
      }
      try {
         // 에러 발생 가능 코드
      } catch(exception e) {
         // 에러 발생 처리 코드
      }
  4. 커스텀 에러
    1. 특정 에러를 별도로 정의해서 사용하는 것으로 Error 프로토콜을 채택하여 사용한다.
      커스텀 에러 정의
      // enum을 이용한 정의 ()
      enum CustomError : Error {
         case myFault
         case yourFault
      }

      // enum으로 정의된 에러 다루기
      do {
          throw CustomError.yourFault
      } catch CustomError.myFault {
         print("내탓")
      } catch CustomError.yourFault {
         print("니탓")
      }
      // Switch-Case와 비슷하게 사용

      // 구조체를 이용한 정의
      struct CustomErrorStruct : Error {
         var msg : String
      }

      // 클래스를 이용한 정의
      class CustomErrorClass : Error {
      }

      // 구조체나 클래스는 where을 이용한 타입 체크로 구분
      do {
          // 에러 발생 시키기
          let error = CustomErrorStruct(msg : "Oh! My God!!")
          throw error // 에러 반환
      catch where error is CustomErrorStruct {
         print("구조체로 작성한 에러 발생")
      catch where error is CustomErrorClass  {
         print("클래스로 작성한 에러 발생")
      }
  5. 클린업(defer)
    1. 에러가 발생여부와 상관없이 끝나기 전에 블록 내의 코드를 실행한다.
    2. 에러가 발생해도 실행되고, 정상 종료 시에도 실행된다.
    3. 자바의 try-catch-finally에서 finally와 유사한 기능이다.
    4. 사용 방법
      defer 사용법
      // defer는 예외가 발생하는 곳에 작성
      func dangerousFunc() throws {
         defer{
             print("메소드 실행 후 마지막 동작 정의")
         }
         // throw 보다 이전에 작성한다.
         throw CustomErrorStruct(msg : "에러 발생")
      }

Swift 프로토콜과 extension

  1. 프로토콜이란? 오직 인터페이스만 있는 상태로 구현하는 부분없이 프로퍼티나 메소드를 선언한 형태로 정의된 API를 의미한다.
    1. 프로토콜 정의와 채택(사용)
      1. 프로토콜 정의
        프로토콜 정의
        protocol PROTOCOL_NAME {
        // 프로퍼티 선언
        // 메소드 선언
        }
      2. 프로토콜 채택 : 클래스나 구조체에서 정의된 프로토콜을 이용하는 것을 프로토콜을 채택했다고 한다. 프로토콜 채택 시에는 프로토콜에 정의된 프로퍼티, 메소드는 반드시 클래스나 구조체에서 구현이 되어야 한다.
        프로토콜 채택
        // 프로토콜 정의
        protocol PROTOCOL_NAME {
        // 프로퍼티 선언
        // 메소드 선언
        }

        // 클래스에서 프로토콜 채택
        class CLASS_NAME : PROTOCOL_NAME {
        }
        // 구조체에서 프로토콜 채택
        struct STRUCT_NAME : PROTOCOL_NAME {
        }
      3. 클래스의 상속과 프로토콜 : 클래스에서는 부모 클래스를 상속하고, 프로토콜 채택을 모두 할 수 있으며 아래와 같이 정의한다.
        클래스 상속과 프로토콜
        // 프로토콜 정의
        protocol PROTOCOL_NAME {
        // 프로퍼티 선언
        // 메소드 선언
        }
        // 부모 클래스
        class PARENT_CLASS {
        }

        // 자식 클래스에서 부모 클래스 상속과 프로토콜 채택
        class CHILD_CLASS : PARENT_CLASS, PROTOCOL_NAME {
        }
        /* 부모 클래스를 먼저 쓰고, 그 다음에 콤마 다음에 프로토콜을 쓴다.*/
      4. 프로토콜 상속 : 프로토콜은 다중 상속이 가능하여 여러 프로토콜을 상속할 수 있다.
        프로토콜 정의
        protocol Singing {
           func sing() {}
        }

        protocol Dancing {
           func dance() {}
        }
        // Singing, Dancing에 정의된 메소드를 모두 구현해야한다.
        protocol Entertaining : Singing, Dancing {
        }

        // Entertaining 프로토콜 채택 시 상속된 모든 메소드를 구현
        class Human : Entertaining {
            func sing() {print("라라라")}
            func dance() {print("둠칫둠칫")}
        }
      5. 프로토콜 내 프로퍼티 선언 : 프로퍼티의 타입 선언 가능하고, 구현 시에 저장 프로퍼티로 사용할지 계산 프로퍼티로 사용할지 프로토콜 채택 후에 정한다.
        프로퍼티 선언
        protocol Singing {
            // 프로퍼티 선언
            var duration : Int {get set}
        }

        // 저장 프로퍼티로 구현
        struct MyStruct : Singing {
           var duration : Int
        }

        // 계산 프로퍼티로 구현
        class MyClass : Singing {
           var duration : Int {
              get { return 0 }
              set { }
           }
        }
      6. 프로토콜 내 Initializer 선언 : 프로토콜 내에 Initializer 선언이 가능하고, 클래스에서 구현 시에는 required를 추가한다.
        프로토콜 내 Initializer 구현
        protocol Singing {
            var sing : String
            // Initializer
            init(sing : String)
        }

        // 클래스가 프로토콜을 채택 시 Initializer 구현
        class Monster : Singing {
            required init(sing : String) {
                self.sing = sing
            }
        }

        // 구조체가 프로토콜을 채택 시 Initializer 구현
        struct Boss : Singing {
            init(sing : String) {}
        }
      7. 프로토콜을 타입으로 사용 시 채택한 클래스나 구조체에 별도의 메소드가 구현되어 있어도, 프로토콜 내의 메소드, 프로퍼티만 사용 가능하다.
        프로토콜 내 Initializer 구현
        protocol Singing {
            // 메소드
            func sing() {}
        }

        // 클래스가 채택하여 구현
        class Human : Singing {
            func sing() {
               print("랄랄랄")
            }

            func dancing() {
               print("쿵쿵쿵")
            }
        }

        // 프로토콜을 타입으로 사용
        var singingAnimal : Singing = Human()
        singingAnimal.sing()
        singingAnimal. dancing() // 사용 못함
  2. extension(확장)
    1. 기존에 작성된 타입(클래스나 구조체, enum)을 확장해서 사용할 수 있다.
    2. extension을 할 경우 하나의 타입으로 동작한다.
      extension 예시
      class Dog{
         func eat() {
            print("뭐든 잘 먹어요.")
         }
      }

      // 확장
      extension Dog {
          func sing() {
              print("멍~멍~")
          }
      }

      var myDog = Dog()
      myDog.eat()
      myDog.sing() // 확장한 메소드 사용 가능
    3. extension이 가능한 것과 불가능한 것
      1. 가능한 것
        1. convenience initializer
        2. 계산 프로퍼티
        3. 메소드
        4. 프로토콜
        5. 서브스크립트
        6. nested type
      2. 불가능한 것
        1. designated initializer
        2. 저장 프로퍼티
  3. 프로토콜과 프로토콜 extension
    1. 기존에 정의된 프로토콜을 확장하여 사용 가능하며, 프로토콜의 다중 상속을 이용하여 코드 중복을 없애는데 사용된다.
    2. 코드 중복이 발생되는 상황
      코드 중복 발생
      class Bird{
         func move() {
             print("날아서 이동")
         }
         func sound() {
             print("소리를 낸다.")
         }
      }

      class Airplane{
         func move() {
             print("날아서 이동")
         }
         func sound() {
             print("소리를 낸다.")
         }
      }
    3. 코드 중복을 없애는데 프로토콜 확장을 사용
      프로토콜 확장으로 코드 중복 제거
      protocol Movable() { }
      extension Movable {
         func move() {print("날아서 이동")}
      }

      protocol Soundable {}
      extension Soundable {
         func sound() {print("소리를 낸다.")}
      }

      // 중복되는 메소드를 프로토콜 확장으로 제거
      class Bird : Movable, Soundable {
      }

      class Airplane : Movable, Soundable {
      }

Swift 클로저

  1. 클로저란? 함수를 객체처럼 사용하며, 함수처럼 정의한 후에 사용 시에 부가 기능을 더 추가할 수 있도록 한 API를 의미하며 형태는 함수나 코드 블록으로 구성된다.
    1. 클로저 사용 API
      1. 클로저의 함수 타입을 결정한다.
        1. 함수 타입 : 함수 객체를 참조 변수로 사용할 때의 타입을 의미한다.
          1. 사용방법
            1. 파라미터가 없고, 반환값이 없는 함수 타입 : () -> (), () -> Void
            2. 파라미터가 있고, 반환값이 있는 함수 타입 : (Int) -> Int
      2. 클로저 표현식
        1. 별도의 함수 정의 없이 Inline 방식으로 작성한다.
          클로저 표현식
          // 방식
          { (파라미터, ...) -> Return Type in
             // 코드
          }

          // 예시
          array.sorted(by : {(a : Int, b : Int) -> Bool in
             return a < b
          })
        2. 클로저 표현식 단축
          1. 파라미터 타입 선언 생략
            array.sorted(by : {a, b -> Bool in return a < b})
          2. 1줄 return인 경우 return 생략
            array.sorted(by:{a, b -> Bool in a<b})
          3. 반환 타입 선언 생략
            array.sorted(by:{a, b in a<b})
          4. 파라미터 선언 생략
            array.sorted(by : {$0 < $1})
      3. 반환 값으로 사용한다.
        반환 값으로 사용
        // 반환 값의 함수 타입 : () -> Void
        func greeting() -> (() -> Void) {
           // 함수 타입이 () -> Void 로 함수로 구성
           func sayGoodmorning() {
               print("Good Morning Everyone")
           }
           return sayGoodmorning
        }

        // 사용하기
        let ret = greeting()
        ret() // Good Morning Everyone 출력

        // 클로저 표현식으로 작성(Inline으로 작성)
        // 코드 블록으로 구성
        func greeting() -> (() -> Void) {
           return { () -> Void in
               print("How are you")
           }
        }
      4. 파라미터로 사용한다.
        파라미터 사용하기
        // 원래 함수
        func add(_ i : Int, _ j : Int) -> Int {
           let sum = i + j
           return sum
        }
        // 함수 타입 : (Int) -> Void
        func add(_ i : Int, _ j : Int, _ handle : ((Int) -> Void)) {
           let sum = i + j
           handle(sum)
        }


        // 사용하기
        add(3, 4, {(result : Int) -> Void in
            print("3 + 4 = \(result)")
        })
      5. 프로퍼티에 사용한다.
        프로퍼티에 사용
        // 클래스 정의
        // 함수 타입 : () -> Int
        class MyClass {
           var property : (() -> Int)!
        }

        // 사용하기
        var obj = MyClass()
        obj.propery = {
           return 0
        }
      6. 클로저에서 프로퍼티 접근 : self를 사용하여 접근
        프로퍼티 접근
        // 클래스 정의
        // 함수 타입 : () -> Void
        class MyClass {
           var value = 0
         
           func showAndPrint() -> (() -> Void) {
               return {
                   print(self.value)
               }
           }
        }
      7. 클로저와 타입 알리아스
        1. 타입 알리아스는 타입에 이름을 주어 해당 타입 대신에 타입 알리아스로 정한 이름으로 대체가 가능하게 한 방법이다.
          타입 알리아스
          // 타입 알리아스 정의
          typealias Century = Int
          // 타입 알리아스 사용
          var thisEra : Century = 12 // Int 대신 사용 가능

          // 클로저에서 타입 알리아스 활용
          // 원래 함수
          func add(_ i : Int, _ j : Int) -> Int {
             let sum = i + j
             return sum
          }
          // 함수 타입 : (Int) -> Void
          func add(_ i : Int, _ j : Int, _ handle : ((Int) -> Void)) {
             let sum = i + j
             handle(sum)
          }
          // 타입 알리아스 정의
          typealias ResultHandler = (Int) -> Void
          // 함수 타입 : (Int) -> Void
          func add(_ i : Int, _ j : Int, _ handle : ResultHandler) {
             let sum = i + j
             handle(sum)
          }
          // 타입 알리아스에 정의된 이름으로 대체 가능

Swift 구조체와 enum

  1. 구조체란? 프로그래밍 언어에서 의미가 연결된 한 덩어리로 처리하는 방식으로 클래스처럼 프로퍼티와 메소드로 구성된다.
    1. 구조체와 클래스의 관계
      1. 구조체와 클래스에 모두 있는 것
        1. 프로퍼티와 메소드
        2. 초기화(Initializer)
      2. 구조체에는 없고 클래스만 있는 것
        1. 상속
        2. 재정의
        3. ARC(Automatic Reference Count)
      3. 구조체와 클래스의 속성
        1. 클래스는 레퍼런스 타입
          1. 클래스의 경우 객체를 대입하면 참조 추가로 동작하여 한 객체의 속성 값을 공유하게 된다.
          2. 아래는 레퍼런스 타입의 객체 공유를 나타내는 그림이다.
          3. 위에서 obj1과 obj2가 동일한 클래스를 가진 객체라면 obj2 = obj1으로 객체를 대입하게 되면 하나의 객체를 가리키게 되고, 둘 중에 하나라도 값을 변경하면 나머지 하나도 변경된 값으로 반영된다.
        2. 구조체는 밸류 타입
          1. 구조체의 경우 객체를 대입하게되면 복사가 되어 한 객체의 속성 값이 바뀌어도 다른 객체의 속성 값은 변경되지 않는다.
          2. 아래는 밸류 타입의 객체 복사를 나타내는 그림이다.
          3. 위에서 obj1과 obj2가 동일한 구조체를 가진 객체라면 obj2 = obj1으로 객체를 대입하게 되면 객체가 복사가 되어 또 다른 객체를 만들게 되며, 한 객체의 값을 변경해도 다른 객체에는 영향을 주지 않는다.
    2. 구조체의 정의
      1. 정의 방법 : struct[구조체 이름]{ // 프로퍼티, 메소드 구현}
      2. 구조체의 객체 생성과 사용
        구조체의 객체 생성과 사용
        // 구조체 정의
        struct Point {
            var x = 0
            var y = 0

            func description() -> String {
                return "Point : \(x), \(y)"
            }
        }

        // 구조체 생성
        var point = Point()
        point.x = 10
        point.y = 20
        // 해당 부분은 var point = Point(x : 10, y : 20) 가능
        // => 구조체에서는 프로퍼티를 자동으로 초기화한다.
        print(point.description())
      3. 구조체의 초기화(Initializer)
        1. 초기화가 필요한 프로퍼티 정의
          초기화가 필요한 프로퍼티 정의
          // 구조체 정의
          struct Point {
              var x : Int
              var y : Int

              func description() -> String {
                  return "Point : \(x), \(y)"
              }
          }

          // 구조체 생성
          var point1 = Point() // 에러 발생
          // init(x : Int, y : Int) 가 있는 것과 동일한 상태
          var point2 = Point(x : 10, y : 20)
        2. Initializer 정의
          구조체에 Initializer 정의
          // 구조체 정의
          struct Point {
              var x : Int
              var y : Int

              init() {
                  self.x = 0
                  self.y = 0
              }
          }

          // 구조체 생성
          var point1 = Point()
          var point2 = Point(x : 10, y : 20) // 에러 발생
          // Initialize 정의 시는 프로퍼티 자동 초기화는 되지 않음
      4. 구조체의 프로퍼티 수정
        1. 초기화 메소드에서 설정 가능
        2. 구조체 외부에서 설정 가능
        3. 구조체 내부에서 변경 시 메소드 선언에 mutating 키워드 추가 후 설정 가능
        4. 구조체의 프로퍼티 수정은 아래와 같다.
          구조체의 프로퍼티 수정
          // 구조체 정의
          struct Point {
              var x : Int
              var y : Int
              // 초기화 메소드 사용
              init() {
                  self.x = 0
                  self.y = 0
              }
           
             // 구조체 내부에서 설정
             mutating func moveTo(x : Int, y : Int) {
                 self.x = x
                 self.y = y
             }
          }

          // 구조체 생성
          var point = Point()
          // 구조체 외부에서 설정
          point.x = 10
          point.y = 20
  2. Enum이란? 열거형 타입으로 원소를 정의하여 그 원소 내에서만 사용하는 데이터 타입의 일종으로 enumeration 에서 따왔다.
    1. Enum 정의
      Enum 정의
      // Enum 정의
      enum Pet {
         case cat
         case dog
         case bird
      }

      enum Week : Int {
          case sun, mon, tue, wed, thur, fri, sat
      }
      // Int 형으로 정의 시 sun은 0이며, 하나씩 증가하게 된다.
      // mon = 1, tue = 2, ...

      enum Day : String {
          case am = "오전"
          case pm = "오후"
      }
      // Int 형 이외에는 값을 반드시 넣어주어야 한다.
    2. Enum 사용
      Enum 사용
      // Enum 정의
      enum Pet {
         case cat
         case dog
         case bird
      }
      // enum 객체 정의
      var pet : Pet
      pet = Pet.cat
      pet = Pet.dog
      pet = Pet.other // 없는 값이므로 에러 발생

      // enum은 주로 Switch 문과 사용
      switch pet {
      case Pet.cat :
              print("고양이")
      case .dog :
              print("강아지")
      case .bird :
              print("새")
      }

      // enum의 원소의 값 얻기
      pet.rawValue // dog를 얻을 수 있음

    3. Enum의 계산 프로퍼티와 메소드
      Enum 사용
      // Enum 정의
      enum Pet {
         case cat
         case dog
         case bird

         // 계산 프로퍼티 정의
         var name : String {
               switch self {
               case .cat : return "고양이"
               case .dog : return "강아지"
               case .bird : return "새"
               }
         }

         func description() -> String {
              return self.name
         }
      }
      // enum 객체 정의
      var pet : Pet
      pet = Pet.cat
      print(pet.description()) // 고양이 출력
      pet = Pet.dog
      print(pet.description()) // 강아지 출력

Swift 메모리 관리

  1. 메모리 관리는 객체를 생성하면 메모리를 차지하게 되는데 메모리의 공간은 한계가 있기 때문에 필요한 객체가 차지하는 메모리는 유지하고 필요없는 객체의 메모리를 해제하여 공간을 효율적으로 사용하도록 하는 방법이다.
    1. 객체가 사용 중인지 여부는 레퍼런스 카운트(Reference Count)가 관리한다.
      1. 객체를 사용하게되면 레퍼런스 카운트가 증가되고, 사용이 끝나면 레퍼런스 카운트를 감소한다.
      2. 만약 레퍼런스 카운트가 0이 되면, 객체를 사용하는 부분이 없어 메모리에서 해제를 하는 방식으로 메모리를 관리한다.
      3. 레퍼런스 카운트는 이전에는 수동으로 관리를 했으나 현재는 ARC(Automatic Reference Count)를 이용하여 자동으로 관리한다.
      4. ARC(Automatic Reference Count)
        1. 객체의 레퍼런스 카운트 관리 코드를 자동으로 생성하여 관리한다.
        2. 레퍼런스 타입(클래스) 객체에만 적용한다.
        3. Value Type(구조체, enum) 객체에서는 적용되지 않는다.
    2. 객체의 메모리 소유와 해제
      1. 메모리 소유 : 객체 생성 시에 메모리를 소유하게 된다.
        객체 생성
        class MyClass {
        }

        var ptr : MyClass! = MyClass()
        // 객체를 생성하여 메모리를 소유한다.
      2. 메모리 해제 : 객체에 nil을 대입하여 레퍼런스 카운트가 0이 되어 메모리에서 해제된다.
        객체 해제
        var ptr : MyClass! = MyClass()

        ptr = nil // 객체를 해제하여 메모리를 해제한다.
      3. 강한 참조 : 객체에 nil을 대입하면 무조건 메모리가 해제되는 것이 아니라 레퍼런스 카운트가 0이되어 메모리가 해제되는 것이며, 다른 객체를 참조하고 있다면 레퍼런스 카운트가 0이 되지 않아 메모리 해제가 발생되지 않는다.
        강한 참조
        var ptr : MyClass! = MyClass() // RC = 1

        var anotherPointer = ptr // RC = 2

        ptr = nil
        // RC = 1로 레퍼런스 카운트가 0이 되지 않아 메모리에서 해제되지 않는다.

        // RC = 0을 만들기 위해서 아래의 코드가 필요하다.
        // anotherPointer = nil : RC = 0이되어 메모리 해제 발생
      4. 메모리 해제 확인은 deinit 메소드로 확인한다.
        메모리 해제 확인
        class MyClass {
            deinit {
                print("메모리 해제")
            }
        }
    3. 강한 참조로 순환 참조
      1. 앞에 강한 참조를 이용하여 서로 다른 클래스가 상대방을 소유하고 있다면 레퍼런스 카운트가 0이 되지 않아 메모리가 할당된 상태가 유지가 된다.
      2. 해당 상황을 표현한 그림은 아래와 같다.
      3. 위 상황을 보면 Car 클래스는 Engine 클래스의 객체를 생성하고, Engine 클래스는 Car 클래스의 객체를 생성하여 서로 상대방의 클래스의 객체를 생성하여 서로 레퍼런스 카운트를 증가시켜주고 있다.
      4. 코드로 나타내면 아래와 같이 된다.
        강한 순환 참조
        class ClassA {
            var objB : ClassB!
            deinit {
                print("ClassA 객체 해제")
            }
        }

        class ClassB {
            var objA : ClassA!
           deinit {
                print("ClassB 객체 해제")
            }
        }

        var a : ClassA! = ClassA() // ClassA RC = 1
        var b : ClassB! = ClassB() // ClassB RC = 1

        a.objB = b // ClassB RC = 2
        b.objA = a // ClassA RC = 2

        a = nil
        // ClassA RC = 1 : RC 가 0이 되지 않아서 메모리 할당유지
        b = nil
        // ClassB RC = 1 : RC 가 0이 되지 않아서 메모리 할당유지
    4. 약한 참조
      1. 강한 순환 참조는 서로 상대방 클래스의 RC 를 늘려 정작 본인 클래스의 메모리 해제 시에 RC 가 0이 되지 않아서 메모리 해제가 발생되지 않는데 객체를 소유하지 않는 약한 참조(weak, unowned)를 이용하여 강한 참조를 발생하지 않을 수 있다.
      2. weak
        1. 참조하던 객체가 해제가 되면 자동으로 nil이 되는 구조이다.
        2. nil이 되므로 옵셔널 타입으로 선언한다.
        3. 상호 독립적으로 존재하는 객체에 사용한다.
        4. 사용 방법
          약한 참조 : weak
          // Person과 Phone이 서로 독립적인 존재
          class Person {
             weak var phone : Phone!
             deinit {
                 print("Person 객체 해제")
             }
          }

          class Phone {
             var person : Person!
             deinit {
                 print("Phone 객체 해제")
             }
          }

          var person : Person! = Person() // Person RC = 1
          var iphone : Phone! = Phone() // Phone RC = 1

          // 순환참조
          iphone.person = person // Person RC = 2
          person.phone = iphone
          // Phone RC = 1 : 약한참조로 소유하지 않음
          // 객체 해제 확인
          person = nil // Person RC = 1
          iphone = nil // Phone RC 0, Person RC = 0
          /* Person, Phone 클래스 모두 레퍼런스 카운트가 0이 되어 메모리 해제 발생 */
      3. unowned
        1. 참조하던 객체가 해제가 되어도 nil로 변하지 않아 Dangling Pointer의 위험이 있다.
        2. 옵셔널 타입으로 선언할 수 없어 Initializer 가 필요하다.
        3. 완전히 종속적인 경우에 사용한다.
        4. 사용 방법
          약한 참조 : unowned
          class Country {
              var capital : City!
          }

          class Capital {
             unowned var country : Country
             init(country:Country) {
                 self.country = country
             }
          }

          var korea : Country!  = Country() // Country RC = 1
          var seoul : Capital! = Capital(country:korea)
          // Country RC = 1 : 약한 참조로 소유하지 않음
          // Capital RC = 1
          // 순환 참조
          korea.capital = seoul
          // Capital RC = 2

          // 객체 해제 확인
          korea = nil // Country RC = 0
          seoul = nil
          // Capital RC = 1, Country RC = 0
          // Capital RC = 0
          /* Country, Capital 클래스 모두 레퍼런스 카운트가 0이 되어 메모리 해제 발생 */

Swift 상속

  1. 상속이란? 기존(부모) 클래스에 구현된 변수(프로퍼티)/메소드를 상속받은 새로운(자식) 클래스가 그대로 사용가능한 상태를 의미한다.
  2. Swift 클래스 상속
    1. 클래스의 상속은 단일 상속만 허용한다. 즉 자식 클래스는 하나의 부모 클래스만 상속한다.
    2. 문법
      클래스 상속 문법
      class Child Class Name : Parent Class Name {
      }
  3. 클래스 상속과 포인터
    1. self, super
      1. self 포인터는 현재 자신의 클래스 내에 있는 프로퍼티/메소드에 접근 시에 사용한다.
      2. super 포인터는 자신이 상속하고 있는 부모 클래스의 프로퍼티/메소드 접근 시에 사용한다.
      3. self 포인터와 super 포인터 사용 예시는 아래와 같다.
        self 포인터와 super 포인터
        // 부모 클래스
        class Parent {
           func description() -> String { return "Parent Class" }
        }

        // 자식 클래스
        class Child {
            // 메소드 재정의
            override func description() -> String {
                return "Child Class"
            }

            func printDescription() {
                print("super.description : \(super.description())")
                // "Parent Class" 출력
                print("self.description : \(self.description())")
                // "Child Class" 출력
            }
        }
  4. 클래스 상속과 재정의
    1. 클래스 상속은 부모 클래스의 프로퍼티/메소드를 자식 클래스에서 그대로 사용 가능하다.
    2. 메소드 재정의
      1. 부모 클래스에 있는 상속받은 메소드(같은 이름의 메소드)를 자식 클래스에서 다른 동작을 하도록 다시 정의하는 것을 메소드 재정의 라고 한다.
      2. 메소드 재정의는 메소드 앞에 override 키워드를 이용하여 재정의한다.
        메소드 재정의
        // 부모 클래스
        class Parent {
            var value = 0
             func hello() {
                print("Hello Parent Class")
             }
        }

        // 자식 클래스
        class Child : Parent { // Parent 클래스 상속
            override func hello() { // 동일 이름의 다른 동작
               print("Hello Child Class")
            }
        }

    3. 프로퍼티 재정의
      1. 부모 클래스에 정의된 프로퍼티를 재정의 한다.
        1. 프로퍼티 재정의 시에도 메소드 재정의와 동일하게 앞에 override 키워드를 이용한다.
        2. 저장 프로퍼티 재정의
          1. 방법 : willSet/didSet 행위를 추가해준다.
            저장 프로퍼티 재정의
            // 사각형의 부모 클래스
            class Rectangle {
                var width = 0
                var height = 0
                var size : Int {
                    get{return width * height}
                }
            }

            // 정사각형의 자식 클래스
            class Square : Rectangle { // Rectangle 클래스를 상속
                  override var width : Int {
                      didSet(newValue) {
                          // 새로 추가되는 행위
                          self.width += newValue
                      }
                  }
            }
        3. 계산 프로퍼티 재정의
          1. 방법 : get/set 행위를 재정의해준다.
            계산 프로퍼티 재정의
            // 사각형의 부모 클래스
            class Rectangle {
                var width = 0
                var height = 0
                var size : Int {
                    get{return width * height}
                }
            }

            // 정사각형의 자식 클래스
            class Square : Rectangle { // Rectangle 클래스를 상속
                  override var size : Int {
                      get {
                             height = width
                             return width * height
                      }
                  }
            }
    4. 재정의 시 주의사항
      1. 재정의가 필요한 프로퍼티/메소드에 override 키워드 누락 시에 에러 발생
      2. 재정의하는 대상이 아닌 프로퍼티/메소드에 override 키워드 붙이는 경우 에러 발생
      3. 부모 클래스의 프로퍼티/메소드 앞에 final 키워드가 붙은 경우는 재정의가 금지된 것으로 자식 클래스에서 재정의 할 수 없다.
  5. 클래스 상속과 초기화
    1. 자식 클래스는 부모 클래스의 프로퍼티를 상속하게 된다.
    2. 부모 클래스에 초기화가 필요한 프로퍼티가 있는 경우, 자식 클래스에서의 초기화는 아래와 같다.
      1. 자식 클래스에 Designated Initializer 가 없는 경우 부모 클래스의 Designated, Convenience Initializer 모두 상속한다.
        부모 클래스의 Initializer 상속
        // 부모 클래스
        class Parent {
            var a : Int
            init(a : Int) {
                self.a = a
            }
            convenience init() {
                self.init(a : 10)
            }
        }

        // 자식 클래스
        class Child : Parent {
            // 부모 클래스에 있는 Initializer 를 모두 상속
        }

        // 객체 생성
        var ChildObj1 = Child(a : 20) // Designated Initializer 상속
        var ChildObj2 = Child() // Convenience Initializer 상속
      2. 자식 클래스에 Designated Initializer 가 있는 경우 부모 클래스의 Designated, Convenience Initializer 모두 상속하지 못한다.
        부모 클래스의 Initializer 상속 못함
        // 부모 클래스
        class Parent {
            var a : Int
            init(a : Int) {
                self.a = a
            }
            convenience init() {
                self.init(a : 10)
            }
        }

        // 자식 클래스
        class Child : Parent {
            var b : Int
            init(a : Int, b : Int) {
                self.b = b
                super.init(a : a)
            }
            /* 자식 클래스에 Designated Initializer 가 있는 경우 부모 클래스의 Initializer 를 상속받지 못함 */
        }

        // 객체 생성
        // 자식 클래스의 Designated Initializer
        var ChildObj1 = Child(a : 20, b : 30)
        // 부모 클래스의 Initializer 를 상속 받지 못함
        var ChildObj2 = Child(a : 20)
        var ChildObj3 = Child()
      3. 자식 클래스에서 Designated Initializer 를 정의하지 않고, Convenience Initializer를 부모 클래스의 Designated Initializer를 상속
        부모 클래스의 Designated Initializer 상속
        // 부모 클래스
        class Parent {
            var a : Int
            init(a : Int) {
                self.a = a
            }
            convenience init() {
                self.init(a : 10)
            }
        }

        // 자식 클래스
        class Child : Parent {
            var b : Int
            convenience init(a : Int, b : Int) {
                self.b = b
                super.init(a : a)
            }
            /* 자식 클래스에 Designated Initializer 가 없고 Convenience Initializer 만 정의한 경우 부모 클래스의 Initializer를 모두 상속 */
        }

        // 객체 생성
        // 자식 클래스의 Designated Initializer
        var ChildObj1 = Child(a : 20, b : 30)
        // 부모 클래스의 Initializer 를 상속 받음
        var ChildObj2 = Child(a : 20)
        var ChildObj3 = Child()
      4. 자식 클래스에 부모 클래스에 있는 모든 Designated Initializer를 재정의한 경우, Convenience Initializer 를 상속한다.
        부모 클래스의 Designated Initializer 모두 상속
        // 부모 클래스
        class Parent {
            var a : Int
            init(a : Int) {
                self.a = a
            }
            convenience init() {
                self.init(a : 10)
            }
        }

        // 자식 클래스
        class Child : Parent {
            override init(a : Int) {
                self.b = 20
                super.init(a : a)
            }
            /* 부모 클래스에 Designated Initializer 를 모두 재정의한 경우 부모 클래스의 Convenience Initializer 를 상속 */
        }

        // 객체 생성
        var ChildObj = Child()
        // 부모 클래스의 Convenience Initializer 상속
    3. 클래스 초기화 관계는 아래와 같다.
    4. 상속과 Failable Initializer
      1. 부모 클래스의 Failable Initializer를 자식 클래스가 위임한다.
      2. 자식 클래스에서 부모 클래스의 Failable Initializer 재정의하는 방법은 아래와 같다.
        1. Failable Initializer를 Failable Initializer로 재정의하는 방법
          Failable Initializer를 Failable Initializer로 재정의
          // 부모 클래스
          class Parent {
             var value : Int
             // Failable Initializer
             init?(value : Int) {
                if(value < 0) {
                    return nil
                }
                self.value = value
             }
             // Non-Failable Initializer
             init() {
                 self.value = 0
             }
          }

          // 자식 클래스
          class Child : Parent {
              override init?(value : Int) {
                  // Failable Initializer로 위임
                  super.init(value : value)
              }
          }
        2. Failable Initializer를 Non-Failable Initializer로 재정의하는 방법
          Failable Initializer를 Non-Failable Initializer로 재정의
          // 부모 클래스
          class Parent {
             var value : Int
             // Failable Initializer
             init?(value : Int) {
                if(value < 0) {
                    return nil
                }
                self.value = value
             }
             // Non-Failable Initializer
             init() {
                 self.value = 0
             }
          }

          // 자식 클래스
          class Child : Parent {
              override init(value : Int) {
                  // Non-Failable Initializer로 위임
                  super.init()
              }
          }
        3. Non-Failable Initializer를 Failable Initializer로 재정의는 할 수 없다.

Swift 객체 초기화

  1. 객체 초기화 : 모든 객체는 사용하기 전에 반드시 클래스 내부에서 사용하는 값에 대한 초기화를 해야한다.
    1. 프로퍼티 초기화
      1. 자동 초기화
        1. 옵셔널 타입을 이용한 프로퍼티 초기화 : var optionalValue : Int?
        2. 초기값이 있는 프로퍼티 초기화 : var initalValue : Int = 0
      2. 수동 초기화
        1. 옵셔널 타입이 아니고 초기값이 없는 프로퍼티 : var manualInitValue : Int
        2. 객체 초기화 코드인 Initializer(init 메소드)를 작성하여 프로퍼티를 초기화한다.
          Initializer를 이용한 초기화
          class Rectangle {
             var width : Int
             var height : Int
              // init 메소드에 초기화 진행
              override init() {
                  width = 0
                   height = 0
              } 
          }

          // Initializer 형태에 맞게 객체 생성
          let rect = Rectangle()
      3. Initializer : 수동 초기화에서 보았듯이 초기화가 필요한 프로퍼티를 초기화하며 init 메소드를 이용한다.
        1. Initializer 종류
          1. Designated Initializer
            • 객체 초기화를 단독으로 완료 가능
            • 모든 초기화가 필요한 프로퍼티 초기화
            • 클래스 내에서 반드시 1개 이상 필요
            • 사용 방법
              Designaged Initializer 사용법
              // 모든 값을 초기화
              class Rectangle {
                 var width : Int
                 var height : Int
                  // init 함수에 초기화 진행
                  // Designated Initializer 1
                  override init() {
                      width = 0
                       height = 0
                  }
                  // Designated Initializer 2
                  init(width : Int, height : Int) {
                      self.width = width
                      self.height = height
                  }
              }

              // Initializer 형태에 맞게 객체 생성
              // Designated Initializer 1 로 객체 생성
              let rect1 = Rectangle()
              // Designated Initializer 2 로 객체 생성
              let rect2 = Rectangle(width : 10, height : 20)
          2. Convenience Initializer
            • 단독으로 초기화 불가능
            • 일부 프로퍼티만 초기화
            • 다른 Initializer(Designated Initializer)를 이용해서 초기화
            • 중복 코드 방지
            • 사용 방법
              Convenience Initializer 사용법
              class Rectangle {
                 var width : Int
                 var height : Int
                  // Designated Initializer 1
                  override init() {
                      width = 0
                       height = 0
                  }
                  // Designated Initializer 2
                  init(width : Int, height : Int) {
                      self.width = width
                      self.height = height
                  }
                  // Convenience Initializer
                  init(height : Int) {
                     self.init() // Designated Initializer 1 반드시 필요
                     self.height = height
                  }
              }

              // Initializer 형태에 맞게 객체 생성
              // Designated Initializer 1 로 객체 생성
              let rect1 = Rectangle()
              // Designated Initializer 2 로 객체 생성
              let rect2 = Rectangle(width : 10, height : 30)

              // Convenience Initializer 로 객체 생성
              var rect3 = Rectangle(height : 20)
      4. Failable Initializer : 객체 생성 시 초기화 실패 시에 nil로 처리하는 방법이다.
        1. 작성 방법은 Initializer와 동일하고, 오류 상황에 대한 조건을 확인하여 nil로 반환해준다.
          1. 옵셔널 타입을 이용하여 Initializer 작성
            옵셔널 타입을 이용하여 Initializer 작성
            class Rectangle {
               var width : Int
               var height : Int
                // Designated Initializer 1
                override init() {
                    width = 0
                     height = 0
                }
                // Designated Initializer 2
                init?(width : Int, height : Int) {
                    // Failable Initializer 작성
                    guard(width > 0 && height >0 ) else {
                        return nil
                    }
                    self.width = width
                    self.height = height
                }
            }

            // Initializer 형태에 맞게 객체 생성
            // Designated Initializer 2 로 객체 생성
            var rect1 = Rectangle(width : 10, height : 30)
            // 정상 실행
            var rect2 = Rectangle(width : -19, height : 30) // nil 반환
          2. 강제 언래핑을 이용하여 Initializer 작성
            Convenience Initializer 사용법
            class Rectangle {
               var width : Int
               var height : Int
                // Designated Initializer 1
                override init() {
                    width = 0
                     height = 0
                }
                // Designated Initializer 2
                init!(width : Int, height : Int) {
                    // Failable Initializer 작성
                    guard(width > 0 && height >0 ) else {
                        return nil
                    }
                    self.width = width
                    self.height = height
                }
            }

            // Initializer 형태에 맞게 객체 생성
            // Designated Initializer 2 로 객체 생성
            var rect1 : Rectangle! = Rectangle(width : 10, height : 30)!
            // 정상 실행
            var rect2 : Rectangle! = Rectangle(width : -10, height : 30)! // nil이 반환되므로 런타임 에러 발생
  2. 객체 해제 : 객체가 메모리에서 해제되는 것을 의미한다.
    1. 객체 해제 시에 deinit 메소드가 호출된다.
    2. deinit 메소드는 파라미터, 리턴 타입이 없다.
    3. deinit 메소드에 객체가 해제 되면서 동작할 코드를 작성한다.
    4. 사용 방법
      deinit 메소드 사용법
      class Rectangle {
         var width : Int
         var height : Int
          // 객체 초기화
          override init() {
              width = 0
               height = 0
          }
          // 객체 해제 시 호출
          deinit {
               // 객체 해제 시 동작할 코드 작성
          }
      }

      // Initializer 형태에 맞게 객체 생성
      /* 객체 해제 시 nil 값을 넣어야 하므로 옵셔널 타입 또는 암시적 언래핑으로 호출*/
      var rect1 : Rectangle? = Rectangle() // 옵셔널 타입
      var rect2 : Rectangle! = Rectangle() // 암시적 언래핑
      // 객체 해제되면서 deinit 메소드를 호출
      rect1 = nil
      rect2 = nil
      // 위와 같이 객체 해제하려면 반드시 옵셔널 타입이나 암시적 언래핑으로 호출

Swift 클래스

  1. 클래스를 이해하기 전에 먼저 객체 지향 프로그램에 대해서 이해가 필요하다.
    1. 해당 내용은 아래 링크에 자세하게 다루었다.
      1. 참고 링크 : https://klausbreaktime.blogspot.com/2017/07/blog-post.html
    2. 정리하자면 객체 지향 프로그래밍의 특징은 아래와 같다.
      • 모든 것을 객체로 취급한다.
      • 객체는 실 세계에 존재하거나 생각할 수 있는 것들을 통틀어서 이야기한다.
      • 클래스로 정의되고 클래스의 속성(변수, 데이터, 프로퍼티)와 클래스의 메소드 형태로 구성된다.
      • 클래스의 메소드는 행위를 나타낸다.
  2. 클래스 정의와 객체 생성 방법
    1. 클래스 정의 : class [클래스 명] { 클래스의 속성 및 메소드 }
      클래스 정의
      class Rectangle {
         // 사각형의 가로, 세로 길이에 대한 속성을 정의
         // 사각형의 넓이에 대한 메소드를 정의
      }
    2. 객체 생성 : [클래스 명]()
      객체 생성
      var rect = Rectangle() // 새로운 객체 대입 가능
      rect = Rectangle()

      let rect1 = Rectangle() // 새로운 객체 대입 불가능
    3. 소스코드 파일과 클래스의 관계
      1. 소스 파일 단위와 클래스 정의 단위는 서로 다르다.
      2. 하나의 소스 코드 파일 내에 다수의 클래스 정의가 가능하다.
      3. 하나의 클래스를 개별 소스 코드 파일로 생성한다.
      4. 하나의 클래스를 다수의 소스 코드 파일에 작성 가능하다.
      5. 소스코드 파일에서 탑 레벨에 실행 코드는 작성할 수 없다.
  3. 클래스와 프로퍼티
    1. 프로퍼티는 객체의 데이터를 나타내며 값을 저장하고 읽는 역할을 한다.
    2. 프로퍼티 종류 (저장 프로퍼티, )
      1. 저장 프로퍼티
        1. 데이터를 저장하는 용도로 쓰인다.
        2. 데이터를 읽고 쓰는 역할을 한다.
        3. 객체 생성 시 초기화하며, 초기화하는 방법이 필요하다.
        4. 저장 프로퍼티 정의 및 사용 예시는 아래와 같다.
          저장 프로퍼티 예시
          // 저장 프로퍼티 정의
          class MyClass{
             // 초기값을 설정한 프로퍼티
             var intProperty = 0
             // 초기값을 설정하지 않은 프로퍼티
             var floatProperty : Float?
             // 초기값 설정이 반드시 필요한 프로퍼티
             var strProperty : String = "Test"
          }

          // 저장 프로퍼티 사용
          var obj = MyClass() // 객체 생성
          // 저장 프로퍼티에 값을 저장
          obj.intProperty = 10
          obj.floatProperty = 3.1
          // 저장 프로퍼티에 값을 사용
          let val = obj.floatProperty
          print("val = \(val)") // val = 3.1 출력
      2. 계산 프로퍼티
        1. 데이터를 저장하지 않는다.
        2. 프로퍼티 읽기/쓰기 코드를 작성한다.(set/get)
        3. 읽기전용은 가능하고 쓰기 전용은 사용할 수 없다.(get만 존재)
        4. 계산 프로퍼티 쓰기에서 설정 값은 반드시 한개만 가능하다.(set에 파라미터는 한개만 가능하다.)
        5. set에 들어가는 기본 파라미터 명은 newValue 이며, 그대로 사용해도 되고, 변경도 가능하다.
        6. 계산 프로퍼티 정의 및 사용 예는 아래와 같다.
          계산 프로퍼티 예시
          // 계산 프로퍼티 정의
          class Rectangle {
              var width = 0
              var height = 0
             // 계산 프로퍼티 정의 (읽기 전용)
             var area : Int {
                get {
                    return (width * height)
                }
             }
          }

          // 계산 프로퍼티 사용
          var rect = Rectangle()
          rect.width = 10
          rect.height = 20
          print(rect.area) // 200 출력
      3. 프로퍼티 변경 감시 : 프로퍼티가 객체 생성 후에 변경이 될 경우 변경 전의 값과 변경 후의 값을 확인할 수 있다.
        1. 프로퍼티 변경 전 : willSet
        2. 프로퍼티 변경 후 : didSet
        3. 프로퍼티 변경 감시 사용 예시는 아래와 같다.
          프로퍼티 감시 예시
          class Rectangle {
             var height : Int = 0 {
                willSet {
                     print("사각형 높이 변경 예정 : \(newValue)")
                 }
                 didSet {
                     print("사각형 높이 변경 완료. 이전 값 : \(oldValue)")
                 }
             }
          }
      4. 프로퍼티의 늦은 초기화 : 보통은 객체 생성과 동시에 프로퍼티를 초기화를 하지만 프로퍼티를 사용할 때 초기화하는 방법이 있다.
        1. 프로퍼티의 늦은 초기화하는 lazy 키워드를 앞에 붙여서 정의한다.
        2. 사용 예시는 아래와 같다.
          프로퍼티의 늦은 초기화
          class Person {
             lazy var phone = Phone()
          }

          let john = Person()
          john.phone // 이 때 초기화가 된다.
  4. 클래스와 메소드
    1. 클래스 내에서 메소드(함수)는 어떤 동작을 할지 정의한다.
    2. 메소드의 종류
      1. 인스턴스 메소드
        1. 객체 생성 후 사용해야한다. (객체를 생성하지 않고는 사용할 수 없다.)
        2. 객체의 데이터(프로퍼티) 접근이 가능하다.
      2. 타입 메소드
        1. 객체를 생성하지 않고 사용 가능하다.
        2. 객체의 데이터(프로퍼티) 접근을 할 수 없다.
        3. 키워드는 static으로 한다.(자세한 내용은 5번에서 다룬다.)
    3. 이번에 다루는 메소드는 인스턴스 메소드이다.
    4. 클래스 내에서 메소드 정의 및 사용 예시는 아래와 같다.
      클래스 내에서 메소드 정의 및 사용
      // 클래스 내에 메소드 정의
      class Counter {
         var count = 0
         // 파라미터가 없는 메소드 정의
         func increment() {
             count += 1
         }
         // 파라미터가 1개 있는 메소드 정의
         func increment(amount : Int) {
            count += amount
         }
         // 파라미터가 2개 있는 메소드 정의
         func increment(amount : Int, times : Int) {
            count += (amount * times)
         }
      }

      // 정의된 메소드 사용
      let counter = Counter()
      counter.increment() // count가 1로 증가
      counter.increment(amount : 5) // counter가 6으로 증가
      counter.increment(amount : 5, times : 3)
      // counter가 21로 증가
    5. self 포인터
      1. 클래스 내에서 객체 자신을 참조하는 포인터로 프로퍼티 이름과 메소드의 파라미터 이름이 겹칠 때 프로퍼티 이름과 파라미터 이름을 구분하기 위해서 사용한다.
        self 포인터 사용
        class Counter {
           var count = 0 // 프로퍼티
            // 메소드
            func setCount(count : Int) { // 메소드의 파라미터
                self.count = count
            }
        }
  5. 클래스의 타입 메소드와 타입 프로퍼티
    1. 타입 메소드와 타입 프로퍼티는 모두 객체 생성 없이 사용이 가능하다.
    2. 타입 메소드는 클래스 내에 정의된 프로퍼티에는 접근할 수가 없다.
    3. 타입 메소드와 타입 프로퍼티는 static을 앞에 붙여서 정의한다.
    4. 타입 메소드, 타입 프로퍼티 사용 예시는 아래와 같다.
      타입 메소드, 타입 프로퍼티 정의 및 사용
      // 클래스 내에 타입 메소드, 타입 프로퍼티 정의
      class Rectangle {
         var width = 0
         var height = 0
         // 사각형에 대한 정보를 타입 프로퍼티로 정의
         static var edge = 4
       
         var area {
             get {
                return (width * height)
             }
         }
         // 이름에 대한 정보를 타입 메소드로 정의
         static func name()  -> String {
              return "사각형"
         }
      }

      // 정의된 타입 메소드, 타입 프로퍼티 사용
      var rect = Rectangle()
      rect.width = 10
      rect.height = 20

      // 타입 프로퍼티 접근
      Rectangle.edge // 4
      // 타입 메소드 접근
      Rectangle.name() // 사각형
  6. 클래스와 연산자
    1. 연산자 : 클래스 내에 메소드를 정의하는 것과 동일하게 연산자를 이용하여 해당 클래스 내에서 전용으로 사용되는 연산자를 정의하여 사용할 수 있다. 예) + 연산자에 추가 기능을 더할 수 있다.
    2. 연산자 정의 시 필요 내용
      1. 피 연산자의 개수는 파라미터의 개수와 동일하다.
        1. 단항 연산자는 파라미터 개수가 1개
        2. 이항 연산자는 파라미터 개수가 2개
      2. 연산자의 위치
        1. 앞에 위치 : 전위 연산자 - prefix
        2. 중간에 위치(기본값) : 중위 연산자 - infix
        3. 뒤에 위치 : 후위 연산자 - postfix
      3. 연산자 함수 정의
        1. 연산자 함수는 타입 메소드로 작성
        2. 연산자 함수의 이름은 연산자 예) +, -
        3. 단항 연산자는 연산자 위치 정보가 필요 (prefix, infix, postfix)
    3. 사용 방법
      연산자 선언 및 사용
      // 클래스 내에 연산자 선언
      class Point {
         var x : Int
         var y : Int
         // Initializer
         init(x : Int, y : Int) {
             self.x = x
             self.y = y
         }
         // 단항 연산자 선언
         static prefix func -(point : Point) -> Point {
             var another = Point()
             another.x = -point.x
             another.y = -point.y
             return another
         }
         // 이항 연산자 선언
         static func +(left : Point, right : Point) -> Point {
             return Point(x : (left.x + right.x), y : (left.y + right.y))
         }
      }

      var point1 = Point(x : 10, y : 20)
      point2 = -point1 // Point(x : -10, y : -20)
      let result = point1 + point2 // Point(x : 0, y : 0)

Swift 함수


  1. 함수란? 애플리케이션에서의 동작의 기본 단위를 의미한다.
    1. 함수 작성 방법
      함수 정의 및 사용
      1. 함수 정의
      func greeting() {
         print("Hello Swift")
      }

      2. 함수 사용
      greeting()
    2. 함수의 실행 결과 반환 : 반환 타입 중 Void는 생략 가능 그 외에는 모두 "-> 반환타입"으로 명시한다.
      함수의 실행 및 반환
      1. 함수 정의
      func greeting() -> String {
         return "Hello Swift"
      }

      2. 함수 사용
      var str = greeting()
      print(str) // Hello Swift 출력
    3. 함수 파라미터 : 함수 동작에 필요한 정보를 전달하는데 사용된다.
      1. 함수 파라미터 정의
        함수 파라미터 정의
        1. 파라미터 1개
        func greeting(person : String) {
           print("Hello \(person)")
        }
        2. 파라미터 2개
        func greeting(person : String, emotion : String) {
           print("Hello \(person) with \(emotion)")
        }
      2. 파라미터 이름의 종류
        1. 내부 파라미터 : 함수 내부에서 접근 및 사용한다.
          함수 내부 파라미터
          1. 내부 파라미터
          func greeting(person : String) {
             print("Hello \(person)")
          }
          // 내부 파라미터는 함수 내부에서 접근 및 사용에 쓰인다.
        2. 외부 파라미터 : 함수 내부에서 접근 및 사용할 수 없으며, 외부에서 함수로 필요한 정보를 전달하는 역할을 한다.
          함수 외부 파라미터
          1. 내부 파라미터와 외부 파라미터의 이름이 동일
          func greeting(person : String) {
             print("Hello \(person)")
          }

          greeting(person : "My Friend") // Hello My Friend 출력
          // 외부 파라미터는 함수 내부로 필요한 정보를 전달 시에 사용

          2. 외부 파라미터 이름을 수동을 설정
          func greeting(who person : String) {
             print("Hello \(person)")
          }

          greeting(who : "Jun") // Hello Jun 출력
    4. 파라미터 기본값 설정 방법 : 기본값 설정은 생략 가능하며, 함수 사용 시에 외부에서 함수로 넘겨주는 값이 없는 경우에만 사용된다.
      파라미터 기본값 설정
      func greeting(person : String = "Mom") {
         print("Hello \(person)")
      }

      greeting(person : "My Friend") // Hello My Friend 출력
      greeting() // Hello Mom 출력
    5. 파라미터는 함수 내부에서 상수로 사용하여 변경 시 에러가 발생하는데 변경이 필요한 경우는 inout 키워드로 변경가능하도록 한다.
      함수 내부에서 파라미터 변경
      func tryParamValueChange1(arg : Int = 0) {
         arg = arg + 1 // 에러 발생
      }

      func tryParamValueChange2(arg : inout Int = 0) {
         arg = arg + 1 // 에러 발생하지 않고 변경 가능
      }

      tryParamValueChange2(1) // 2로 변경

      func swapTwoValue(_ arg1 : inout Int, _ arg2 : inout Int) {
        let temp = arg1
        arg1 = arg2
        arg2 = temp
      }

      var value1 = 1
      var value2 = 2
      swapTwoValue(&value1, &value2)
      // value1에는 2, value2에는 1로 변경되며 &로 사용해야 변경
    6. 함수와 옵셔널 : 함수의 반환 타입, 함수 파라미터에 옵셔널로 선언되면 nil 사용이 가능하다.
      1. 함수의 반환 타입에 옵셔널 선언 : 반환 타입에 nil 반환이 가능하다.
        함수 반환 타입에 옵셔널 선언
        func nilReturnFunction() -> Int? {
           return nil
        }

        if let ret = nilReturnFunction() {
           print("실행 결과 : \(ret)") // 실행 결과 : nil 출력
        }
      2. 함수 파라미터에 옵셔널 선언 : 파라미터 사용 전에 반드시 nil 여부를 판단해서 사용하도록 한다. (nil 여부 판단은 if 문이나 guard 문을 이용한다.)
        함수 파라미터에 옵셔널 선언
        func greeting(person : String, emotion : String?) {
           guard let str = emotion else {
               print("Hello \(person)") // emotion이 nil 인 경우 출력
               return
           }
           print("Hello \(person) with \(emotion)")
           // emotion이 nil이 아닌 경우 출력
        }

        greeting(person : "My Friend", emotion : "play")
        // Hello My Friend with play 출력
        greeting(person : "Mom", emotion : nil)
        // Hello Mom 출력