83

Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

  • Upload
    yandex

  • View
    241

  • Download
    3

Embed Size (px)

DESCRIPTION

Зачем нужен JavaScript в iOS-приложениях

Citation preview

Page 1: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов
Page 2: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

JavaScript в iOS приложениях

Александр Денисов Евгений Дымов

Page 3: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Часть 1 JavaScript и WebView

Page 4: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Почему WebView?

Page 5: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

5

Просто и удобно

Открытие веб-страниц

Safari

Пользователь остается в нашем приложении

WebView

Page 6: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Социальная авторизация

6

Потому что никто не любит регистрироваться

Page 7: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Просмотр документов

7

〉Pdf

〉Microsoft Office

〉iWork

〉Медиа-контент

Page 8: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

UIWebView

Page 9: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

WKWebView!

Page 10: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

WKWebView

〉WebKit framework

〉Богатый API

〉Быстрый рендеринг

〉Быстрый JavaScript

〉Отдельный процесс

10

http://bigwol.com/surprised-baby-hd-widescreen-hd-free-wallpaper

Page 11: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Скорость выполнения JavaScript

SunSpider: https://www.webkit.org/perf/sunspider/sunspider.html

11

@ iPhone 5s, iOS 8.1

WKWebView

UIWebView

0 375 750 1125 1500

Время выполнения, мс

Page 12: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

WKWebView

Бонус: “из коробки” получаем:

〉Навигация жестами

〉Звонок с другого устройства

〉Открытие в новой вкладке

〉Заголовок страницы

API diff: http://nshipster.com/wkwebkit/

12

Page 13: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Как?

Page 14: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Что нам потребуется

〉Процесс загрузки страницы

〉Выполнение JavaScript

〉Связь JavaScript — нативный код

14

Page 15: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Процесс загрузки страницы

15

WKWebView

@protocol WKNavigationDelegate; @protocol WKUIDelegate;

UIWebView

@protocol UIWebViewDelegate;

Page 16: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

- (void)webView:(WKWebView*)webView decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;

- (void)webView:(WKWebView*)webView decidePolicyForNavigationResponse: (WKNavigationResponse*)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;

- (void)webView:(WKWebView*)webView didStartProvisionalNavigation:(WKNavigation*)navigation;

- (void)webView:(WKWebView*)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation*)navigation;

WKNavigationDelegate

16

Page 17: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

17

- (void)webView:(WKWebView*)webView didFailProvisionalNavigation:(WKNavigation*)navigation withError:(NSError*)error;

- (void)webView:(WKWebView*)webView didCommitNavigation:(WKNavigation*)navigation;

- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation;

- (void)webView:(WKWebView*)webView didFailNavigation:(WKNavigation*)navigation withError:(NSError*)error;

- (void)webView:(WKWebView*)webView didReceiveAuthenticationChallenge: (NSURLAuthenticationChallenge*)challenge completionHandler:(void (^)( NSURLSessionAuthChallengeDisposition disposition, NSURLCredential*credential))completionHandler;

Page 18: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

- (WKWebView*)webView:(WKWebView*)webView createWebViewWithConfiguration: (WKWebViewConfiguration*)configuration forNavigationAction:(WKNavigationAction*)navigationAction windowFeatures:(WKWindowFeatures*)windowFeatures;

- (void)webView:(WKWebView*)webView runJavaScriptAlertPanelWithMessage:(NSString*)message initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)())completionHandler;

WKUIDelegate

18

Page 19: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

19

- (void)webView:(WKWebView*)webView runJavaScriptConfirmPanelWithMessage:(NSString*)message initiatedByFrame:(WKFrameInfo*)frame completionHandler:(void (^)(BOOL result))completionHandler;

- (void)webView:(WKWebView*)webView runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt defaultText:(NSString*)defaultText initiatedByFrame:(WKFrameInfo*)frame

Page 20: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType;

- (void)webViewDidStartLoad:(UIWebView*)webView;

- (void)webViewDidFinishLoad:(UIWebView*)webView;

