POP(Protocol Oriented Programming)프로토콜 지향 프로그래밍이란?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
참고한 블로그


POP(Protocol Oriented Programming)프로토콜 지향 프로그래밍

애플은 2015년 9월, 스위프트 버전 2.0을 발표하면서 지난 수십년간 사용된 개발 패턴이었던 객체지향 프로그래밍(OOP)가 아닌 프로토콜 지향 언어라고 발표했다고 한다. 이는 쉽게 말하자면 Class를 통한 OOP가 Struct와 Protocol을 통한 POP로 바뀌고 있다는 것을 의미한다. OOP의 핵심은 상속이다. 스위프트에서의 클래스 또한 상속이 가능하지만, 구조체나 열거형의 경우 상속이 불가능하다는 특징이 있다. 그렇기때문에 이전에는 스위프트 코드 대부분의 데이터 구조는 클래스를 통해 작성되었다. 그러나 스위프트에서 클래스와 같은 참조 타입은 단일 원본이 여기저기 참조되기 때문에 그리 권유되는 방법은 아니다. » iOS의 멀티쓰레드 참고 해당 블로그에서도 정리했듯이 클래스를 통해 기능적으로 하나의 원본을 여러군데에서 사용해야하는 경우도 있지만, 멀티쓰레드 환경에서는 한 원본을 두고 여러작업이 동시에 진행되게 되면 원본 데이터가 꼬일 가능성이 크기때문에 하나의 원본으로 작업해야할 필요가 없다면 가급적으로 값 타입인 구조체나 열거형을 사용하는 것을 권장한다.

그러다보니 현재 스위프트의 표준 라이브러리에서 타입과 관련된 것을 살펴보면 대부분이 구조체로 구현되어 있다. 그렇다면 상속도 되지 않는 구조체가 어떻게 다양한 공통 기능을 가질 수 있는 것일까?

Protocol Extension

참고할 블로그 - 프로토콜이란?

프로토콜은 이전에 정리했듯이 특정 역할을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항등의 청사진이라고 할 수 있다. 예로들어 우리가 테이블뷰를 사용하려면 UITableViewDelegate 프로토콜을 채택하게 되는데, 해당 프로토콜을 채택하게 되면 우리가 필수적으로 구현해야하는 메서드가 존재한다. 즉, 우리가 프로토콜을 채택 한다면 반드시 프로토콜이 요구하는 기능을 구현하여 프로토콜을 반드시 준수 해야한다.

익스텐션은 기존 타입의 기능을 확장하는 것을 의미하는데, 한번 생각해보도록 하자.

만약 특정 프로토콜을 정의하고 여러 타입에서 프로토콜을 준수하게 만들어 타입마다 똑같은 메서드, 똑같은 프로퍼티, 똑같은 서브스크립트등을 구현해야한다고 생각해보자. 정말 많은 중독코드들이 발생하게 될 것이며, 더 나아가 유지,보수에 있어 큰 어려움을 겪게 될 것이다. 따라서 이때 필요한 것이 익스텐션과 프로토콜의 결합이다.

프로토콜 초기구현

프로토콜을 채택한 타입의 정의부에 프로토콜의 요구사항을 구현하지 않더라도 프로토콜의 익스텐션에 미리 프로토콜의 요구사항을 구현하는 것을 의미한다.

원래 프로토콜에는 기능을 수행하는 코드는 작성을 할 수 없는데, 이를 swift2 protocol extension에는 실제 값을 계산하고, 기능을 하는 메서드를 구현할 수 있게 되었다. 즉 일반 클래스처럼 프로퍼티와 메서드를 정의하는 것이 가능해짐을 의미한다. 즉 extension을 통해 protocol의 기능을 추가하는 것이 가능해지게 된 것이다.

protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}

struct Person: Talkable {
    var topic: String
    var name: String

    func talk(to: Person) {
        print("\(topic)에 대해 \(to.name)에게 이야기합니다")
    }
}

이런식으로 Talkable이라는 프로토콜을 생성하고 Person은 이 프로토콜을 채택한다. 근데 Person 말고도 다른 타입에서도 채택하고 싶어진다면 그 타입에서도 Talkable 프로토콜이 요구하는 사항을 모두 구현해주어야 한다. 그런데 이때 프로토콜이 요구하는 사항을 미리 한꺼번에 구현해둔다면 중복되는 코드를 피할 수 있게 될 것이다.

protocol Talkable {
    var topic: String { get set }
    func talk(to: Self)
}

