SlideShare a Scribd company logo
AppDelegate
разделяй и властвуй
Вадим Смаль
iOS разработчик Rambler&Co
AppDelegate
UIResponder
<UIApplicationDelegate>
• Реагирует на получение уведомлений
• Реагирует на ключевые изменения в
состоянии вашего приложения
• Реагирует на события, которые
нацелены на само приложение
• Управляет процессом сохранения и
восстановления состояния приложения
Запуск
приложения
Изменение
состояния
приложения
Восстановление
состояния
приложения
Загрузка данных
в фоне
Локальные и
удаленные
уведомления
Пользовательская
активность
WatchKit
Открытие URL’ов
HealthKit
Системные
события
Разрешения для
расширений
Геометрия
интерфейса
Window
CoreData
SharedInstance
Quick Actions
<UIApplicationDelegate>
import Shared
import Storage
import AVFoundation
import XCGLogger
import Breakpad
import MessageUI
import WebImage
import SwiftKeychainWrapper
import LocalAuthentication
private let log = Logger.browserLogger
let LatestAppVersionProfileKey = "latestAppVersion"
let AllowThirdPartyKeyboardsKey = "settings.allowThirdPartyKeyboards"
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var browserViewController: BrowserViewController!
var rootViewController: UINavigationController!
weak var profile: BrowserProfile?
var tabManager: TabManager!
var adjustIntegration: AdjustIntegration?
weak var application: UIApplication?
var launchOptions: [NSObject: AnyObject]?
let appVersion = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as! String
var openInFirefoxURL: NSURL? = nil
func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Hold references to willFinishLaunching parameters for delayed app launch
self.application = application
self.launchOptions = launchOptions
log.debug("Configuring window…")
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
self.window!.backgroundColor = UIConstants.AppBackgroundColor
// Short circuit the app if we want to email logs from the debug menu
if DebugSettingsBundleOptions.launchIntoEmailComposer {
self.window?.rootViewController = UIViewController()
presentEmailComposerWithLogs()
return true
} else {
return startApplication(application, withLaunchOptions: launchOptions)
}
}
Импорты
Зависимости
WINDOW
startApplication
private func startApplication(application: UIApplication, withLaunchOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
log.debug("Setting UA…")
// Set the Firefox UA for browsing.
setUserAgent()
log.debug("Starting keyboard helper…")
// Start the keyboard helper to monitor and cache keyboard state.
KeyboardHelper.defaultHelper.startObserving()
log.debug("Starting dynamic font helper…")
// Start the keyboard helper to monitor and cache keyboard state.
DynamicFontHelper.defaultHelper.startObserving()
log.debug("Setting custom menu items…")
MenuHelper.defaultHelper.setItems()
log.debug("Creating Sync log file…")
let logDate = NSDate()
// Create a new sync log file on cold app launch. Note that this doesn't roll old logs.
Logger.syncLogger.newLogWithDate(logDate)
log.debug("Creating corrupt DB logger…")
Logger.corruptLogger.newLogWithDate(logDate)
log.debug("Creating Browser log file…")
Logger.browserLogger.newLogWithDate(logDate)
log.debug("Getting profile…")
let profile = getProfile(application)
if !DebugSettingsBundleOptions.disableLocalWebServer {
log.debug("Starting web server…")
// Set up a web server that serves us static content. Do this early so that it is ready when the UI is presented.
setUpWebServer(profile)
}
log.debug("Setting AVAudioSession category…")
do {
// for aural progress bar: play even with silent switch on, and do not stop audio from other apps (like music)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions:
AVAudioSessionCategoryOptions.MixWithOthers)
} catch _ {
log.error("Failed to assign AVAudioSession category to allow playing with silent switch on for aural progress bar")
}
Настройка
приложения
let defaultRequest = NSURLRequest(URL: UIConstants.DefaultHomePage)
let imageStore = DiskImageStore(files: profile.files, namespace: "TabManagerScreenshots", quality:
UIConstants.ScreenshotQuality)
log.debug("Configuring tabManager…")
self.tabManager = TabManager(defaultNewTabRequest: defaultRequest, prefs: profile.prefs, imageStore: imageStore)
self.tabManager.stateDelegate = self
// Add restoration class, the factory that will return the ViewController we
// will restore with.
log.debug("Initing BVC…")
browserViewController = BrowserViewController(profile: self.profile!, tabManager: self.tabManager)
browserViewController.restorationIdentifier = NSStringFromClass(BrowserViewController.self)
browserViewController.restorationClass = AppDelegate.self
browserViewController.automaticallyAdjustsScrollViewInsets = false
rootViewController = UINavigationController(rootViewController: browserViewController)
rootViewController.automaticallyAdjustsScrollViewInsets = false
rootViewController.delegate = self
rootViewController.navigationBarHidden = true
self.window!.rootViewController = rootViewController
log.debug("Configuring Breakpad…")
activeCrashReporter = BreakpadCrashReporter(breakpadInstance: BreakpadController.sharedInstance())
configureActiveCrashReporter(profile.prefs.boolForKey("crashreports.send.always"))
log.debug("Adding observers…")
NSNotificationCenter.defaultCenter().addObserverForName(FSReadingListAddReadingListItemNotification, object: nil, queue: nil)
{ (notification) -> Void in
if let userInfo = notification.userInfo, url = userInfo["URL"] as? NSURL {
let title = (userInfo["Title"] as? String) ?? ""
profile.readingList?.createRecordWithURL(url.absoluteString, title: title, addedBy: UIDevice.currentDevice().name)
}
}
// check to see if we started 'cos someone tapped on a notification.
if let localNotification = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification {
viewURLInNewTab(localNotification)
}
adjustIntegration = AdjustIntegration(profile: profile)
// We need to check if the app is a clean install to use for
// preventing the What's New URL from appearing.
if getProfile(application).prefs.intForKey(IntroViewControllerSeenProfileKey) == nil {
getProfile(application).prefs.setString(AppInfo.appVersion, forKey: LatestAppVersionProfileKey)
}
log.debug("Updating authentication keychain state to reflect system state")
self.updateAuthenticationInfo()
log.debug("Done with setting up the application.")
return true
}
func applicationWillTerminate(application: UIApplication) {
log.debug("Application will terminate.")
// We have only five seconds here, so let's hope this doesn't take too long.
self.profile?.shutdown()
// Allow deinitializers to close our database connections.
self.profile = nil
self.tabManager = nil
self.browserViewController = nil
self.rootViewController = nil
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
// Override point for customization after application launch.
var shouldPerformAdditionalDelegateHandling = true
log.debug("Did finish launching.")
log.debug("Setting up Adjust")
self.adjustIntegration?.triggerApplicationDidFinishLaunchingWithOptions(launchOptions)
log.debug("Making window key and visible…")
self.window!.makeKeyAndVisible()
// Now roll logs.
log.debug("Triggering log roll.")
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
Logger.syncLogger.deleteOldLogsDownToSizeLimit()
Logger.browserLogger.deleteOldLogsDownToSizeLimit()
}
if #available(iOS 9, *) {
// If a shortcut was launched, display its information and take the appropriate action
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem {
QuickActions.sharedInstance.launchedShortcutItem = shortcutItem
// This will block "performActionForShortcutItem:completionHandler" from being called.
shouldPerformAdditionalDelegateHandling = false
}
}
log.debug("Done with applicationDidFinishLaunching.")
return shouldPerformAdditionalDelegateHandling
}
Quick Actions
func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool {
if let components = NSURLComponents(URL: url, resolvingAgainstBaseURL: false) {
if components.scheme != "firefox" && components.scheme != "firefox-x-callback" {
return false
}
var url: String?
for item in (components.queryItems ?? []) as [NSURLQueryItem] {
switch item.name {
case "url":
url = item.value
default: ()
}
}
if let url = url, newURL = NSURL(string: url.unescape()) {
// If we are active then we can ask the BVC to open the new tab right away. Else we remember the
// URL and we open it in applicationDidBecomeActive.
if application.applicationState == .Active {
if #available(iOS 9, *) {
self.browserViewController.switchToPrivacyMode(isPrivate: false)
}
self.browserViewController.openURLInNewTab(newURL)
} else {
openInFirefoxURL = newURL
}
return true
}
}
return false
}
func application(application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: String) -> Bool {
if let thirdPartyKeyboardSettingBool = getProfile(application).prefs.boolForKey(AllowThirdPartyKeyboardsKey) where
extensionPointIdentifier == UIApplicationKeyboardExtensionPointIdentifier {
return thirdPartyKeyboardSettingBool
}
return true
}
Открытие URL’ов
Разрешения для
расширений
func applicationDidBecomeActive(application: UIApplication) {
guard !DebugSettingsBundleOptions.launchIntoEmailComposer else {
return
}
self.profile?.syncManager.applicationDidBecomeActive()
// We could load these here, but then we have to futz with the tab counter
// and making NSURLRequests.
self.browserViewController.loadQueuedTabs()
// handle quick actions is available
if #available(iOS 9, *) {
let quickActions = QuickActions.sharedInstance
if let shortcut = quickActions.launchedShortcutItem {
// dispatch asynchronously so that BVC is all set up for handling new tabs
// when we try and open them
quickActions.handleShortCutItem(shortcut, withBrowserViewController: browserViewController)
quickActions.launchedShortcutItem = nil
}
// we've removed the Last Tab option, so we should remove any quick actions that we already have that are last tabs
// we do this after we've handled any quick actions that have been used to open the app so that we don't b0rk if
// the user has opened the app for the first time after upgrade with a Last Tab quick action
QuickActions.sharedInstance.removeDynamicApplicationShortcutItemOfType(ShortcutType.OpenLastTab, fromApplication:
application)
}
// If we have a URL waiting to open, switch to non-private mode and open the URL.
if let url = openInFirefoxURL {
openInFirefoxURL = nil
// This needs to be scheduled so that the BVC is ready.
dispatch_async(dispatch_get_main_queue()) {
if #available(iOS 9, *) {
self.browserViewController.switchToPrivacyMode(isPrivate: false)
}
self.browserViewController.switchToTabForURLOrOpen(url)
}
}
}
func applicationWillEnterForeground(application: UIApplication) {
// The reason we need to call this method here instead of `applicationDidBecomeActive`
// is that this method is only invoked whenever the application is entering the foreground where as
// `applicationDidBecomeActive` will get called whenever the Touch ID authentication overlay disappears.
self.updateAuthenticationInfo()
}
private func updateAuthenticationInfo() {
if let authInfo = KeychainWrapper.authenticationInfo() {
if !LAContext().canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: nil) {
authInfo.useTouchID = false
KeychainWrapper.setAuthenticationInfo(authInfo)
}
}
}
Quick Actions
func applicationDidEnterBackground(application: UIApplication) {
self.profile?.syncManager.applicationDidEnterBackground()
var taskId: UIBackgroundTaskIdentifier = 0
taskId = application.beginBackgroundTaskWithExpirationHandler { _ in
log.warning("Running out of background time, but we have a profile shutdown pending.")
application.endBackgroundTask(taskId)
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
self.profile?.shutdown()
application.endBackgroundTask(taskId)
}
// Workaround for crashing in the background when <select> popovers are visible (rdar://24571325).
let jsBlurSelect = "if (document.activeElement && document.activeElement.tagName === 'SELECT')
{ document.activeElement.blur(); }"
tabManager.selectedTab?.webView?.evaluateJavaScript(jsBlurSelect, completionHandler: nil)
}
func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification:
UILocalNotification, completionHandler: () -> Void) {
if let actionId = identifier {
if let action = SentTabAction(rawValue: actionId) {
viewURLInNewTab(notification)
switch(action) {
case .Bookmark:
addBookmark(notification)
break
case .ReadingList:
addToReadingList(notification)
break
default:
break
}
} else {
print("ERROR: Unknown notification action received")
}
} else {
print("ERROR: Unknown notification received")
}
}
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
viewURLInNewTab(notification)
}
Загрузка данных
в фоне
Локальные и
удаленные
уведомления
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) ->
Void) -> Bool {
if let url = userActivity.webpageURL {
browserViewController.switchToTabForURLOrOpen(url)
return true
}
return false
}
@available(iOS 9.0, *)
func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem,
completionHandler: Bool -> Void) {
let handledShortCutItem = QuickActions.sharedInstance.handleShortCutItem(shortcutItem, withBrowserViewController:
browserViewController)
completionHandler(handledShortCutItem)
}
var activeCrashReporter: CrashReporter?
func configureActiveCrashReporter(optedIn: Bool?) {
if let reporter = activeCrashReporter {
configureCrashReporter(reporter, optedIn: optedIn)
}
}
public func configureCrashReporter(reporter: CrashReporter, optedIn: Bool?) {
let configureReporter: () -> () = {
let addUploadParameterForKey: String -> Void = { key in
if let value = NSBundle.mainBundle().objectForInfoDictionaryKey(key) as? String {
reporter.addUploadParameter(value, forKey: key)
}
}
addUploadParameterForKey("AppID")
addUploadParameterForKey("BuildID")
addUploadParameterForKey("ReleaseChannel")
addUploadParameterForKey("Vendor")
}
if let optedIn = optedIn {
// User has explicitly opted-in for sending crash reports. If this is not true, then the user has
// explicitly opted-out of crash reporting so don't bother starting breakpad or stop if it was running
if optedIn {
reporter.start(true)
configureReporter()
reporter.setUploadingEnabled(true)
} else {
reporter.stop()
}
}
// We haven't asked the user for their crash reporting preference yet. Log crashes anyways but don't send them.
else {
reporter.start(true)
configureReporter()
}
}
Пользовательска
я активность
Пользовательска
я активность
ThirdParties
// MARK: - Root View Controller Animations
extension AppDelegate: UINavigationControllerDelegate {
func navigationController(navigationController: UINavigationController,
animationControllerForOperation operation: UINavigationControllerOperation,
fromViewController fromVC: UIViewController,
toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == UINavigationControllerOperation.Push {
return BrowserToTrayAnimator()
} else if operation == UINavigationControllerOperation.Pop {
return TrayToBrowserAnimator()
} else {
return nil
}
}
}
extension AppDelegate: TabManagerStateDelegate {
func tabManagerWillStoreTabs(tabs: [Browser]) {
// It is possible that not all tabs have loaded yet, so we filter out tabs with a nil URL.
let storedTabs: [RemoteTab] = tabs.flatMap( Browser.toTab )
// Don't insert into the DB immediately. We tend to contend with more important
// work like querying for top sites.
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(ProfileRemoteTabsSyncDelay * Double(NSEC_PER_MSEC))), queue) {
self.profile?.storeTabs(storedTabs)
}
}
}
extension AppDelegate: MFMailComposeViewControllerDelegate {
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error:
NSError?) {
// Dismiss the view controller and start the app up
controller.dismissViewControllerAnimated(true, completion: nil)
startApplication(application!, withLaunchOptions: self.launchOptions)
}
}
Стек навигации
AppDelegate:
45 методов
~1000 строк кода
~ 30 зависимостей
Принцип единственной
обязанности
Принцип разделения
интерфейса
Тестируемость
Расширяемость
<UIApplicationDelegate> Логика
StartConfigurator
ThirdPartiesConfigurator
ApplicationConfigurator
AppStateConfigurator
HandoffHandler
SpotlightIndexer
QuickActionHandler
…
…
…
AppDelegate
didFinishLaunchingWithOptions {
[self.thirdPartiesConfigurator configure]
[self.startConfigurator configure]
…
[self.applicationConfigurator configure]
[self.handoffHandler activate]
[self.spotlightIndexer activate]
…
[self.quickActionHandler activate]
…
}
@interface AppDelegate : UIResponder
<UIApplicationDelegate>
@property UIWindow *window;
@property startConfigurator;
@property thirdPartiesConfigurator;
@property applicationConfigurator;
...
@property handoffHandler;
@property quickActionHandler ;
@property NSArray *urlHandlers;
@property forceLogoutHandler;
...
@end
AppDelegate:
45 методов
~200 строк кода
~20 зависимостей
Принцип единственной
обязанности
Принцип разделения
интерфейса
Тестируемость
God object
<UIApplicationDelegate>
Зависимости
Launching
Search
RemoteNotification
QuickAction
URLHandler
Handoff
ApplicationState BackgroundData
AppDelegateProxy AppDelegate
Array<UIApplicationDelegate>
@implementation RemoteNotificationAppDelegate
- didFinishLaunchingWithOptions {
if (notification) {
[self.pushNotificationCenter process:notification];
}
return YES;
}
- didRegisterForRemoteNotificationsWithDeviceToken: {
[self.pushNotificationCenter didRegisterDeviceToken:token];
}
- didReceiveRemoteNotification {
[self.pushNotificationCenter processWithUserInfo:userInfo];
}
@end
AppDelegate:
~ 3 метода
~50 строк кода
~2 зависимостей
Принцип единственной
обязанности
Принцип разделения
интерфейса
Тестируемость
Принципип “разделяй
и властвуй”
<UIApplicationDelegate>
AppDelegateProxy
RemoteNotificationAppDelegateAppDelegateProxyPUSH NOTIFICATION
<PushNotificationCenter><StartUpConfigurator>
<NavigationStackBuilder> Present navigation stack
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc,
argv,
nil,
NSStringFromClass([RCMAppDelegateProxy class]));
}
}
@interface RCMAppDelegateProxy : NSProxy
- (void)addAppDelegate:(id<UIApplicationDelegate>)delegate;
- (void)addAppDelegates:(NSArray *)delegates;
@end
Где еще использовать?
<UITableViewDelegate>/<UITableViewDataSource>
<UICollectionViewDelegate>/
<UICollectionViewDataSource>
<UITextViewDelegate>
Спасибо!
RamblerAppDelegateProxy
https://github.com/rambler-ios/RamblerAppDelegateProxy

