0

Background Execution: Finite-Length Tasks

Mỗi lần apple cung cấp phiên bản hệ điều hành mới của iOS, các kỹ sư của họ mở rộng hơn kho public API vốn bị hạn chế. Trong đó không thể bỏ qua Background Excution - được công bố từ phiên bản iOS 4. Với sự mở rộng này đã giúp cho developer có sự thay đổi lớn về mặt thiết kế kiến trúc phần mềm cho nên tảng này. Hiện nay chúng ta có thể thiết kế những ứng dụng vẫn có thể hoạt động cho dù user đã thoát app - ấn vào nút Home. Thậm chí khi đó mọi hoạt động của ứng dụng được tạm dùng và sẽ khởi động lại khi user mở lại ứng dụng.

Tất nhiên Apple cũng đưa ra những hạn chế nhất định, nhằm giới hạn thời gian cho phép 1 tiến trình và số lượng tiến trình được thực thi ở chế độ background. Những hạn chế này nhằm giảm thiểu việc tiêu hao pin và cải thiện trải nghiệm người dùng. Ứng dụng của bạn chỉ được phép ctieesp tục chạy ở chế độ background trong trường hợp rất cụ thể như: play sound, update location, hoặc thực hiện download/upload.

Các phiên bản hệ điều hành trước iOS 7, ứng dụng đã được đưa ra lên đến 10 phút liên tục để hoàn thành bất cứ điều gì họ đã làm trước khi thực sự bị huỷ bởi hệ điều hành. Nhưng kể từ iOS 7 trở đi, thời hạn đưa ra cho ứng dụng hoat động ở chế độ background chỉ khoảng từ 180 giây tới 5 giây. Điều này hoàn toàn do hệ điều hành quyết định. Và ngay cả việc sử dụng NSURLSession để thực hiện download cũng gặp phải hạn chế như trên.

Trong bài viết này chúng ta sẽ tìm hiểu Finite-Length Task - một trong những hoạt động đươc phép chạy ở chế độ background.

FInite-Length Task

Về mặt kỹ thuật, đây không phải là một chế độ nền ở tất cả, là bạn không cần phải khai báo rằng ứng dụng của bạn sử dụng chế độ này trong Info.plist (hoặc sử dụng chế độ nền hộp kiểm). Thay vào đó, nó là một API cho phép bạn chạy mã nhị phân cho một số lượng hữu hạn của thời gian khi ứng dụng của bạn trong nền. Sao cũng được!

Trong quá khứ, chế độ này thường được sử dụng để hoàn thành một tải lên hay tải về và hào phóng (nhưng không được bảo đảm) 10 phút đã được cung cấp để thực hiện các nhiệm vụ này. Nhưng nếu kết nối là chậm và quá trình này vẫn chưa hoàn thành? Nó sẽ để lại ứng dụng của bạn trong trạng thái kỳ lạ và bạn sẽ có thêm nhiều lỗi xử lý mã để làm cho mọi việc sanely. Bởi vì điều này, Apple đã giới thiệu NSURLSession.

Mặc dù không phải là một chủ đề của bài hướng dẫn này, NSURLSessionlà mạnh mẽ khi đối mặt với backgrounding và thậm chí thiết bị khởi động lại và thực hiện việc này một cách hiệu quả năng lượng. Nếu bạn đang đối phó với tải lớn, hãy kiểm tra của chúng tôi hướng dẫn NSURLSession .

Một trường hợp sử dụng vẫn còn rất giá trị của các Dù chế độ nền là để hoàn thành một số nhiệm vụ lâu dài chạy, chẳng hạn như hiển và viết một đoạn video vào thư viện ảnh.

Whatever_cheers.png

Nhưng đây chỉ là một ví dụ. Theo mã bạn có thể chạy là tùy ý, bạn có thể sử dụng API này để làm khá nhiều bất cứ điều gì: thực hiện các phép tính dài (như trong hướng dẫn này), áp dụng các bộ lọc hình ảnh, vẽ một lưới 3D phức tạp ... bất cứ điều gì! Trí tưởng tượng của bạn là giới hạn, miễn là bạn hãy ghi nhớ rằng bạn chỉ nhận được một số thời gian, không giới hạn thời gian. Bao nhiêu thời gian bạn nhận được sau khi ứng dụng của bạn được background được xác định bởi iOS. Có gì đảm bảo về thời gian mặc định, nhưng bạn luôn có thể kiểm tra backgroundTimeRemainingtrên UIApplication. Điều này sẽ cho bạn biết bao nhiêu thời gian bạn đã để lại.