// 익스텐션을 사용한 프로토콜 초기 구현
extension Talkable {
    func talk(to: Self) {
        print("\(to)! \(topic)")
    }
}

이렇게 하나의 프로토콜을 만들어주고 초기구현을 해준다면 여러타입에서 해당 기능을 사용하고 싶을 때 해당 프로토콜만 채택해주면 된다. 만약 프로토콜의 초기구현과 다른 동작을 하고 싶다면, 해당 타입에 프로토콜의 요구사항을 재정의해주기만 하면 된다. 따라서 프로토콜 초기구현만 제대로 해놓는다면 여러 프로토콜을 그저 채택하기만 해도 그 타입에 기능이 추가될 수 있음을 의미한다.

프로토콜 지향 프로그래밍을 추구하는 이유

구조체, 클래스, 열거형 등 구조화된 타입 중에 상속은 클래스 타입에서만 가능하다.

클래스는 참조 타입이므로 참조 추적에 비용이 많이 발생한다. 비교적 비용이 적은 값 타입을 활용하고 싶어도, 상속을 할 수 없으므로 때마다 기능을 다시 구현해 주어야 했지만, 프로토콜 지향 프로그래밍은 그 한계를 없앴다. 즉 이를 통해 상속이 필요한 프로그램을 짤때에도 class가 아닌 struct, enum을 사용할 수 있도록 해준다.

기능의 모듈화가 더욱 명확해진다

클래스가 상속을 할 수 있도록 설계되어 있다고 하더라도 다중상속을 지원하는 언어는 많지 않다. 다중상속을 지원하지 않는다는 뜻은 하나의 상속체계에서 다른 상속체계에 속해있는 기능을 끌어다 쓸 수 없다는 뜻인데, 프로토콜 지향 프로그래밍은 기능을 프로토콜이라는 단위로 묶어 표현하고 초기 구현을 해 둘 수 있으니 상속이라는 한계점을 탈피할 수 있다. 따라서 OOP처럼 수직적인 확장구조가 아닌 수평적인 확장구조의 형태를 띄게 된다. 이는 곧 이것저것 제약없이 필요한 기능을 가져와 쓸 수 있음을 의미한다.

더 나아가 class는 모든 api에 접근이 가능한 반면 Protocol은 정의한 api만 가져오게 된다. 이는 곧 더 가볍고 보안성 높게 코드를 짤 수 있음을 의미한다.

네비게이션과 모달의 차이점은 무엇인가? +구현하는 방식

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
참고한 블로그


네비게이션과 모달

이전에 네비게이션 컨트롤러와 뷰 컨트롤러의 차이점에 대한 블로그를 작성한 적이 있다 » 블로그 참고
네비게이션과 모달 모두 뷰 컨트롤러를 제어한다는 공통점이 잇지만, 표현방식에서의 차이를 보여준다.

네비게이션 은 스택구조로 뷰를 쌓아올려 최상위 뷰만을 보여주는 형태를 말한다. 따라서 뷰를 스택에 담을때 화면에 보여주기 위해서는 push, 화면에서 제거할 때는 pop을 사용한다. 모달 은 네비게이션과 다르게 새로운 뷰를 보여주는 방식이 아닌 현재 화면을 덮는 형태를 말한다. 화면에 뷰를 보여주기 위해서 present, 화면에서 내릴때는 dismiss를 사용하는 것이 특징이다.

용도의 차이

네비게이션은 주로 정보의 깊이와 흐름을 가지는 구조에서 사용된다. 네비게이션 바 타이틀을 통해 현재 위치를 확인할 수 있으며 이전 화면으로 돌아가는 버튼 또한 네비게이션 컨트롤러에서 제공한다. 설정앱을 생각해보면 쉽게 네비게이션이 무엇인지 이해하기 쉬울 것이다. 하나의 정보 흐름을 가지고 깊게 파고드는 형식! [설정] > [Apple ID] > [이름, 전화번호, 이메일]…

반면 모달은 화면 전체를 정보의 흐름에서 벗어나 전환해주는 것을 의미한다. » 모달이란?

이전 블로그 내용을 다시 조금 정리해보자면, 모달은 사용자의 이목을 끌기위해 사용하는 화면 전환 기법을 의미한다. 이목을 집중해야하는 화면을 다른 화면 위로 띄워(present) 표현하는 방식으로 모달로 보이는 화면을 사라지게 하려면 반드시 특정 선택을 해야하는 특징을 가진다. 위에서 언급했던 것과 같이 모달은 네비게이션 인터페이스와는 달리 정보의 흐름을 가지고 화면을 이동한다기 보다는 꼭 이목을 끌어야 하는 화면에서 사용하는것이 특징이다. 되도록 단순하고 사용자가 빠르게 처리할 수 있는 내용을 표현하는 것이 좋다. [정보의 흐름이 아닌 잠깐의 팜업 혹은 잠시간의 입력폼 » 아이폰 내의 구글 메일앱]