More Related Content

What's hot

Workshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte IIWorkshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte II
Visual Engineering
 
Workshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSWorkshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJS
Visual Engineering
 
Angular 1 + es6
Angular 1 + es6Angular 1 + es6
Angular 1 + es6
장현 한
 
Practical Protocol-Oriented-Programming
Practical Protocol-Oriented-ProgrammingPractical Protocol-Oriented-Programming
Practical Protocol-Oriented-Programming
Natasha Murashev
 
The AngularJS way
The AngularJS wayThe AngularJS way
The AngularJS way
Boyan Mihaylov
 
Introduction to Angularjs
Introduction to AngularjsIntroduction to Angularjs
Introduction to Angularjs
Manish Shekhawat
 
Wicket 6
Wicket 6Wicket 6
Wicket 6
codepitbull
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applications
Gabor Varadi
 
Angular js
Angular jsAngular js
Angular js
Behind D Walls
 
Reactive state management with Jetpack Components
Reactive state management with Jetpack ComponentsReactive state management with Jetpack Components
Reactive state management with Jetpack Components
Gabor Varadi
 
Building scalable applications with angular js
Building scalable applications with angular jsBuilding scalable applications with angular js
Building scalable applications with angular js
Andrew Alpert
 
Speed up your GWT coding with gQuery
Speed up your GWT coding with gQuerySpeed up your GWT coding with gQuery
Speed up your GWT coding with gQuery
Manuel Carrasco Moñino
 
