Вадим Смаль объяснил, что плохого в содержании одного огромного класса и как решить эту проблему с помощью разработанного им компонента.
Rambler.iOS - митапы iOS-разработчиков, организуемые компанией RAMBLER&Co.
3. • Реагирует на получение уведомлений
• Реагирует на ключевые изменения в
состоянии вашего приложения
• Реагирует на события, которые
нацелены на само приложение
• Управляет процессом сохранения и
восстановления состояния приложения
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)
}
}
Стек навигации