네비게이션 구현 방식

@IBAction func signUpBtn(_ sender: UIButton) {
    let storyboard = UIStoryboard.init(name:"Main", bundle: nil)
    let viewController: SecondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
    self.navigationController?.pushViewController(viewController, animated: true)
}

모달 구현 방식

let storyboard: UIStoryboard = UIStoryboard(name: "SecondStoryboard", bundle: nil)
if let myViewController: MyViewController = storyboard.instantiateViewController(withIdentifier: "MyViewController") as? MyViewController {
	self.present(myViewController, animated: true, completion: nil)
}

구현하는 방식은 각자 조금의 차이는 있겠지만, 기본적으로 나는 위의 형식으로 구현한다.

iOS에서는 멀티쓰레드를 지원해주는가?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
참고한 블로그


들어가기에 앞서….

파이썬만 공부해봤던 나에게는(파이썬 멀티쓰레드 일반적으로는 지원하지않음) 이 개념이 조금 생소하지만, 일단 결과적으로 iOS에서는 멀티쓰레드를 지원한다!

프로세스(process): 실행중인 프로그램을 의미한다. 프로그램과 프로세스의 차이는 알고 있어야한다.
실행중이라는 의미는 말 그래도 사용자가 작성한 프로그램이 운영체제에 의해 메모리를 할당받았다는 것이고, 이러한 프로세스는 프로그램에 사용되는 메모리와 데이터등의 자원 그리고 스레드로 구성된다.

쓰레드(thread): 프로세스 내에서 실제로 작업이 수행되는 주체를 의미한다. 모든 프로세스는 한개 이상의 스레드가 존재해 작업을 수행한다.
두개 이상의 쓰레드를 가지는 프로세스를 멀티쓰레드 프로세스라고 한다.(multi-thread process)

멀티쓰레딩: 여러개의 쓰레드가 동시에 진행되는 것을 의미한다. 하나의 프로세스에 여러개의 쓰레드가 존재하고 이 스레드들이 프로세스의 자원을 공유하되 실행은 독립적으로 이루어지는 구조를 갖는다.

  • 장점
    • 메모리 공간과 시스템 자원 소모가 줄어든다.
    • 프로세스간 통신방법보다 간단하다 » 별도의 자원공간이 아닌 전역변수의 공간 또는 동적으로 할당된 공간(heap영역)을 이용해 데이터를 주고받음
  • 단점
    • 서로다른 쓰레드가 데이터와 heap영역을 공유하기때문에 한 쓰레드가 다른 쓰레드에서 사용중인 변수나 자료구조에 접근해 엉뚱한 값을 읽어올 수 있다.
    • 병목현상(과도한 lock)이 발생하여 성능이 저하될 가능성이 있다.

iOS에서는 멀티쓰레드를 지원한다!

그러나 멀티쓰레드 프로그래밍을 할 경우 고려해야하는 상황이 있다.

  1. UI업데이트 관한 작업들은 메인쓰레드에서 구현해야 한다.
  2. 쓰레드에 안전하지 않은(Thread-unsafe) 변수는 서로 다른 쓰레드에서 동시 접근하면 위험하기에 해당 작업을 반드시 신경써야한다.

leading, trailing과 left, right 무엇을 사용하는게 더 좋을까?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.


leading, trailing과 left, right 무엇을 사용하는게 더 좋을까?

결론부터 이야기하자면, leading, trailig을 이용하는 것을 더 권장한다.

오토레이아웃을 사용하다보면 우리는 leading, trailing, left, right 개념을 알게될텐데 기본적으로 텍스트를 왼쪽에서 오른쪽으로 읽는 우리에게는 leading을 쓰든 left를 쓰든 별 차이가 없을거라고 생각한다. 우리는 당연하게 글을 왼쪽에서부터 읽으니 텍스트의 시작점을 left로 지정을 해도되지만 만약 우리가 만드는 애플리케이션이 타 국가에도 사용되게끔 만드는데 그때에도 left를 사용하게 된다면 문제가 생기게 될 것이다. 그때 우리가 사용해야하는 것이 leading과 trailing이다.

  • leading: 텍스트의 시작점
  • trailing: 텍스트의 끝
  • left: 왼쪽
  • right: 오른쪽