- (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error;

UIWebViewDelegate

20

Page 21: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Выполнение кода JavaScript

21

WKWebView

- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler;

UIWebView

- (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)script;

Page 22: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Выполнение кода JavaScript: пример

22

// Favicon NSString* faviconJS = @"document.querySelector(\"link[rel='shortcut icon']\")" ".getAttribute('href')"; [wkWebView evaluateJavaScript:faviconJS completionHandler: ^(NSString* favicon, NSError* error) { if (favicon) { [self didObtainFavicon:favicon]; } }];

WKWebView

Page 23: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Выполнение кода JavaScript: пример

23

// Заголовок страницы NSString* titleJS = @"document.title"; return [uiWebView stringByEvaluatingJavaScriptFromString:titleJS];

UIWebView

Page 24: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Связь JavaScript — нативный код

24

WKWebView

@protocol WKScriptMessageHandler <NSObject>

@required

- (void)userContentController: (WKUserContentController*)userContentController didReceiveScriptMessage:(WKScriptMessage*)message;

@end

Page 25: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

25

@interface WKUserContentController : NSObject

...

- (void)addScriptMessageHandler: (id<WKScriptMessageHandler>)scriptMessageHandler name:(NSString*)name;

@end !

!

!

// Добавление scriptMessageHandler создает для всех веб-фреймов // функцию: window.webkit.messageHandlers.<name>.postMessage(<messageBody>)

Page 26: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Связь JavaScript — нативный код

26

UIWebView

function YACSendToNativeCode(name, data) { var message = { }; message.name = name; message.data = data; window.location = "jsbridge://" + escape(JSON.stringify(message)); };

Page 27: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

27

- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType { if ([request.URL.scheme isEqualToString:@"jsbridge"]) { // Обработка сообщения return NO; } return YES; }

Page 28: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

JavaScript библиотеки

Пример: jQuery

〉Быстро получаем богатую функциональность

〉Возможен конфликт

〉Снижение производительности

〉Кроссбраузерная совместимость?

Можем реализовать необходимую функциональность самостоятельно!

28

Page 29: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

NSURLProtocol (только UIWebView)

Управление сетевым стеком с помощью NSURLProtocolhttps://tech.yandex.ru/events/yac/2013/talks/1076/

Можем реализовать:

〉Сжатие трафика

〉Поддержку новых форматов WebP WebM …

29

Page 30: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Саади

«Два человека бесплодно трудились и без пользы старались: тот, кто копил богатство и не пользовался им, и тот, кто учился наукам, но не применял их»

Page 31: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

А теперь делаем «фичи»!

Inspired by

Page 32: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Примеры

〉Prerendering, DNS prefetching

〉Управление медиаконтентом

〉Контекстное меню

〉Сохранение паролей

〉Социальная авторизация

〉Изменение DOM

32

Page 33: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Prerendering, DNS prefetching

DNS-запросы: от 1мс до нескольких секунд

Prerendering: субъективное ускорение загрузки страниц

33

<link rel="prerender" href="http://example.org/index.html">

<link rel="dns-prefetch" href="http://host-to-prefetch.com">

Page 34: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

34

function YACPrerenderLinks() { var results = [], nodeList = document.querySelector("link[rel='prerender']"); !

for (var i = 0; i < nodeList.length; i++) { var href = nodeList[i].getAttribute("href"); results.push(href); } return results.join(); } !

function YACDNSPrefetchLinks() { ... }

Page 35: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Управление медиаконтентом

var YACAudioElementsList = [ ], YACHTMLAudioElementPlay = window.HTMLAudioElement.prototype.play; !

function YACAddHTMLAudioElementToList(el) { YACHTMLAudioElementsList.push(el); } !

window.HTMLAudioElement.prototype.play = function() { YACAddHTMLAudioElementToList(this); this.play = YACHTMLAudioElementPlay; this.play(); }

35

Page 36: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

36

function YACPauseHTMLAudioElements() { for (i = 0; i < YACHTMLAudioElementsList.length; i++) { YACHTMLAudioElementsList[i].pause(); } } !

function YACResumePausedHTMLAudio() { ... }

Page 37: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Контекстное меню

37

〉Открыть вкладку

〉Копировать ссылку

〉Поделиться ссылкой

〉Сохранить изображение

〉Позвонить с другого устройства

Page 38: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Контекстное меню

// 1. Получаем элемент по координатам и декодируем из JSON // 2. Заполняем и показываем UIActionSheet - (void)openContextualMenuAtPoint:(CGPoint)point { YACInteractiveHTMLElement* element = [self interactiveElementAtPoint:point]; [self showContextualMenuForElement:element atPoint:point]; }

38

Разберем на примере YACImageLinkForTouchAtPoint

Page 39: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

39

function YACElementFromPoint(x, y) { var currentDocument = document, element = currentDocument.elementFromPoint(x, y), boundRect; try { while (element && (element.nodeName === "FRAME" || element.nodeName === "IFRAME")) { boundRect = element.getBoundingClientRect(); x -= boundRect.left; y -= boundRect.top; currentDocument = element.contentDocument; element = currentDocument.elementFromPoint(x, y); } } catch (e) { } return element; }

Page 40: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

40

function YACImageLinkForTouchAtPoint(x, y) { var element = YACElementFromPoint(x, y); return YACImageLinkForElement(element); } !

!

function YACImageLinkForElement(element) { if (element.tagName === "IMG" && element.src) { return element.src; } return null; }

Page 41: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Сохранение паролей

41

Обязательно спрашиваем разрешения!

Page 42: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

42

// 1. Перехватываем событие отправки формы // Обработчик вызывается для каждой отправляемой формы window.addEventListener("submit", YACFormSubmitHandler, false); !

!

// 2. Сохраняем заполненные данные в обработчике события function YACFormSubmitHandler(e) { if (e.target.nodeName !== "FORM") { return; } !

YACCollectLoginFormDataAndSendToNativeCode(e.target); }

Page 43: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

43

// 3. Заполняем данные при загрузке страницы с такой же формой function YACFillLoginFormWithData( form, usernameElementName, usernameValue, passwordElementName, passwordValue) { !

var usernameElement = form.querySelector( "input[name='" + usernameElementName + "']"); usernameElement.value = usernameValue; !

var passwordElement = form.querySelector( "input[type='password'][name='" + passwordElementName + "']"); passwordElement.value = passwordValue; }

Page 44: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Социальная авторизация

44

Это:

〉Удобно

〉Привычно для пользователя

〉“Must have”

Page 45: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Социальная авторизация

45

Реализация с помощью WebView:

Доступ к данным на странице авторизации!

Page 46: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Социальная авторизация

45

Реализация с помощью WebView:

Доступ к данным на странице авторизации!

Page 47: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Социальная авторизация

45

Реализация с помощью WebView:

Доступ к данным на странице авторизации!

Совет: не используйте WebView для социальной авторизации

Page 48: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Реализация с помощью OAuth

1. Регистрируем приложение

2. Отправляем запрос https://oauth.yandex.ru/authorize? response_type=token & client_id=<идентификатор приложения>

3. Пользователь авторизуется и выдает разрешения для нашего приложения

4. Получаем данные в ответе http://www.example.com/token# access_token=<новый OAuth-токен> & expires_in=<время жизни токена в секундах>

46

Page 49: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

47

// 1. Native SDK !

// 2. Callback URL scheme - обрабатываем в AppDelegate - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url sourceApplication:(NSString*)sourceApplication annotation:(id)annotation; !

// 3. WebView (пример для UIWebView, WKWebView - аналогично) - (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType;

Page 50: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Изменение DOM с помощью JavaScript

48

VIDEO

Page 51: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Изменение DOM с помощью JavaScript

48

VIDEO

Page 52: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Что же получилось?

〉Мы разобрались в возможностях UIWebView и WKWebView

〉Научились писать “фичи” для веб-контента

〉Делаем пользователей счастливее

〉И все это — благодаря JavaScript!

49

Page 53: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Часть 2 JavaScript без WebView

Page 54: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Зачем?

Page 55: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Расширяемость

〉Внешняя логика

〉Интерпретация в runtime

〉JavaScript!

52

Page 56: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Платформонезависимость

〉Нативный UI

〉Общая бизнес-логика

〉Мгновенное обновление бизнес-логики

〉JavaScript!

53

Page 57: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Программируемость

Мы продолжаем наши передачи на этой частоте!

54

Page 58: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Как?

Page 59: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

JavaScriptCore

〉Objective-C API доступно с iOS 7, Mac OS X 10.9

〉JavaScript движок в WebKit

〉Также известен как SquirrelFish, Nitro

56

© 2008 Goopymart

Page 60: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

JavaScriptCore

〉JSContext

〉JSValue

〉<JSExport>

〉JSManagedValue

〉JSVirtualMachine

57

Page 61: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

JSContext

〉Среда выполнения JavaScript кода

〉Глобальный объект (à la window)

!

- (JSValue*)evaluateScript:(NSString*)script;

58

Page 62: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

JSValue

Ссылка на JavaScript объект

!

+ (JSValue*)valueWithObject:(id)value inContext:(JSContext*)context;

59

Page 63: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

<JSExport>

Экспорт методов и свойств в JavaScript окружение

60

@protocol MyClassExports <JSExport>

- (void)foo; // Доступен из JavaScript

@end

@interface MyClass : NSObject <MyClassExports>

- (void)bar; // Недоступен из JavaScript

@end

Page 64: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Поехали!

Page 65: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Программируемость?

62© CodeCombat http://codecombat.com

Page 66: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Реализация

〉Нативная графика — Sprite Kit

〉Управление персонажем — JavaScript

〉Соединительное звено — JavaScriptCore

63

Page 67: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Adventure

64

© 2013 Apple https://developer.apple.com

Page 68: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

CharacterAI.js

var CharacterAI = function() { }; !

CharacterAI.prototype.onIdle = function(event) { this.character.ahead(100); this.character.turn(45); this.character.back(100); }; !

CharacterAI.prototype.onScannedEnemy = function(event) { this.character.fire(); }; ...

65

Page 69: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

// TODO:

〉Создать экземпляр CharacterAI

〉Передать персонаж в JavaScript окружение

〉Экспортировать необходимые методы

〉Сообщать о событиях (onIdle, onScannedEnemy…)

66

Page 70: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Создать экземпляр CharacterAI

@property JSContext* context; @property JSValue* characterAI; !

... !

self.context = [[JSContext alloc] init]; [self.context evaluateScript:self.script.contents]; self.characterAI = [self.context evaluateScript:@"new CharacterAI()"];

67

Page 71: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Передать персонаж в JavaScript окружение

@property APAIntelligentHeroCharacter* character; !

... !

self.characterAI[@"character"] = self.character;

68

Page 72: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Экспортировать необходимые методы

@protocol APAIntelligentHeroCharacterExports <JSExport> - (void)ahead:(CGFloat)amount; ... @end !

@interface APAIntelligentHeroCharacter : APAHeroCharacter <APAIntelligentHeroCharacterExports> @end !

@implementation APAIntelligentHeroCharacter - (void)ahead:(CGFloat)amount { } ... @end

69

Page 73: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Сообщать о событиях

- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)interval { ... if (self.character.state == APACharacterStateIdle) { [self.characterAI invokeMethod:@"onIdle" withArguments:@[ event ]]; } ... }