Các chung, sự đồng thuận quan sát dựa trên là bạn có được 3 phút. Một lần nữa, có gì đảm bảo và các tài liệu API thậm chí không cung cấp cho một số sân chơi bóng chày - do đó, không dựa vào số này. Bạn có thể nhận được 5 phút hoặc 5 giây, vì vậy ứng dụng của bạn cần phải được chuẩn bị sẵn sàng cho bất cứ điều gì!

Dưới đây là một nhiệm vụ phổ biến mà mọi sinh viên CS nên quen thuộc với: tính toán các số trong dãy số Fibonacci . The twist ở đây là bạn sẽ tính toán các con số trong nền !

Mở WhateverViewController.swift và thêm các tính chất sau đây để WhateverViewController:

var previous =  NSDecimalNumber .one ( )
var current =  NSDecimalNumber .one ( )
var position :  UInt  =  1
var updateTimer :  NSTimer ?
var backgroundTask : UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid

Các NSDecimalNumberssẽ giữ hai giá trị trước đó của các số trong dãy. NSDecimalNumberCó thể giữ số lượng rất lớn, vì vậy nó rất thích hợp cho mục đích của bạn. positionChỉ là một bộ đếm cho bạn biết vị trí số hiện trong series.

Bạn sẽ sử dụng updateTimerđể chứng minh rằng ngay cả giờ tiếp tục làm việc khi sử dụng API này, và cũng để làm chậm quá trình tính toán một chút, do đó bạn có thể quan sát chúng.

Thêm một số phương pháp hữu ích để WhateverViewControllermà thiết lập lại các tính toán Fibonacci và khởi đầu đó và dừng lại một công việc nền-thể:

func resetCalculation() {
  previous = NSDecimalNumber.one()
  current = NSDecimalNumber.one()
  position = 1
}

func registerBackgroundTask() {
  backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
    [unowned self] in
    self.endBackgroundTask()
  }
  assert(backgroundTask != UIBackgroundTaskInvalid)
}

func endBackgroundTask() {
  NSLog("Background task ended.")
  UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
  backgroundTask = UIBackgroundTaskInvalid
}

Đây là phần quan trọng, điền vào thực hiện sản phẩm nào cho didTapPlayPause(_😃

@IBAction func didTapPlayPause(sender: UIButton) {
  sender.selected = !sender.selected
  if sender.selected {
    resetCalculation()
    updateTimer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self,
      selector: "calculateNextNumber", userInfo: nil, repeats: true)
    registerBackgroundTask()
  } else {
    updateTimer?.invalidate()
    updateTimer = nil
    if backgroundTask != UIBackgroundTaskInvalid {
      endBackgroundTask()
    }
  }
}

Các nút sẽ chuyển nhà nước lựa chọn của nó phụ thuộc vào việc tính toán được dừng lại và nên bắt đầu, hoặc đã được bắt đầu và nên dừng lại.