따라서 애플리케이션의 지역화를 지원해야하는 경우 leading과 trailing은 기본적으로 사용되어야하며, 이런 저런 이유를 떠나서 left, right 딱 왼쪽과 오른쪽을 구분지어 사용하기 보다는 leading, trailing을 사용하여 조금 더 유연하게 코드를 짜는게 옳다고 생각한다. » LTR과 RTL을 모두 원활히 지원하는 코드로 !!

Alamofire란 무엇이고 어떻게 사용하는가?

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
참고한 블로그


간단하게…

iOS HTTP통신을 할 때 필수 라이브러리인 Alamofire에 대해서 아주 간단히 정리해보겠다!

Alamofire는 iOS, macOS를 위한 스위프트 기반 HTTP 네트워킹 라이브러리로 Apple의 Foundation networking 기반으로 인터페이스를 제공하여 일반적인 네트워킹 작업을 단순화한다. Alamofire는 함께 사용가능한(chainable) request/response 매소드들, JSON 파라미터, 응답 직렬화(response serialization), 인증(authentication) 그리고 많은 다른 기능을 제공한다.

  • 연결가능한(Chainable) Request/Response 메서드
  • URL / JSON / plist 파라미터 인코딩
  • 파일 / 데이타 / 스트림 / 멀티파트 폼 데이타 업로드
  • Request 또는 Resume 데이터를 활용한 다운로드
  • NSURLCredential을 통한 인증(Authentication)
  • HTTP 리스폰스 검증(Validation)
  • TLS 인증서와 공개 키 Pinning
  • 진행 상태 클로저와 NSProgress
  • cURL 디버깅 출력
  • 광범위한 단위 테스트 보장
  • 완벽한 문서화

즉, 정리해보자면 아래와 같다.

  • Alamore란 iOS, macOS를 위한 Swift 기반의 HTTP 네트워킹 라이브러리
  • Alamofire는 URLSession 기반이며 URLSession은 네트워킹 호출에서 모호한 부분이 많은데 Alamofire를 사용한다면 데이터를 접근하기 위한 노력을 줄일 수 있으며 코드를 더 깔끔하고 가독성 있게 쓰는 것이 가능해짐

Alamofire는 HTTP 네트워킹을 하는데 자주 사용하게 되는 코드나 함수를 더 쉽게 사용할 수 있도록 모아놓은 것

iOS에서 기본적으로 제공하고 있는 HTTP통신 방법은 여러가지가 있지만 URLSession을 이용한 방법이 있다. 모두가 같은 방법으로 사용하진 않겠지만 기본적으로 이러한 형태로 요청이 가능하다.

var request = URLRequest(url: URL(string: "https://api.github.com/users")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { (data, response, error) in    
}

만약 여기서 Get요청이 POST요청으로 바뀐다고 한다면 아래와 같 파라미터들을 넘길 수 있습니다.

var request = URLRequest(url: URL(string: "https://api.github.com/users")!)
request.httpMethod = "POST"
let params = ["id":id, "password":password] as Dictionary

do{
  try request.httpBody = JSONSerialization.data(withJSONObject: params, options: [])
} catch {
  return
}

URLSession.shared.dataTask(with: request) { (data, response, error) in }

만약에 여기서 Header를 추가하게 된다면 아래를 추가해줘야한다.

request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

즉 이렇게 성공, 실패를 또 나눠야한다.

var request = URLRequest(url: URL(string: "https://api.github.com/users")!)
request.httpMethod = "POST"
let params = ["id":id, "password":password] as Dictionary

do {
  try request.httpBody = JSONSerialization.data(withJSONObject: params, options: [])
} catch {
  return
}

request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")

URLSession.shared.dataTask(with: request, completionHandler: {(data, response, error) -> Void in
  if let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode {
    //SUCCESS
  }else{
    //Failure
    }
})

이런식의 코드는 가독성은 물론이며, 조금의 설정만 바꾸면 많은 것이 변경되어야 해서 여러모로 불편한점이 있다. 그래서 이러한 불편한 것들을 개선시키는 라이브러리인 Alamofire를 사용해보자!

Alamofire는 Swift로 작성된 HTTP 네트워킹 라이브러리

설치는 CocoaPod으로 진행할 수 있다.

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '<Your Target Name>' do
    pod 'Alamofire', '~> 4.4'
end

이제 설치까지 완료 하였으니 한번 똑같은 URL로 요청해본다!

Alamofire.request("https://api.github.com/users", method: .get, parameters: [:], encoding: URLEncoding.default, headers: ["Content-Type":"application/json", "Accept":"application/json"])
            .validate(statusCode: 200..<300)
            .responseJSON { (response) in
            if let JSON = response.result.value
            {
                print(JSON)
            }
        }

끝이다!

일단 request메소드의 변수들을 보자 > request메소드를 Command + Mouse Left를 클릭해 정보를 보면

request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)

  • url: 요청할 URL
  • method: 요청 형식 (get, post, put, delete..등)
  • parameters: 요청 시 같이 보낼 파라미터
  • encoding: encoding
  • header: [String:String]형태로 보낼 수 있음