Jetpack, with new features in 2021 GDG Georgetown IO Extended
Jetpack, with new features in 2021 GDG Georgetown IO ExtendedJetpack, with new features in 2021 GDG Georgetown IO Extended
Jetpack, with new features in 2021 GDG Georgetown IO Extended
Toru Wonyoung Choi
 
Workshop 17: EmberJS parte II
Workshop 17: EmberJS parte IIWorkshop 17: EmberJS parte II
Workshop 17: EmberJS parte II
Visual Engineering
 
Angular performance slides
Angular performance slidesAngular performance slides
Angular performance slides
David Barreto
 
Angular 2 Component Communication - Talk by Rob McDiarmid
Angular 2 Component Communication - Talk by Rob McDiarmidAngular 2 Component Communication - Talk by Rob McDiarmid
Angular 2 Component Communication - Talk by Rob McDiarmid
Amrita Chopra
 
MBLTDev15: Egor Tolstoy, Rambler&Co
MBLTDev15: Egor Tolstoy, Rambler&CoMBLTDev15: Egor Tolstoy, Rambler&Co
MBLTDev15: Egor Tolstoy, Rambler&Co
e-Legion
 
Workshop 19: ReactJS Introduction
Workshop 19: ReactJS IntroductionWorkshop 19: ReactJS Introduction
Workshop 19: ReactJS Introduction
Visual Engineering
 
Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!
Stacy Devino
 
Workshop 3: JavaScript build tools
Workshop 3: JavaScript build toolsWorkshop 3: JavaScript build tools
Workshop 3: JavaScript build tools
Visual Engineering
 

What's hot (20)

Workshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte IIWorkshop 13: AngularJS Parte II
Workshop 13: AngularJS Parte II
 
Workshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSWorkshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJS
 
Angular 1 + es6
Angular 1 + es6Angular 1 + es6
Angular 1 + es6
 
Practical Protocol-Oriented-Programming
Practical Protocol-Oriented-ProgrammingPractical Protocol-Oriented-Programming
Practical Protocol-Oriented-Programming
 
The AngularJS way
The AngularJS wayThe AngularJS way
The AngularJS way
 
