iOS ↔ Typescript 함수 공유 방법 (1)
Web에서 Native Application의 Function을 호출하고 싶을 때 사용하는 방법이다.
(예를 들어 Front에서 아이디저장, 자동로그인을 Checked했을 때 Native Application에 사용자 계정 정보를 Preferences에 담고 있어야 이 후에 앱이 다시 실행되면 아이디를 불러온다던지, 자동로그인을 할 수 있기 때문에.
사용자 계정 정보는 Web의 Session이나 Local Storage 등에 저장을 하게 되면 노출의 위험이 있을 뿐더러, 사용자가 캐시, 세션에 대한 데이터를 초기화를 진행했을 때 정보가 다 사라지기 때문에... 그리고 기간이 유효하니 Native Application에 저장하는 방식을 채택했다.)
먼저 Typescript에서는 아래와 같은 class를 하나 생성해주자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
declare global {
interface Window {
webkit: any;
}
}
declare global {
interface Native {
}
}
export class NativeService implements Native {
}
|
cs |
global 안에 interface가 2개 따져있는데, 하나는 Window, 하나는 Native에 대한 Interface다.
Window는 뭐냐면, Android나 iOS에서 Webview를 통해 함수를 주입하게 될 경우 'window' Object 안에 들어가게 된다.
(Android : window.(Android Native에서 주입할 때 정의한 Key).(함수명))
(iOS : window.webkit.messageHandlers.(iOS Native에서 사용하는 Key).(함수명))
iOS에 대해서 작성하고 있으니, Window라는 interface 안에다가 webkit을 넣어주자.
그래야지 window.webkit으로 사용이 가능하다.
그 다음 Native Interface는 Native Application에서 정의한 함수의 원형과 동일하게 작성해준다.
(이 Interface를 NativeService에서 사용할 것이니 자신이 생각하는 함수의 원형을 적고, 서비스에다가는 그 함수를 구현해주면 된다.)
아래는 위에 기술한 바와 같이 아이디 비밀번호 저장을 위해 iOS Preference에 저장하는 로직이다.
NativeService.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
declare global {
interface Window {
webkit: any;
}
}
declare global {
interface Native {
setPref: (key: string, data: string) => Promise<void>;
}
}
export class NativeService implements Native {
async setPref(key: string, data: string, isArray: boolean = false): Promise<void> {
await window.webkit.messageHandlers.native.postMessage(
{
'funcName': 'setPref',
'key': key,
'data': data
});
}
}
|
cs |
ViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
@available(iOS 8.0, *)
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage){
if message.name == "native" {
if let dictionary: [String: String] = message.body as? Dictionary {
if let funcName = dictionary["funcName"] {
switch funcName {
case "setPref":
if let _key = dictionary["key"] {
if _key == "" {
let alert = UIAlertController(title: "알림", message: "[오류] 웹 오류 발생\nKEY가 빈값입니다..", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
}))
self.present(alert, animated: true, completion: nil)
} else {
if let _data = dictionary["data"] {
self.preferences.setValue(_data, forKey: _key)
} else {
let alert = UIAlertController(title: "알림", message: "[오류] 웹 오류 발생\n설정값을 찾을 수 없습니다.", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
}))
self.present(alert, animated: true, completion: nil)
}
}
} else {
let alert = UIAlertController(title: "알림", message: "[오류] 웹 오류 발생\nKEY를 찾을 수 없습니다.", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
}))
self.present(alert, animated: true, completion: nil)
}
break;
default:
let alert = UIAlertController(title: "알림", message: "[오류] 웹 오류 발생\n정의되지 않은 funcName 입니다.\n[\(funcName)]", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
}))
self.present(alert, animated: true, completion: nil)
print("undefined functionName")
}
} else {
let alert = UIAlertController(title: "알림", message: "[오류] 웹 오류 발생\nfuncName을 찾을 수 없습니다.", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action: UIAlertAction!) in
}))
self.present(alert, animated: true, completion: nil)
print("funcName is Not Found!")
}
}
}
}
|
cs |
먼저, TypeScript부터 해석을 해보자면 16번째 줄부터 해석을 진행하면 된다.
window.webkit.messageHandlers.native.postMessage를 사용하게 될 경우에 Swift의 userContentController 딴으로 넘길 수 있다.
여기서 native 란, Swift 코드상의 message.name에 속하는 부분이다.
messageHandlers method를 사용하게 될 경우 뒤에 붙은 메소드가 'message.name' 이 되는거고, postMessage를 이용하게 되면 postMessage 함수 내부에 있는 데이터가 'message.body' 가 되는 것 이다.
현재 위에 기술되어있는 소스를 보면 window.webkit.messageHandlers.native.postMessage를 통해 JSON 형식으로 Body를 작성하여 Request 하였다.
Swift에서는 이를 받고 name이 사전에 정의한 'native' 가 맞을 때 처리를 진행하며, *Optional Bidning을 사용하여 message의 body를 *Dictionary 로 형변환을 진행했을 때 'Key: Value' 형식이 'String: String' 이 정상적으로 처리되었을 때 진행한다는 내용이다.
(Swift 소스 Line No 3, 4)
(*Optional Binding ? → If let 구문은 어떻게 동작할까?)
(*Dictionary ? → [Key: Value] 형식의 Collection)
정상적으로 Dictionary로 형변환을 진행하였으면, 이제 Key를 줘서 Value를 가져올 수 있는데 Key는 사전에 Web Application에서 정의한 것을 그대로 사용하면 된다.
위 함수가 작동되면, self.preferences(iOS Application의 저장공간)에 data가 저장되는 코드다.
지금 위 예제는 리턴 값이 없을때의 예제이고, 리턴값을 있게 하고 싶을 경우에는 후술하도록 하겠다.