70

Page 74: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Demo

Page 75: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

72

Page 76: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

72

Page 77: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Two more things…

Page 78: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Управление памятью@property JSValue* characterAI; ... - (void)setCharacterAI:(JSValue*)characterAI { _managedCharacterAI = [JSManagedValue managedValueWithValue:characterAI]; [characterAI.context.virtualMachine addManagedReference:_managedCharacterAI withOwner:self]; } !

- (JSValue*)characterAI { return [_managedCharacterAI value]; }

74

removeManagedReference:withOwner: для удаления ссылки

Page 79: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Многопоточность

〉API потокобезопасно

〉Область блокировки — JSVirtualMachine

〉JSVirtualMachine — контейнер для JSContext

〉Использование отдельной JSVirtualMachine для каждого потока

75

Page 80: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Материалы

〉WWDC 2014 Introducing the Modern WebKit API

〉nshipster.com/wkwebkit

〉tech.yandex.ru/events/yac/2013/talks/1076

〉WWDC 2013 Integrating JavaScript into Native Apps

〉trac.webkit.org/wiki/JavaScriptCore

〉github.com/WebKit/webkit

〉bit.ly/yac2014-ios-jstalks76

Page 81: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Зачем JavaScript в iOSприложениях?

Page 82: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

Спасибо за внимание!

Page 83: Зачем нужен JavaScript в iOS-приложениях. Евгений Дымов

79

Александр Денисов

Контакты

[email protected]

stonespb

[email protected]

@_dymv

Евгений Дымов