Introduction to Angularjs
Introduction to AngularjsIntroduction to Angularjs
Introduction to Angularjs
 
Wicket 6
Wicket 6Wicket 6
Wicket 6
 
State management in android applications
State management in android applicationsState management in android applications
State management in android applications
 
Angular js
Angular jsAngular js
Angular js
 
Reactive state management with Jetpack Components
Reactive state management with Jetpack ComponentsReactive state management with Jetpack Components
Reactive state management with Jetpack Components
 
Building scalable applications with angular js
Building scalable applications with angular jsBuilding scalable applications with angular js
Building scalable applications with angular js
 
Speed up your GWT coding with gQuery
Speed up your GWT coding with gQuerySpeed up your GWT coding with gQuery
Speed up your GWT coding with gQuery
 
Jetpack, with new features in 2021 GDG Georgetown IO Extended
Jetpack, with new features in 2021 GDG Georgetown IO ExtendedJetpack, with new features in 2021 GDG Georgetown IO Extended
Jetpack, with new features in 2021 GDG Georgetown IO Extended
 
Workshop 17: EmberJS parte II
Workshop 17: EmberJS parte IIWorkshop 17: EmberJS parte II
Workshop 17: EmberJS parte II
 
Angular performance slides
Angular performance slidesAngular performance slides
Angular performance slides
 
Angular 2 Component Communication - Talk by Rob McDiarmid
Angular 2 Component Communication - Talk by Rob McDiarmidAngular 2 Component Communication - Talk by Rob McDiarmid
Angular 2 Component Communication - Talk by Rob McDiarmid
 
MBLTDev15: Egor Tolstoy, Rambler&Co
MBLTDev15: Egor Tolstoy, Rambler&CoMBLTDev15: Egor Tolstoy, Rambler&Co
MBLTDev15: Egor Tolstoy, Rambler&Co
 
Workshop 19: ReactJS Introduction
Workshop 19: ReactJS IntroductionWorkshop 19: ReactJS Introduction
Workshop 19: ReactJS Introduction
 
Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!Async task, threads, pools, and executors oh my!
Async task, threads, pools, and executors oh my!
 
Workshop 3: JavaScript build tools
Workshop 3: JavaScript build toolsWorkshop 3: JavaScript build tools
Workshop 3: JavaScript build tools
 

Viewers also liked

Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101
RAMBLER&Co
 
NSNotificationCenter vs. AppDelegate
NSNotificationCenter vs. AppDelegateNSNotificationCenter vs. AppDelegate
NSNotificationCenter vs. AppDelegate
John Wilker
 
iOS Dev Moscow: Как получать заказы по рекомендациям
iOS Dev Moscow: Как получать заказы по рекомендациямiOS Dev Moscow: Как получать заказы по рекомендациям
iOS Dev Moscow: Как получать заказы по рекомендациям
Alina Mikhaylova
 
Concurrent Programming in iOS
Concurrent Programming in iOSConcurrent Programming in iOS
Concurrent Programming in iOS
Sam Mejlumyan
 
Как пройти собеседование и получить первую работу на Swift
Как пройти собеседование и получить первую работу на SwiftКак пройти собеседование и получить первую работу на Swift
Как пройти собеседование и получить первую работу на Swift
Anton Loginov
 
Интуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейс
Интуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейсИнтуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейс
Интуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейсГлеб Тарасов
 
Роман Бусыгин "Yandex Map Kit для iOS в примерах"
Роман Бусыгин "Yandex Map Kit для iOS в примерах"Роман Бусыгин "Yandex Map Kit для iOS в примерах"
Роман Бусыгин "Yandex Map Kit для iOS в примерах"
Yandex
 
Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...
Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...
Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...
Глеб Тарасов
 
Архитектура компилятора Swift
Архитектура компилятора SwiftАрхитектура компилятора Swift
Архитектура компилятора Swift
Andrey Volobuev
 
Преимущества и недостатки языка Swift
Преимущества и недостатки языка SwiftПреимущества и недостатки языка Swift
Преимущества и недостатки языка Swift
Andrey Volobuev
 
CS193P Lecture 5 View Animation
CS193P Lecture 5 View AnimationCS193P Lecture 5 View Animation
CS193P Lecture 5 View Animation
onoaonoa
 
Denis Lebedev, Swift
Denis  Lebedev, SwiftDenis  Lebedev, Swift
Denis Lebedev, Swift
Yandex
 
Мобильный веб: назад в будущее
Мобильный веб: назад в будущееМобильный веб: назад в будущее
Мобильный веб: назад в будущее
Badoo Development
 
Технологии vs коммуникации: что важнее?
Технологии vs коммуникации: что важнее?Технологии vs коммуникации: что важнее?
Технологии vs коммуникации: что важнее?
Badoo Development
 
Багфиксинг процесса разработки в iOS: взгляд с двух сторон
Багфиксинг процесса разработки в iOS: взгляд с двух сторонБагфиксинг процесса разработки в iOS: взгляд с двух сторон
Багфиксинг процесса разработки в iOS: взгляд с двух сторон
Badoo Development
 
Как автотесты ускоряют релизы в OK.ru
Как автотесты ускоряют релизы в OK.ruКак автотесты ускоряют релизы в OK.ru
Как автотесты ускоряют релизы в OK.ru
Badoo Development
 
Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...
Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...
Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...
AvitoTech
 
Вадим Дробинин. Защищаем себя и пользователей: руководство по безопасности
Вадим Дробинин. Защищаем себя и пользователей: руководство по безопасностиВадим Дробинин. Защищаем себя и пользователей: руководство по безопасности
Вадим Дробинин. Защищаем себя и пользователей: руководство по безопасности
AvitoTech
 
How to Lean
How to LeanHow to Lean
How to Lean
Jake Causby
 
Юзабилити и функциональность ДБО2017
Юзабилити и функциональность ДБО2017Юзабилити и функциональность ДБО2017
Юзабилити и функциональность ДБО2017
Дмитрий Силаев
 

Viewers also liked (20)

Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101Rambler.iOS #7: Интернет-эквайринг 101
Rambler.iOS #7: Интернет-эквайринг 101
 
NSNotificationCenter vs. AppDelegate
NSNotificationCenter vs. AppDelegateNSNotificationCenter vs. AppDelegate
NSNotificationCenter vs. AppDelegate
 
iOS Dev Moscow: Как получать заказы по рекомендациям
iOS Dev Moscow: Как получать заказы по рекомендациямiOS Dev Moscow: Как получать заказы по рекомендациям
iOS Dev Moscow: Как получать заказы по рекомендациям
 
Concurrent Programming in iOS
Concurrent Programming in iOSConcurrent Programming in iOS
Concurrent Programming in iOS
 
Как пройти собеседование и получить первую работу на Swift
Как пройти собеседование и получить первую работу на SwiftКак пройти собеседование и получить первую работу на Swift
Как пройти собеседование и получить первую работу на Swift
 
Интуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейс
Интуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейсИнтуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейс
Интуит. Разработка приложений для iOS. Лекция 9. Нестандартный интерфейс
 