Trước tiên, bạn phải thiết lập các biến dãy Fibonacci. Sau đó, bạn có thể tạo một NSTimermà sẽ cháy hai lần mỗi cuộc gọi thứ hai và calculateNextNumber()(vẫn còn tới). Bây giờ đến các bit cần thiết: các cuộc gọi đến registerBackgroundTask()trong cuộc gọi lần lượt beginBackgroundTaskWithExpirationHandler(_😃. Đây là phương pháp mà nói với iOS mà bạn cần thêm thời gian để hoàn thành bất cứ điều gì bạn đang làm trong trường hợp các ứng dụng được background. Sau khi cuộc gọi này, nếu ứng dụng của bạn được background nó vẫn sẽ có được thời gian CPU cho đến khi bạn gọi endBackgroundTask().

Vâng, gần như. Nếu bạn không gọi endBackgroundTask()sau một khoảng thời gian trong nền, iOS sẽ gọi đóng cửa được xác định khi bạn gọi beginBackgroundTaskWithExpirationHandler(_:)đến cho bạn cơ hội để ngăn chặn mã thực thi. Vì vậy, nó là một ý tưởng rất tốt để sau đó gọi endBackgroundTask()để nói với các hệ điều hành mà bạn đang thực hiện. Nếu bạn không làm điều này và tiếp tục thực thi mã sau khi khối này được gọi là, ứng dụng của bạn sẽ được chấm dứt!

Phần thứ hai của ifrất đơn giản: nó chỉ làm mất hiệu lực bộ đếm thời gian và các cuộc gọi endBackgroundTask()để chỉ cho iOS mà bạn không cần thêm bất kỳ thời gian CPU. Điều quan trọng là bạn gọi endBackgroundTask()cho mỗi lần bạn gọi điện beginBackgroundTaskWithExpirationHandler(😃. Nếu bạn gọi beginBackgroundTaskWithExpirationHandler(:)hai lần và chỉ gọi endBackgroundTask()cho một trong những nhiệm vụ, bạn vẫn sẽ có được thời gian CPU cho đến khi bạn gọi endBackgroundTask()một lần thứ hai với giá trị của các tác vụ chạy nền thứ hai. Đây cũng là lý do tại sao bạn cần backgroundTask.

Bây giờ bạn có thể thực hiện các phương pháp ít dự án CS. Thêm các phương pháp sau đây để WhateverViewController:

func calculateNextNumber() {
  let result = current.decimalNumberByAdding(previous)

  let bigNumber = NSDecimalNumber(mantissa: 1, exponent: 40, isNegative: false)
  if result.compare(bigNumber) == .OrderedAscending {
    previous = current
    current = result
    ++position
  }
  else {
    // This is just too much.... Start over.
    resetCalculation()
  }

  let resultsMessage = "Position \(position) = \(current)"

  switch UIApplication.sharedApplication().applicationState {
    case .Active:
      resultsLabel.text = resultsMessage
    case .Background:
      NSLog("App is backgrounded. Next number = %@", resultsMessage)
      NSLog("Background time remaining = %.1f seconds", UIApplication.sharedApplication().backgroundTimeRemaining)
    case .Inactive:
      break
  }
}

Một lần nữa, đây là một mánh khóe nhà nước ứng dụng để hiển thị kết quả ngay cả khi ứng dụng của bạn trong nền. Trong trường hợp này, có một mảnh thú vị hơn của thông tin: giá trị của backgroundTimeRemaining. Việc tính toán sẽ chỉ dừng lại khi iOS gọi khối thêm vào thông qua các cuộc gọi đến beginBackgroundTaskWithExpirationHandler(_😃.

Xây dựng và chạy, sau đó chuyển sang tab thứ ba.

fib-352x500.png

Chọn Play và bạn sẽ thấy các ứng dụng tính toán các giá trị. Bây giờ nhấn vào nút nhà và xem các đầu ra trong giao diện điều khiển của Xcode. Bạn sẽ thấy các ứng dụng vẫn cập nhật các số trong khi thời gian còn lại đi xuống.

Trong hầu hết các trường hợp, thời gian này sẽ bắt đầu với 180 (180 giây = 3 phút) và đi xuống đến 5 giây. Nếu bạn chờ đợi cho thời gian hết hạn khi bạn đạt đến 5 giây (có thể là một giá trị tùy thuộc vào điều kiện cụ thể của bạn), khối hết hạn sẽ được gọi và ứng dụng của bạn nên ngừng xuất ra bất cứ điều gì. Sau đó, nếu bạn quay trở lại các ứng dụng, tính giờ nên bắt đầu bắn một lần nữa và toàn bộ điên rồ sẽ tiếp tục. Chỉ có một lỗi trong mã này, và nó mang lại cho tôi cơ hội để giải thích về thông báo nền. Giả sử bạn nền ứng dụng và chờ đợi cho đến khi hết hạn thời gian quy định. Trong trường hợp này, ứng dụng của bạn sẽ gọi hàm xử lý hết hạn và gọi endBackgroundTask(), do đó kết thúc sự cần thiết cho thời gian nền.

Nếu bạn sau đó quay lại ứng dụng, bộ đếm thời gian sẽ tiếp tục cháy. Nhưng nếu bạn để ứng dụng một lần nữa, bạn sẽ nhận được không có thời gian ở tất cả các nền tảng. Tại sao? Bởi vì nơi nào giữa hết hạn và trở về nền đã làm các cuộc gọi ứng dụng beginBackgroundTaskWithExpirationHandler(_😃.

Làm thế nào bạn có thể giải quyết này? Có một số cách để đi về nó và một trong số đó là sử dụng một thông báo thay đổi trạng thái.

Có hai cách để bạn có thể nhận được một thông báo rằng ứng dụng của bạn đã thay đổi trạng thái. Đầu tiên là thông qua phương pháp chính ứng dụng đại biểu của bạn. Thứ hai là bằng cách lắng nghe một số thông báo rằng iOS sẽ gửi đến ứng dụng của bạn:

  • UIApplicationWillResignActiveNotificationvà applicationWillResignActive(_:)Chúng được gửi đi và gọi khi ứng dụng của bạn là về để đi vào trạng thái không hoạt động. Tại thời điểm này, ứng dụng của bạn chưa được trong nền - nó vẫn còn các ứng dụng nền trước nhưng nó sẽ không nhận được bất kỳ sự kiện UI.

  • UIApplicationDidEnterBackgroundNotificationvà applicationDidEnterBackground(:)Chúng được gửi đi và gọi khi ứng dụng vào trạng thái nền. Tại thời điểm này, ứng dụng của bạn không hoạt động nữa và bạn chỉ nhận được cơ hội cuối cùng này để chạy một số mã. Đây là thời điểm hoàn hảo để gọi beginBackgroundTaskWithExpirationHandler(:)nếu bạn muốn có thêm thời gian CPU.

  • UIApplicationWillEnterForegroundNotificationvà applicationWillEnterForeground(:)Chúng được gửi đi và gọi khi ứng dụng đang trở lại trạng thái hoạt động. Các ứng dụng vẫn còn trong nền nhưng bạn đã có thể khởi động lại bất cứ điều gì bạn muốn làm. Đây là thời điểm tốt để gọi endBackgroundTask()nếu bạn chỉ gọi beginBackgroundTaskWithExpirationHandler(:)khi thực sự bước vào nền.

  • UIApplicationDidBecomeActiveNotificationvà applicationDidBecomeActive(_:)Chúng được gửi đi và gọi ngay sau khi thông báo trước đó, trong trường hợp ứng dụng của bạn đang trở lại từ phía sau. Đây cũng được gửi đi và gọi là nếu ứng dụng của bạn đã được chỉ tạm ​​thời bị gián đoạn - bởi một cuộc gọi, ví dụ - và không bao giờ thực sự bước vào nền, nhưng bạn nhận được UIApplicationWillResignActiveNotification.

Bạn có thể thấy tất cả điều này một cách chi tiết đồ họa (theo nghĩa đen - có một số biểu đồ dòng chảy tốt đẹp) trong tài liệu của Apple cho App Kỳ Apps . Thời gian để sửa chữa lỗi. Đầu tiên, ghi đè lên viewDidLoad()và đăng ký UIApplicationDidBecomeActiveNotification.

override func viewDidLoad() {
  super.viewDidLoad()
  NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("reinstateBackgroundTask"), name: UIApplicationDidBecomeActiveNotification, object: nil)
}

Điều này chỉ định bộ chọn reinstateBackgroundTaskđể được gọi là bất cứ khi nào các ứng dụng sẽ được kích hoạt. Bất cứ khi nào bạn đăng ký một thông báo bạn cũng nên suy nghĩ về nơi để bỏ đăng ký. Sử dụng deinitđể làm điều này. Thêm dòng sau vào WhateverViewController:

deinit {
 NSNotificationCenter.defaultCenter().removeObserver(self)
}

Cuối cùng, thực hiện reinstateBackgroundTask().

func reinstateBackgroundTask() {
  if updateTimer != nil && (backgroundTask == UIBackgroundTaskInvalid) {
    registerBackgroundTask()
  }
}

Bạn chỉ cần để phục hồi nếu có một bộ đếm thời gian đi nhưng các tác vụ chạy nền không hợp lệ. Phá vỡ mã của bạn vào chức năng tiện ích nhỏ mà làm một việc là trả hết. Bạn chỉ cần gọi registerBackgroundTask()khi một tác vụ chạy nền không chạy cho bộ đếm thời gian hiện tại.

Dưới đây là link download project Download


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí