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()