Роман Бусыгин "Yandex Map Kit для iOS в примерах"
Роман Бусыгин "Yandex Map Kit для iOS в примерах"Роман Бусыгин "Yandex Map Kit для iOS в примерах"
Роман Бусыгин "Yandex Map Kit для iOS в примерах"
 
Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...
Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...
Интуит. Разработка приложений для iOS. Лекция 11. Расширенные возможности уст...
 
Архитектура компилятора Swift
Архитектура компилятора SwiftАрхитектура компилятора Swift
Архитектура компилятора Swift
 
Преимущества и недостатки языка Swift
Преимущества и недостатки языка SwiftПреимущества и недостатки языка Swift
Преимущества и недостатки языка Swift
 
CS193P Lecture 5 View Animation
CS193P Lecture 5 View AnimationCS193P Lecture 5 View Animation
CS193P Lecture 5 View Animation
 
Denis Lebedev, Swift
Denis  Lebedev, SwiftDenis  Lebedev, Swift
Denis Lebedev, Swift
 
Мобильный веб: назад в будущее
Мобильный веб: назад в будущееМобильный веб: назад в будущее
Мобильный веб: назад в будущее
 
Технологии vs коммуникации: что важнее?
Технологии vs коммуникации: что важнее?Технологии vs коммуникации: что важнее?
Технологии vs коммуникации: что важнее?
 
Багфиксинг процесса разработки в iOS: взгляд с двух сторон
Багфиксинг процесса разработки в iOS: взгляд с двух сторонБагфиксинг процесса разработки в iOS: взгляд с двух сторон
Багфиксинг процесса разработки в iOS: взгляд с двух сторон
 
Как автотесты ускоряют релизы в OK.ru
Как автотесты ускоряют релизы в OK.ruКак автотесты ускоряют релизы в OK.ru
Как автотесты ускоряют релизы в OK.ru
 
Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...
Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...
Кортунов Никита. Как ускорить разработку приложений или есть ли жизнь после P...
 
Вадим Дробинин. Защищаем себя и пользователей: руководство по безопасности
Вадим Дробинин. Защищаем себя и пользователей: руководство по безопасностиВадим Дробинин. Защищаем себя и пользователей: руководство по безопасности
Вадим Дробинин. Защищаем себя и пользователей: руководство по безопасности
 
How to Lean
How to LeanHow to Lean
How to Lean
 
Юзабилити и функциональность ДБО2017
Юзабилити и функциональность ДБО2017Юзабилити и функциональность ДБО2017
Юзабилити и функциональность ДБО2017
 

Similar to Rambler.iOS #6: App delegate - разделяй и властвуй

What's new in Android P @ I/O Extended Bangkok 2018
What's new in Android P @ I/O Extended Bangkok 2018What's new in Android P @ I/O Extended Bangkok 2018
What's new in Android P @ I/O Extended Bangkok 2018
Somkiat Khitwongwattana
 
Quick Intro to Android Development
Quick Intro to Android DevelopmentQuick Intro to Android Development
Quick Intro to Android Development
Jussi Pohjolainen
 
Mini curso Android
Mini curso AndroidMini curso Android
Mini curso Android
Mario Jorge Pereira
 
Android app development basics
Android app development basicsAndroid app development basics
Android app development basics
Anton Narusberg
 
Android 3
Android 3Android 3
Android 3
Robert Cooper
 
Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W...
 	Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W... 	Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W...
Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W...
Robert Nyman
 
Apache Cordova In Action
Apache Cordova In ActionApache Cordova In Action
Apache Cordova In Action
Hazem Saleh
 
Single page webapps & javascript-testing
Single page webapps & javascript-testingSingle page webapps & javascript-testing
Single page webapps & javascript-testing
smontanari
 
Introduction to Palm's Mojo SDK
Introduction to Palm's Mojo SDKIntroduction to Palm's Mojo SDK
Introduction to Palm's Mojo SDK
Brendan Lim
 
Adopting 3D Touch in your apps
Adopting 3D Touch in your appsAdopting 3D Touch in your apps
Adopting 3D Touch in your apps
Juan C Catalan
 
Android & iOS Automation Using Appium
Android & iOS Automation Using AppiumAndroid & iOS Automation Using Appium
Android & iOS Automation Using Appium
Mindfire Solutions
 
Building an app with Google's new suites
Building an app with Google's new suitesBuilding an app with Google's new suites
Building an app with Google's new suites
Toru Wonyoung Choi
 
Director x Backbone = :)
Director x Backbone = :)Director x Backbone = :)
Director x Backbone = :)
Janessa Det
 
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
Hazem Saleh
 
Lightning Talk - Xamarin
Lightning Talk - Xamarin Lightning Talk - Xamarin
Lightning Talk - Xamarin
Deivison Sporteman
 
Android development with Scala and SBT
Android development with Scala and SBTAndroid development with Scala and SBT
Android development with Scala and SBT
Anton Yalyshev
 
Adaptive UI - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ -
Adaptive UI  - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ - Adaptive UI  - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ -
Adaptive UI - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ -
Yuji Hato
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
Andrew Dupont
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
Knoldus Inc.
 
Android Best Practices
Android Best PracticesAndroid Best Practices
Android Best Practices
Yekmer Simsek
 

Similar to Rambler.iOS #6: App delegate - разделяй и властвуй (20)

What's new in Android P @ I/O Extended Bangkok 2018
What's new in Android P @ I/O Extended Bangkok 2018What's new in Android P @ I/O Extended Bangkok 2018
What's new in Android P @ I/O Extended Bangkok 2018
 
Quick Intro to Android Development
Quick Intro to Android DevelopmentQuick Intro to Android Development
Quick Intro to Android Development
 
Mini curso Android
Mini curso AndroidMini curso Android
Mini curso Android
 
Android app development basics
Android app development basicsAndroid app development basics
Android app development basics
 
Android 3
Android 3Android 3
Android 3
 
Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W...
 	Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W... 	Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W...
Bringing the open web and APIs to mobile devices with Firefox OS - Whisky W...
 
Apache Cordova In Action
Apache Cordova In ActionApache Cordova In Action
Apache Cordova In Action
 
Single page webapps & javascript-testing
Single page webapps & javascript-testingSingle page webapps & javascript-testing
Single page webapps & javascript-testing
 
Introduction to Palm's Mojo SDK
Introduction to Palm's Mojo SDKIntroduction to Palm's Mojo SDK
Introduction to Palm's Mojo SDK
 
Adopting 3D Touch in your apps
Adopting 3D Touch in your appsAdopting 3D Touch in your apps
Adopting 3D Touch in your apps
 
Android & iOS Automation Using Appium
Android & iOS Automation Using AppiumAndroid & iOS Automation Using Appium
Android & iOS Automation Using Appium
 
Building an app with Google's new suites
Building an app with Google's new suitesBuilding an app with Google's new suites
Building an app with Google's new suites
 
Director x Backbone = :)
Director x Backbone = :)Director x Backbone = :)
Director x Backbone = :)
 
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
[JMaghreb 2014] Developing JavaScript Mobile Apps Using Apache Cordova
 
Lightning Talk - Xamarin
Lightning Talk - Xamarin Lightning Talk - Xamarin
Lightning Talk - Xamarin
 
Android development with Scala and SBT
Android development with Scala and SBTAndroid development with Scala and SBT
Android development with Scala and SBT
 
