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

Preview:

DESCRIPTION

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

Citation preview

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

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

Часть 1 JavaScript и WebView

Почему WebView?

5

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

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

Safari

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

WebView

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

6

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

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

7

〉Pdf

〉Microsoft Office

〉iWork

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

UIWebView

WKWebView!

WKWebView

〉WebKit framework

〉Богатый API

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

〉Быстрый JavaScript

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

10

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

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

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

11

@ iPhone 5s, iOS 8.1

WKWebView

UIWebView

0 375 750 1125 1500

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

WKWebView

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

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

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

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

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

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

12

Как?

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

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

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

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

14

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

15

WKWebView

@protocol WKNavigationDelegate; @protocol WKUIDelegate;

UIWebView

@protocol UIWebViewDelegate;

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

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;

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

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

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

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

21

WKWebView

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

UIWebView

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

Выполнение кода 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

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

23

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

UIWebView

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

24

WKWebView

@protocol WKScriptMessageHandler <NSObject>

@required

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

@end

25

@interface WKUserContentController : NSObject

...

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

@end !

!

!

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

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

26

UIWebView

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

27

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

JavaScript библиотеки

Пример: jQuery

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

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

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

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

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

28

NSURLProtocol (только UIWebView)

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

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

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

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

29

Саади

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

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

Inspired by

Примеры

〉Prerendering, DNS prefetching

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

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

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

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

〉Изменение DOM

32

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">

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() { ... }

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

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

36

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

function YACResumePausedHTMLAudio() { ... }

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

37

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

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

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

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

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

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

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

38

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

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; }

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; }

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

41

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

42

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

!

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

YACCollectLoginFormDataAndSendToNativeCode(e.target); }

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; }

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

44

Это:

〉Удобно

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

〉“Must have”

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

45

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

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

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

45

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

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

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

45

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

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

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

Реализация с помощью 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

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;

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

48

VIDEO

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

48

VIDEO

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

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

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

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

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

49

Часть 2 JavaScript без WebView

Зачем?

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

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

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

〉JavaScript!

52

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

〉Нативный UI

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

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

〉JavaScript!

53

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

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

54

Как?

JavaScriptCore

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

〉JavaScript движок в WebKit

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

56

© 2008 Goopymart

JavaScriptCore

〉JSContext

〉JSValue

〉<JSExport>

〉JSManagedValue

〉JSVirtualMachine

57

JSContext

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

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

!

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

58

JSValue

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

!

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

59

<JSExport>

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

60

@protocol MyClassExports <JSExport>

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

@end

@interface MyClass : NSObject <MyClassExports>

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

@end

Поехали!

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

62© CodeCombat http://codecombat.com

Реализация

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

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

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

63

Adventure

64

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

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

// TODO:

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

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

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

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

66

Создать экземпляр 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

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

@property APAIntelligentHeroCharacter* character; !

... !

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

68

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

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

@interface APAIntelligentHeroCharacter : APAHeroCharacter <APAIntelligentHeroCharacterExports> @end !

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

69

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

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

70

Demo

72

72

Two more things…

Управление памятью@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: для удаления ссылки

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

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

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

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

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

75

Материалы

〉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

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

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

79

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

Контакты

stonespb@yandex-team.ru

stonespb

dymv@yandex-team.ru

@_dymv

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

Recommended