주석이 필요 없는 코드.key

%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-002

주석 자체가 나쁜 것은 아니다. 개발자는 주석을 남기는 것을 귀찮아 한다. 그럼에도 주석을 작성해야 되겠다 생각이 들었다면 그것은 나쁜 코드를 작성하고 보완을 하려는 목적일 가능성이 매우 높다. 코드를 보완하는 것이 훨씬 낫다. 다시 말하지만 주석 자체가 나쁜 것은 아니다. 하지만 주석으로 보완하지 않는 코드를 작성하는 습관이 선행되어야 한다 주석을 잘 다는 습관은 그 다음에 익혀도 된다.

%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-003

개발자라면 일정에 쫓겨 나쁜 코드를 작성한 후 나중에 수정해야지 라는 생각을 가진 경험이 있을 것이다. 그리고 개발자라면 그 ‘나중’이 단 한번도 온 적이 없다는 것을 경험으로 알 것이다.

%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-004

선언 의도를 파악할 수 있는 이름, 의미가 있는 이름. 불필요하게 단순화 시킨 이름은 검색이 어렵다. 발음이 쉽지 않은 이름은 커뮤니케이션을 어렵게 한다. 동일한 행위에 대해서는 동일한 단어를 사용하는 것이 좋다. get receive fetch -> receive / 그러나 통일성을 위해 강박적으로 단어를 통일 시켜서도 안된다. 동일한 행위라도 차이가 있다면 구분을 해주어야 한다. add insert append

%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-005%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-006

%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-007

마지막으로 강조하고 싶은 것은 코드는 만들때 잘 만드는 것도 중요하지만 실상 코드는 수정, 추가를 거치면서 나쁜 코드로 가속화 된다. 남이 만든 코드든 자신이 만든 코드든 수정, 추가할때 더욱 유의해야 한다. pull 받은 코드는 push 할때 더욱 깨끗하게.

%ec%a3%bc%ec%84%9d%ec%9d%b4-%ed%95%84%ec%9a%94%ec%97%86%eb%8a%94-%ec%bd%94%eb%93%9c-008

반복된 디버깅에 지쳐 있다면 코드부터 뜯어 고치자. 같은 방식으로 개발하면서 버그가 발생하고 유지보수가 어렵다고 투덜 거리는 건 의미 없다.

Advertisements

HTTP 주소로 iOS 앱을 실행하여 보자. (Support Universal Links)

아래의 방법은 iOS 9 이상에서 정상적으로 지원합니다. 또한 서버는 HTTPS를 지원해야 합니다.
참조 문서 : Support Universal Links

아이폰으로 서핑하다보면 유투브 링크가 있고 해당 링크를 누르면 유투브 앱이 열리는 상황을 경험한 적이 있습니다.

본인이 만든 앱도 이렇게 하고 싶다는 생각을 했을 것입니다.

앱을 설치하지 않은 사용자는 간략한 정보가 표시되는 모바일웹을, 앱을 설치한 사용자는 풀서비스를 이용할 수 있도록 앱으로 연결을.

애플은 이런 개발자들의 욕구를 수용해 Universal Link 기능을 추가하였습니다.
하지만 간단히 앱에서 설정하는 것만으로 해당 기능을 이용할 수 없습니다.
보안을 중요시 여기는 애플 답게 우선 해당 HTTP 주소에 대해 앱이 권한을 가지고 있는 지 확인을 합니다.

우선 해당 HTTP 주소에 앱이 권한을 가지고 있다는 것을 명시하는 방법 부터 소개 하겠습니다.

서버의 https://[당신의 도메인]/ 또는 https://[당신의 도메인]/.well-known/ 으로 접근 가능하도록 apple-app-site-association 파일을 생성해야 합니다.

https://[당신의 도메인]/apple-app-site-association 또는 https://[당신의 도메인]/.well-known/apple-app-site-association 되겠죠.