Adaptive UI - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ -
Adaptive UI  - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ - Adaptive UI  - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ -
Adaptive UI - 解像度の異なるデバイスや画面の向きに対応する 最適なレイアウトへ -
 
Writing Maintainable JavaScript
Writing Maintainable JavaScriptWriting Maintainable JavaScript
Writing Maintainable JavaScript
 
Backbone.js
Backbone.jsBackbone.js
Backbone.js
 
Android Best Practices
Android Best PracticesAndroid Best Practices
Android Best Practices
 

More from RAMBLER&Co

RDSDataSource: Основы LLVM
RDSDataSource: Основы LLVMRDSDataSource: Основы LLVM
RDSDataSource: Основы LLVM
RAMBLER&Co
 
Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!
RAMBLER&Co
 
Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?
Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?
Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?
RAMBLER&Co
 
Rambler.iOS #9: Life with out of memory
Rambler.iOS #9: Life with out of memoryRambler.iOS #9: Life with out of memory
Rambler.iOS #9: Life with out of memory
RAMBLER&Co
 
RDSDataSource: Чистые тесты на Swift
RDSDataSource: Чистые тесты на SwiftRDSDataSource: Чистые тесты на Swift
RDSDataSource: Чистые тесты на Swift
RAMBLER&Co
 
RDSDataSource: OCLint
RDSDataSource: OCLintRDSDataSource: OCLint
RDSDataSource: OCLint
RAMBLER&Co
 
RDSDataSource: Построение UML диаграмм
RDSDataSource: Построение UML диаграммRDSDataSource: Построение UML диаграмм
RDSDataSource: Построение UML диаграмм
RAMBLER&Co
 
RDSDataSource: App Thinning
RDSDataSource: App ThinningRDSDataSource: App Thinning
RDSDataSource: App Thinning
RAMBLER&Co
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
RAMBLER&Co
 
RDSDataSource: YapDatabase
RDSDataSource: YapDatabaseRDSDataSource: YapDatabase
RDSDataSource: YapDatabase
RAMBLER&Co
 
Rambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тесты
RAMBLER&Co
 
Rambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектураRambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектура
RAMBLER&Co
 
Rambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCoreRambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCore
RAMBLER&Co
 
Rambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеровRambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеров
RAMBLER&Co
 
RDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperiencedRDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperienced
RAMBLER&Co
 
RDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDKRDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDK
RAMBLER&Co
 
RDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOSRDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOS
RAMBLER&Co
 
RDSDataSource: Promises
RDSDataSource: PromisesRDSDataSource: Promises
RDSDataSource: Promises
RAMBLER&Co
 
RDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwiftRDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwift
RAMBLER&Co
 
Rambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейсаRambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейса
RAMBLER&Co
 

More from RAMBLER&Co (20)

RDSDataSource: Основы LLVM
RDSDataSource: Основы LLVMRDSDataSource: Основы LLVM
RDSDataSource: Основы LLVM
 
Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!Rambler.iOS #9: Анализируй это!
Rambler.iOS #9: Анализируй это!
 
Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?
Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?
Rambler.iOS #9: Нужны ли бэкенд-разработчики, когда есть Swift?
 
Rambler.iOS #9: Life with out of memory
Rambler.iOS #9: Life with out of memoryRambler.iOS #9: Life with out of memory
Rambler.iOS #9: Life with out of memory
 
RDSDataSource: Чистые тесты на Swift
RDSDataSource: Чистые тесты на SwiftRDSDataSource: Чистые тесты на Swift
RDSDataSource: Чистые тесты на Swift
 
RDSDataSource: OCLint
RDSDataSource: OCLintRDSDataSource: OCLint
RDSDataSource: OCLint
 
RDSDataSource: Построение UML диаграмм
RDSDataSource: Построение UML диаграммRDSDataSource: Построение UML диаграмм
RDSDataSource: Построение UML диаграмм
 
RDSDataSource: App Thinning
RDSDataSource: App ThinningRDSDataSource: App Thinning
RDSDataSource: App Thinning
 
RDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по DipRDSDataSource: Мастер-класс по Dip
RDSDataSource: Мастер-класс по Dip
 
RDSDataSource: YapDatabase
RDSDataSource: YapDatabaseRDSDataSource: YapDatabase
RDSDataSource: YapDatabase
 
Rambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тестыRambler.iOS #8: Чистые unit-тесты
Rambler.iOS #8: Чистые unit-тесты
 
Rambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектураRambler.iOS #8: Сервис-ориентированная архитектура
Rambler.iOS #8: Сервис-ориентированная архитектура
 
Rambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCoreRambler.iOS #8: Make your app extensible with JavaScriptCore
Rambler.iOS #8: Make your app extensible with JavaScriptCore
 
Rambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеровRambler.iOS #8: Как не стать жертвой бэкендеров
Rambler.iOS #8: Как не стать жертвой бэкендеров
 
RDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperiencedRDSDataSource: iOS Reverse Engineering for inexperienced
RDSDataSource: iOS Reverse Engineering for inexperienced
 
RDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDKRDSDataSource: Автогенерация документации для SDK
RDSDataSource: Автогенерация документации для SDK
 
RDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOSRDSDataSource: Плюрализация в iOS
RDSDataSource: Плюрализация в iOS
 
RDSDataSource: Promises
RDSDataSource: PromisesRDSDataSource: Promises
RDSDataSource: Promises
 
RDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwiftRDSDataSource: Flux, Redux, ReSwift
RDSDataSource: Flux, Redux, ReSwift
 
Rambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейсаRambler.iOS #7: Построение сложного табличного интерфейса
Rambler.iOS #7: Построение сложного табличного интерфейса
 