위에서 일일이 추가 하던 것과는 다르게 확연히 다른 차이를 볼 수 있으며, 훨씬 간결해서 보기도 편하다. 뿐만 아니라, request가 성공인지 실패인지를 필터를 하는 validate(statusCode: 200..<300)로 200~299사이의 statusCode결과만 받아올 수 있는 간편한 기능도 지원한다.

저렇게 요청을 하면 아래처럼 쉽게 요청을 할 수 있다.

{
        "avatar_url" = "https://avatars3.githubusercontent.com/u/1?v=3";
        "events_url" = "https://api.github.com/users/mojombo/events{/privacy}";
        "followers_url" = "https://api.github.com/users/mojombo/followers";
        "following_url" = "https://api.github.com/users/mojombo/following{/other_user}";
        "gists_url" = "https://api.github.com/users/mojombo/gists{/gist_id}";
        "gravatar_id" = "";
        "html_url" = "https://github.com/mojombo";
        id = 1;
        login = mojombo;
        "organizations_url" = "https://api.github.com/users/mojombo/orgs";
        "received_events_url" = "https://api.github.com/users/mojombo/received_events";
        "repos_url" = "https://api.github.com/users/mojombo/repos";
        "site_admin" = 0;
        "starred_url" = "https://api.github.com/users/mojombo/starred{/owner}{/repo}";
        "subscriptions_url" = "https://api.github.com/users/mojombo/subscriptions";
        type = User;
        url = "https://api.github.com/users/mojombo";
    },

        {
        "avatar_url" = "https://avatars3.githubusercontent.com/u/2?v=3";
        "events_url" = "https://api.github.com/users/defunkt/events{/privacy}";
        "followers_url" = "https://api.github.com/users/defunkt/followers";
        "following_url" = "https://api.github.com/users/defunkt/following{/other_user}";
        "gists_url" = "https://api.github.com/users/defunkt/gists{/gist_id}";
        "gravatar_id" = "";
        "html_url" = "https://github.com/defunkt";
        id = 2;
        login = defunkt;
        "organizations_url" = "https://api.github.com/users/defunkt/orgs";
        "received_events_url" = "https://api.github.com/users/defunkt/received_events";
        "repos_url" = "https://api.github.com/users/defunkt/repos";
        "site_admin" = 1;
        "starred_url" = "https://api.github.com/users/defunkt/starred{/owner}{/repo}";
        "subscriptions_url" = "https://api.github.com/users/defunkt/subscriptions";
        type = User;
        url = "https://api.github.com/users/defunkt";
    }

구체적으로 파보기

Request 만들기

import Alamofire

Alamofire.request(.GET, "https://httpbin.org/get")

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
         .responseJSON { response in
             print(response.request)  // original URL request
             print(response.response) // URL response
             print(response.data)     // server data
             print(response.result)   // result of response serialization

             if let JSON = response.result.value {
                 print("JSON: \(JSON)")
             }
         }

Alamofire은 비동기(asynchronously)로 네트워크 연동을 하기 때문에 서버로부터 응답을 받을 때까지 기다리지 않고 콜백을 통해서 응답을 처리해준다. 위의 예에서 처럼 요청에 대한 응답은 이를 처리하는 핸들러 안에서만 유효하므로 수신한 응답이나 데이터에 의존적인 동작들은 반드시 해당 핸들러 내에서 완료 해야한다.

Response 처리

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
         .response { request, response, data, error in
             print(request)
             print(response)
             print(data)
             print(error)
          }

response 시리얼라이저는 수신한 데이터에 별도의 처리를 하지 않고 URL session 델리게이트로 부터 수신한 모든 정보를 그대로 전달할 뿐이다. Response나 Result 자료형의 장점을 활용할 수 있도록 다른 시리얼라이저를 활용하기를 권장합니다.

Alamofire는 Request뿐만 아니라 Upload, Download등 여러가지를 지원하며 그에 맞는 기능을 최대한 편하게, 간결하게 사용할 수 있도록 제공한다.