명심해야 될 2가지는 https를 지원해야 한다는 것과 파일에 확장자가 없어야 한다는 것입니다. 아래의 예제를 보시면 아시겠지만 apple-app-site-association 파일은 JSON 형태입니다. 하지만 .json 을 붙이지 마세요.

{
"applinks": {
    "apps": [ ],
    "details": [
        {
            "appID": "[Prefix].[앱의 Bundle ID]",
            "paths": [ "*/members/*", "/detail/*", "NOT /detail/old" ]
        },
        {
            "appID": "[Prefix].[두번째 앱의 Bundle ID]",
            "paths": [ "*" ]
        }
    ]
}
}

위의 apple-app-site-association에는 2개의 앱이 선언되어 있습니다.
apps는 비워 둡니다.
details에 등록할 앱 정보를 기입합니다.
appID에 Prefix와 앱의 Bundle ID를 기입합니다.
paths는 중요합니다. 모든 HTTP 주소에 앱을 연결 시킬 수 있고 특정 HTTP 주소에만 앱이 연결 되도록 할 수 있기 때문입니다.
당연히 NOT는 제외할 주소입니다. /detail/*는 허용하지만 /detail/old 는 거부할 수 있죠.
* 만 해 놓으면 모든 주소를 연결 시킬 수 있습니다.

유투브의 경우엔 모든 주소를 앱으로 연결 되도록 하고 제외할 주소를 추가하는 방식을 썼습니다.
실용 예제로 삼아 한번 보시기 바랍니다.(Document Root에 파일을 두기 싫었는 지 .well-known 아래에 두었군요.)

http://youtube.com/.well-known/apple-app-site-association

유투브 앱의 Prefix는 EQHXZ8M8AV 이었군요. 모르시는 분은 없으시겠지만 Prefix는 https://developer.apple.com 에서 알 수 있습니다.

Developer > Certificates, Identifiers & Profiles (https://developer.apple.com/account/ios/certificate) > App IDs (https://developer.apple.com/account/ios/identifier/bundle) > 원하는 앱 선택.

teamid

자, 이제 정상적으로 설정이 끝났는 지 확인을 해봅시다.

https://branch.io/resources/universal-links/

간단히 도메인만 넣어 주면 apple-app-site-association 파일을 찾아 테스트를 하여 줍니다.
모든 항목이 초록색이 나오면 준비 끝.
(애플에서도 테스트를 할 수 있는 툴(https://search.developer.apple.com/appsearch-validation-tool/)을 제공하고 있고 앱 설정까지 확인해 주기 때문에 더 정확합니다. 하지만 개발 단계에서 서버에서의 작업만 확인 하기에는 위 툴이 더 편리합니다.)

다음은 앱으로 넘어 갑니다.

Xcode에서 간단히 처리 가능합니다.

associateddomains

Associated Domains 항목을 활성화 후 Domains에 applinks:도메인 을 입력합니다.

끝!

이제 링크를 눌렀을때 앱이 실행됩니다. Custom Scheme를 이용한 Deep Link 시에는 앱을 열 것이냐는 물음이 뜹니다. Universal Link는 그런 물음도 없이 자연스럽게 앱이 실행됩니다.

위 작업을 모두 끝낸 후 테스트 할때 주의 하실 점.

  1. 사파리에서 직접 주소를 치면 웹으로 가고 싶어 하는 의사로 판단 Universal Link가 작동하지 않습니다.
  2. 테스트를 위해 링크를 삽입한 웹페이지가 동일한 도메인이면 웹 내의 정상적인 서핑으로 판단 Universal Link가 작동하지 않습니다.

테스트에 성공하였나요? 축하 드립니다.

참고로 이거 달고 앱 설치하면 Safari에서 모바일 웹 상단에 ‘열기’가 생깁니다. 사용자의 앱 이용을 유도할 수 있는 것이죠.

앱을 실행 시킬때는 HTTP 주소를 함께 전달해 줍니다. AppDelegate.swift 에서 받아서 처리할 수 있습니다.

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Swift.Void) -> Bool {
    // 이곳에서 userActivity.activityType 과 userActivity.webpageURL 을 이용하여 필요한 ViewController 를 띄우는 처리를 하면 됩니다.
    return true
}

옵셔널 변수를 적절하게 처리하는 예제들.

var productNo: String?
var productTitle: String?

.
.
.

guard let no: String = self.productNo else {
    print("제폼 번호가 전달자로 부터 대입되지 않음. 이후 행등 실행 종료.")
    return
}

print("제품 번호는 \(no)")

.
.
.

if let no: String = self.productNo, let title: String = self.productTitle {
    print("제품 \(title)의 번호는 \(no)")
}

.
.
.

// 변수 대입 시 nil 일 경우 별도값을 지정.
let no: String = self.productNo ?? "1"

.
.
.

// guard의 else return의 적절한 사용예.
func aaa(a: Int?) -> Bool {
    guard let b: Int = a else {
        return false
    }

    if b > 3 {
        return true
    }
    else {
        return false
    }
}

앱을 다시 시작하면 WKWebView 쿠키가 모두 사라져요.

WKWebView에서 생성된 쿠키가 앱을 재시작할때 모두 사라지는 문제는 여러 해결 방법이 있지만, 그 중 가장 단순한 건 역시 모든 통신 종료 시점에 쿠키를 저장했다가 앱을 시작하면 쿠키를 수동으로 생성 시켜 주는 방법일 것입니다.

이전에도 Javascript를 애용했으므로 이번에도 Javascript를 이용하도록 하겠습니다.

예제 상에서 파일을 저장하고 불러 오는 것은 Devg.framework를 이용하도록 하겠습니다.
(https://bitbucket.org/ZenR/devg.git)

우선 모든 웹 통신이 종료될 시 가지고 있던 쿠키를 평문으로 저장하도록 하겠습니다. 보안에 좀 더 신경을 쓴다면 한번 암호화를 거치는 것도 좋겠죠.

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation) {
    webView.evaluateJavaScript("document.cookie") { (object, error) in // 자바스크립트로 모든 쿠키를 가져와서
         if let string: String = object as? String { // 스트링으로 변환 후 저장.
             Devg.createFile("COOKIE", contents: string) // 파일 저장하는 부분이므로 대체 하세요.
         }
    }
}

 

 

이제 저장한 쿠키 정보를 불러와서 웹뷰에서 생성하는 작업을 해주어야 합니다.
적절한 도메인에 쿠키를 생성해 주기 위해 첫 웹뷰 로딩은 그냥 진행 시키도록 할게요.
결국 Javascript 기반이기 때문에 쿠키 생성도 didFinishNavigation 에서 진행해 주어야 하는데 쿠키 파일 저장과 생성하는 것이 한 곳에서 이루어 지므로 구분 하기 위해 isFirstLoad라는 멤버 변수를 만들어 주겠습니다.

var isFirstLoad: Bool = true

이제 isFirstLoad 변수를 참조하여 didFinishNavigation에 쿠키 생성하는 코드를 추가 합니다.

func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation) {
    if self.isFirstLoad { // 앱 처음 실행인지 판단.
        self.isFirstLoad = false // 쿠키 생성을 다시 하지 않도록 처리.
        if let string: String = Devg.getContents("COOKIE") { // 저장된 쿠키 파일을 불러 옴.
            // 쿠키를 생성하는 Javascript
            let cookies: [String] = string.componentsSeparatedByString(" ")
            var javascript: String = ""
            for cookie: String in cookies {
                javascript = javascript + "document.cookie='\(cookie)';"
            }
            webView.evaluateJavaScript(javascript, completionHandler: { (object, error) in // 쿠키를 생성하는 Javascript를 실행.
                webView.reload() // 쿠기 생성 후 쿠키 정보가 반영된 웹페이지를 불러 올 수 있도록 새고 고침을 해줍니다.
            })
        }
    }
    else {
        webView.evaluateJavaScript("document.cookie") { (object, error) in // 자바스크립트로 모든 쿠키를 가져와서
             if let string: String = object as? String { // 스트링으로 변환 후 저장.
                 Devg.createFile("COOKIE", contents: string) // 파일 저장하는 부분이므로 대체 하세요.
             }
        }
    }
}

https://ip.devg.net 은 사용자 아이피, 헤더 정보 등을 확인할 수 있는 웹페이지인데, 쿠키 정보도 볼 수 있으므로 간단히 테스트 후 실제 URL을 적용하시면 됩니다.

이름으로 쿠키 가져오기 in Javascript

function getCookie(name) {
  var value = "; " + document.cookie;
  var parts = value.split("; " + name + "=");
  if (parts.length == 2) return parts.pop().split(";").shift();
}

Swift에서도 쿠키를 가져 올 수 있으나 가급적 웹뷰에 대한 부분은 Javascript에 의존하기로 합시다.

위 Javascript를 아래와 같이 적용하면 필요한 쿠키를 손쉽게 가져올 수 있습니다.

webView.evaluateJavaScript("function getCookie(name) {  var value = \"; \" + document.cookie;  var parts = value.split(\"; \" + name + \"=\");  if (parts.length == 2) return parts.pop().split(\";\").shift();}; getCookie('basket_cnt');") { (object, error) in
    print(object)
}

 

WKWebView 에서 Ajax 통신을 감지하는 방법. feat. Javascript

UIWebView 시절에도 그랬지만, WKWebView 에서도 Ajax 통신을 감지할 수 없습니다.

WKWebView 에서 추가된

@available(iOS 8.0, *)
optional public func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void)

에서는 <iframe /> 통신(완료)은 감지 가능하지만 역시 Ajax 통신을 감지할 수 없습니다.

웹은 Swift의 전문 분야가 아닙니다. 웹의 전문 분야인 Javascript에게 도움을 요청합시다.

@available(iOS 8.0, *)
optional public func webView(webView: WKWebView, didFinishNavigation navigation: WKNavigation!)

didFinishNavigation에서 일반 통신이 종료되는 시점을 감지하여 Ajax 통신을 감지하는 자바스크립트 명령을 삽입합니다.

self.webKit.evaluateJavaScript("_send = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.send = function() { alert('ajax:'); var callback = this.onreadystatechange; this.onreadystatechange = function() { if (this.readyState === 4 &amp;amp;&amp;amp; this.status === 200) { alert('ajaxDone'); } callback.apply(this, arguments); }; _send.apply(this, arguments); }; 'script inserted';", completionHandler: { (object, error) in
    print(object)
})

위 명령은 Ajax 를 관장하는 XMLHttpRequest의 send() 메소드를 오버라이딩하여 Ajax 통신 상태를 앱에 전달하여 주기 위한 자바스크립트입니다.

_send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function() {
    alert('ajax');
    var callback = this.onreadystatechange;
    this.onreadystatechange = function() {
        if (this.readyState === 4 && this.status === 200) {
            alert('ajaxDone');
        }

        callback.apply(this, arguments);
    };

    _send.apply(this, arguments);
};

Javascript와 WKWebView간의 효과적인 통신 방법은 WKUserContentController를 이용하는 방법이 있습니다. 하지만 이것은 다음에 기회가 되면 다시 설명 드리기로 하고 여기에서는 간단하게 Javascript의 alert()과 WKUIDelegate의

@available(iOS 8.0, *)
optional public func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void)

를 이용하는 방법을 사용하겠습니다. 위 protocol은 WKWebView에서 alert() 자바스크립트가 작동되면 호출됩니다.

위 자바스크립트를 이용하여 Ajax(XMLHttpRequest)가 작동하면 alert(‘ajax’); 를 호출 하도록 했고, 통신이 정상적으로 완료 되면 alert(‘ajaxDone’); 를 호출 하도록 하였습니다.

Swift에서는 아래와 같이 각 메세지를 받아서 분기 처리를 할 수 있습니다.

func webView(webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: () -> Void) {
    if message == "ajax" {
        // Ajax 통신 시 웹뷰의 URL을 변경 시키는 경우가 있으므로 이를 확인.
        // Ajax 통신이 이루어지는 URL과는 다름.
        print(webView.URL?.absoluteString)
        completionHandler()

        return
    }
    else if message == "ajaxDone" {
        // Ajax 통신으로 달라 진 HTML (DOM) 을 확인.
        self.webKit.evaluateJavaScript("document.documentElement.outerHTML", completionHandler: { (object, error) in
            print(object)
        })
        completionHandler()

        return
    }

    // UIAlertController 사용 message 내용 띄우기.

    completionHandler()
}

이제 WKWebView는 Ajax를 포함한 모든 통신을 감지할 수 있게 되었습니다.

단순히 웹뷰에 웹을 보여 주는 한계에서 벗어 나 앱의 제어권을 더 넒힌 하이브리드앱의 제작이 가능해졌습니다.

Optional – ?와 !를 이해 하자.

Objective-C와 Swift의 차이 중 하나는 다이나믹 형변환이 없어지며 자료형이 매우 빡빡해 졌다는 것이다.

빡빡한 자료형에, 개발자들이 품자 마자 풀려 버렸던 의문과 답은 바로

“그럼 null(nil)은 어떻게 넣는데?” 와
“?(Optional)를 써.” 였다.

그렇다고 단순히 Optional을 ‘nil을 넣을 수 있게 자료형을 선언 하는 녀석’으로 이해하면 곤란한데.
보통 이렇게 이해한 개발자들이 !를 남용하기 때문이다.

!를 남용하게 되는 가장 큰 이유는 ?로 선언된 객체는 사용 전에 항상 확인을 해주어야 한다.
이게 귀찮기 때문에 !를 사용하게 되는데.

왜 컴파일러가 Syntax Check를 할때 ?는 사용 전 확인을 강요하고, !는 그냥 넘기는 지 이해 한다면
자연히 ?와 !의 진짜 차이점을 이해할 수 있다.

 

간단하다.

?는 nil 일 수도 있는 녀석.
!는 nil 일 가능성이 없는 녀석.

Swift로 작성된 iOS 개발 예제를 보면 @IBOutlet은 항상 !으로 선언한다.

@IBOutlet var shareButton: UIButton!

뷰콘트롤러가 선언될 당시엔 해당 변수엔 nil이 담기겠지만 Storyboard에 작성된 View가 로드 되면서 해당 변수엔 뷰객체가 담기게 된다. 그래서 해당 변수는 Optional로 선언되어야 하기에 !가 사용되었다.

그러나 ?가 아니다. !는 남용하지 말라고 했는데 그럼 대부분의 개발자가 사용하고 공식 예제에도 사용하는 위의 경우는 남용인가?

아니다.

정상적으로 개발했다면 해당 변수엔 정상적으로 UIButton 객체가 담겨야 한다. nil 일 가능성이 없는 녀석이기 때문에 !를 사용하는 것이 맞다.

Storyboard에서 제대로 작업하지 않아 shareButton과 UIButton을 제대로 연결 시키지 않았다면 앱은 fatal error를 내뱉고 튕겨 버릴 것이다.

이건 수정해야 할 버그지 ?나 !를 따질 문제가 아니다.

그렇다. 사실 ?와 !를 적절히 구분해서 사용해야 되는 이유는 바로 fatal error때문이다.
?를 사용하여 선언한 변수에 대해 컴파일러가 매번 사용 전 확인을 하라고 강제 하는 것은 fatal error를 막기 위해서이다.

그래서 ?는 서버와의 통신이나 사용자 데이타 등 nil 일 가능성이 충분히 있는 변수에 사용하는 것이다.

그러나 그렇다고 너무 ?만 사용해도 fatal error를 내뱉어 즉시적으로 개발상 문제를 파악할 수 있는 부분도 그냥 넘어 가거나 사용전 확인 코드 때문에 코드가 너무 복잡해 지는 등의 문제가 있기 때문에.

개발상 최선의 판단으로 ?와 !를 적절히 사용하는 것이 중요하다.