Rambler.iOS #6: App delegate - разделяй и властвуй

  • 1. AppDelegate разделяй и властвуй Вадим Смаль iOS разработчик Rambler&Co
  • 3. • Реагирует на получение уведомлений • Реагирует на ключевые изменения в состоянии вашего приложения • Реагирует на события, которые нацелены на само приложение • Управляет процессом сохранения и восстановления состояния приложения
  • 4. Запуск приложения Изменение состояния приложения Восстановление состояния приложения Загрузка данных в фоне Локальные и удаленные уведомления Пользовательская активность WatchKit Открытие URL’ов HealthKit Системные события Разрешения для расширений Геометрия интерфейса Window CoreData SharedInstance Quick Actions <UIApplicationDelegate>
  • 5. import Shared import Storage import AVFoundation import XCGLogger import Breakpad import MessageUI import WebImage import SwiftKeychainWrapper import LocalAuthentication private let log = Logger.browserLogger let LatestAppVersionProfileKey = "latestAppVersion" let AllowThirdPartyKeyboardsKey = "settings.allowThirdPartyKeyboards" class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var browserViewController: BrowserViewController! var rootViewController: UINavigationController! weak var profile: BrowserProfile? var tabManager: TabManager! var adjustIntegration: AdjustIntegration? weak var application: UIApplication? var launchOptions: [NSObject: AnyObject]? let appVersion = NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleShortVersionString") as! String var openInFirefoxURL: NSURL? = nil func application(application: UIApplication, willFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Hold references to willFinishLaunching parameters for delayed app launch self.application = application self.launchOptions = launchOptions log.debug("Configuring window…") self.window = UIWindow(frame: UIScreen.mainScreen().bounds) self.window!.backgroundColor = UIConstants.AppBackgroundColor // Short circuit the app if we want to email logs from the debug menu if DebugSettingsBundleOptions.launchIntoEmailComposer { self.window?.rootViewController = UIViewController() presentEmailComposerWithLogs() return true } else { return startApplication(application, withLaunchOptions: launchOptions) } } Импорты Зависимости WINDOW startApplication
  • 6. private func startApplication(application: UIApplication, withLaunchOptions launchOptions: [NSObject: AnyObject]?) -> Bool { log.debug("Setting UA…") // Set the Firefox UA for browsing. setUserAgent() log.debug("Starting keyboard helper…") // Start the keyboard helper to monitor and cache keyboard state. KeyboardHelper.defaultHelper.startObserving() log.debug("Starting dynamic font helper…") // Start the keyboard helper to monitor and cache keyboard state. DynamicFontHelper.defaultHelper.startObserving() log.debug("Setting custom menu items…") MenuHelper.defaultHelper.setItems() log.debug("Creating Sync log file…") let logDate = NSDate() // Create a new sync log file on cold app launch. Note that this doesn't roll old logs. Logger.syncLogger.newLogWithDate(logDate) log.debug("Creating corrupt DB logger…") Logger.corruptLogger.newLogWithDate(logDate) log.debug("Creating Browser log file…") Logger.browserLogger.newLogWithDate(logDate) log.debug("Getting profile…") let profile = getProfile(application) if !DebugSettingsBundleOptions.disableLocalWebServer { log.debug("Starting web server…") // Set up a web server that serves us static content. Do this early so that it is ready when the UI is presented. setUpWebServer(profile) } log.debug("Setting AVAudioSession category…") do { // for aural progress bar: play even with silent switch on, and do not stop audio from other apps (like music) try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: AVAudioSessionCategoryOptions.MixWithOthers) } catch _ { log.error("Failed to assign AVAudioSession category to allow playing with silent switch on for aural progress bar") } Настройка приложения
  • 7. let defaultRequest = NSURLRequest(URL: UIConstants.DefaultHomePage) let imageStore = DiskImageStore(files: profile.files, namespace: "TabManagerScreenshots", quality: UIConstants.ScreenshotQuality) log.debug("Configuring tabManager…") self.tabManager = TabManager(defaultNewTabRequest: defaultRequest, prefs: profile.prefs, imageStore: imageStore) self.tabManager.stateDelegate = self // Add restoration class, the factory that will return the ViewController we // will restore with. log.debug("Initing BVC…") browserViewController = BrowserViewController(profile: self.profile!, tabManager: self.tabManager) browserViewController.restorationIdentifier = NSStringFromClass(BrowserViewController.self) browserViewController.restorationClass = AppDelegate.self browserViewController.automaticallyAdjustsScrollViewInsets = false rootViewController = UINavigationController(rootViewController: browserViewController) rootViewController.automaticallyAdjustsScrollViewInsets = false rootViewController.delegate = self rootViewController.navigationBarHidden = true self.window!.rootViewController = rootViewController log.debug("Configuring Breakpad…") activeCrashReporter = BreakpadCrashReporter(breakpadInstance: BreakpadController.sharedInstance()) configureActiveCrashReporter(profile.prefs.boolForKey("crashreports.send.always")) log.debug("Adding observers…") NSNotificationCenter.defaultCenter().addObserverForName(FSReadingListAddReadingListItemNotification, object: nil, queue: nil) { (notification) -> Void in if let userInfo = notification.userInfo, url = userInfo["URL"] as? NSURL { let title = (userInfo["Title"] as? String) ?? "" profile.readingList?.createRecordWithURL(url.absoluteString, title: title, addedBy: UIDevice.currentDevice().name) } } // check to see if we started 'cos someone tapped on a notification. if let localNotification = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification { viewURLInNewTab(localNotification) } adjustIntegration = AdjustIntegration(profile: profile) // We need to check if the app is a clean install to use for // preventing the What's New URL from appearing. if getProfile(application).prefs.intForKey(IntroViewControllerSeenProfileKey) == nil { getProfile(application).prefs.setString(AppInfo.appVersion, forKey: LatestAppVersionProfileKey) } log.debug("Updating authentication keychain state to reflect system state") self.updateAuthenticationInfo() log.debug("Done with setting up the application.") return true }
  • 8. func applicationWillTerminate(application: UIApplication) { log.debug("Application will terminate.") // We have only five seconds here, so let's hope this doesn't take too long. self.profile?.shutdown() // Allow deinitializers to close our database connections. self.profile = nil self.tabManager = nil self.browserViewController = nil self.rootViewController = nil } func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { // Override point for customization after application launch. var shouldPerformAdditionalDelegateHandling = true log.debug("Did finish launching.") log.debug("Setting up Adjust") self.adjustIntegration?.triggerApplicationDidFinishLaunchingWithOptions(launchOptions) log.debug("Making window key and visible…") self.window!.makeKeyAndVisible() // Now roll logs. log.debug("Triggering log roll.") dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { Logger.syncLogger.deleteOldLogsDownToSizeLimit() Logger.browserLogger.deleteOldLogsDownToSizeLimit() } if #available(iOS 9, *) { // If a shortcut was launched, display its information and take the appropriate action if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsShortcutItemKey] as? UIApplicationShortcutItem { QuickActions.sharedInstance.launchedShortcutItem = shortcutItem // This will block "performActionForShortcutItem:completionHandler" from being called. shouldPerformAdditionalDelegateHandling = false } } log.debug("Done with applicationDidFinishLaunching.") return shouldPerformAdditionalDelegateHandling } Quick Actions
  • 9. func application(application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: AnyObject) -> Bool { if let components = NSURLComponents(URL: url, resolvingAgainstBaseURL: false) { if components.scheme != "firefox" && components.scheme != "firefox-x-callback" { return false } var url: String? for item in (components.queryItems ?? []) as [NSURLQueryItem] { switch item.name { case "url": url = item.value default: () } } if let url = url, newURL = NSURL(string: url.unescape()) { // If we are active then we can ask the BVC to open the new tab right away. Else we remember the // URL and we open it in applicationDidBecomeActive. if application.applicationState == .Active { if #available(iOS 9, *) { self.browserViewController.switchToPrivacyMode(isPrivate: false) } self.browserViewController.openURLInNewTab(newURL) } else { openInFirefoxURL = newURL } return true } } return false } func application(application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: String) -> Bool { if let thirdPartyKeyboardSettingBool = getProfile(application).prefs.boolForKey(AllowThirdPartyKeyboardsKey) where extensionPointIdentifier == UIApplicationKeyboardExtensionPointIdentifier { return thirdPartyKeyboardSettingBool } return true } Открытие URL’ов Разрешения для расширений
  • 10. func applicationDidBecomeActive(application: UIApplication) { guard !DebugSettingsBundleOptions.launchIntoEmailComposer else { return } self.profile?.syncManager.applicationDidBecomeActive() // We could load these here, but then we have to futz with the tab counter // and making NSURLRequests. self.browserViewController.loadQueuedTabs() // handle quick actions is available if #available(iOS 9, *) { let quickActions = QuickActions.sharedInstance if let shortcut = quickActions.launchedShortcutItem { // dispatch asynchronously so that BVC is all set up for handling new tabs // when we try and open them quickActions.handleShortCutItem(shortcut, withBrowserViewController: browserViewController) quickActions.launchedShortcutItem = nil } // we've removed the Last Tab option, so we should remove any quick actions that we already have that are last tabs // we do this after we've handled any quick actions that have been used to open the app so that we don't b0rk if // the user has opened the app for the first time after upgrade with a Last Tab quick action QuickActions.sharedInstance.removeDynamicApplicationShortcutItemOfType(ShortcutType.OpenLastTab, fromApplication: application) } // If we have a URL waiting to open, switch to non-private mode and open the URL. if let url = openInFirefoxURL { openInFirefoxURL = nil // This needs to be scheduled so that the BVC is ready. dispatch_async(dispatch_get_main_queue()) { if #available(iOS 9, *) { self.browserViewController.switchToPrivacyMode(isPrivate: false) } self.browserViewController.switchToTabForURLOrOpen(url) } } } func applicationWillEnterForeground(application: UIApplication) { // The reason we need to call this method here instead of `applicationDidBecomeActive` // is that this method is only invoked whenever the application is entering the foreground where as // `applicationDidBecomeActive` will get called whenever the Touch ID authentication overlay disappears. self.updateAuthenticationInfo() } private func updateAuthenticationInfo() { if let authInfo = KeychainWrapper.authenticationInfo() { if !LAContext().canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: nil) { authInfo.useTouchID = false KeychainWrapper.setAuthenticationInfo(authInfo) } } } Quick Actions
  • 11. func applicationDidEnterBackground(application: UIApplication) { self.profile?.syncManager.applicationDidEnterBackground() var taskId: UIBackgroundTaskIdentifier = 0 taskId = application.beginBackgroundTaskWithExpirationHandler { _ in log.warning("Running out of background time, but we have a profile shutdown pending.") application.endBackgroundTask(taskId) } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) { self.profile?.shutdown() application.endBackgroundTask(taskId) } // Workaround for crashing in the background when <select> popovers are visible (rdar://24571325). let jsBlurSelect = "if (document.activeElement && document.activeElement.tagName === 'SELECT') { document.activeElement.blur(); }" tabManager.selectedTab?.webView?.evaluateJavaScript(jsBlurSelect, completionHandler: nil) } func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) { if let actionId = identifier { if let action = SentTabAction(rawValue: actionId) { viewURLInNewTab(notification) switch(action) { case .Bookmark: addBookmark(notification) break case .ReadingList: addToReadingList(notification) break default: break } } else { print("ERROR: Unknown notification action received") } } else { print("ERROR: Unknown notification received") } } func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) { viewURLInNewTab(notification) } Загрузка данных в фоне Локальные и удаленные уведомления
  • 12. func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool { if let url = userActivity.webpageURL { browserViewController.switchToTabForURLOrOpen(url) return true } return false } @available(iOS 9.0, *) func application(application: UIApplication, performActionForShortcutItem shortcutItem: UIApplicationShortcutItem, completionHandler: Bool -> Void) { let handledShortCutItem = QuickActions.sharedInstance.handleShortCutItem(shortcutItem, withBrowserViewController: browserViewController) completionHandler(handledShortCutItem) } var activeCrashReporter: CrashReporter? func configureActiveCrashReporter(optedIn: Bool?) { if let reporter = activeCrashReporter { configureCrashReporter(reporter, optedIn: optedIn) } } public func configureCrashReporter(reporter: CrashReporter, optedIn: Bool?) { let configureReporter: () -> () = { let addUploadParameterForKey: String -> Void = { key in if let value = NSBundle.mainBundle().objectForInfoDictionaryKey(key) as? String { reporter.addUploadParameter(value, forKey: key) } } addUploadParameterForKey("AppID") addUploadParameterForKey("BuildID") addUploadParameterForKey("ReleaseChannel") addUploadParameterForKey("Vendor") } if let optedIn = optedIn { // User has explicitly opted-in for sending crash reports. If this is not true, then the user has // explicitly opted-out of crash reporting so don't bother starting breakpad or stop if it was running if optedIn { reporter.start(true) configureReporter() reporter.setUploadingEnabled(true) } else { reporter.stop() } } // We haven't asked the user for their crash reporting preference yet. Log crashes anyways but don't send them. else { reporter.start(true) configureReporter() } } Пользовательска я активность Пользовательска я активность ThirdParties
  • 13. // MARK: - Root View Controller Animations extension AppDelegate: UINavigationControllerDelegate { func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == UINavigationControllerOperation.Push { return BrowserToTrayAnimator() } else if operation == UINavigationControllerOperation.Pop { return TrayToBrowserAnimator() } else { return nil } } } extension AppDelegate: TabManagerStateDelegate { func tabManagerWillStoreTabs(tabs: [Browser]) { // It is possible that not all tabs have loaded yet, so we filter out tabs with a nil URL. let storedTabs: [RemoteTab] = tabs.flatMap( Browser.toTab ) // Don't insert into the DB immediately. We tend to contend with more important // work like querying for top sites. let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(ProfileRemoteTabsSyncDelay * Double(NSEC_PER_MSEC))), queue) { self.profile?.storeTabs(storedTabs) } } } extension AppDelegate: MFMailComposeViewControllerDelegate { func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) { // Dismiss the view controller and start the app up controller.dismissViewControllerAnimated(true, completion: nil) startApplication(application!, withLaunchOptions: self.launchOptions) } } Стек навигации
  • 14. AppDelegate: 45 методов ~1000 строк кода ~ 30 зависимостей
  • 18. didFinishLaunchingWithOptions { [self.thirdPartiesConfigurator configure] [self.startConfigurator configure] … [self.applicationConfigurator configure] [self.handoffHandler activate] [self.spotlightIndexer activate] … [self.quickActionHandler activate] … }
  • 19. @interface AppDelegate : UIResponder <UIApplicationDelegate> @property UIWindow *window; @property startConfigurator; @property thirdPartiesConfigurator; @property applicationConfigurator; ... @property handoffHandler; @property quickActionHandler ; @property NSArray *urlHandlers; @property forceLogoutHandler; ... @end
  • 20. AppDelegate: 45 методов ~200 строк кода ~20 зависимостей
  • 24. @implementation RemoteNotificationAppDelegate - didFinishLaunchingWithOptions { if (notification) { [self.pushNotificationCenter process:notification]; } return YES; } - didRegisterForRemoteNotificationsWithDeviceToken: { [self.pushNotificationCenter didRegisterDeviceToken:token]; } - didReceiveRemoteNotification { [self.pushNotificationCenter processWithUserInfo:userInfo]; } @end
  • 25. AppDelegate: ~ 3 метода ~50 строк кода ~2 зависимостей
  • 29. int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([RCMAppDelegateProxy class])); } }
  • 30. @interface RCMAppDelegateProxy : NSProxy - (void)addAppDelegate:(id<UIApplicationDelegate>)delegate; - (void)addAppDelegates:(NSArray *)delegates; @end