Поведение и общий вариант использования с примерами
Оператор defer
используется для выполнения кода непосредственно перед передачей управления программой за пределы текущего блока кода, в котором появляется оператор defer
. Это означает, что оператор defer
используется для выполнения фрагмента кода непосредственно перед тем, как выполнение покидает текущий блок кода. . Текущий блок кода может быть оператором method
, оператором if
, блоком do
, блоком loop
или switch
.
Операторы в defer
выполняются независимо от того, как передается управление программой, что делает его идеальным для запуска кода очистки, такого как закрытие файлов, закрытие соединений с базой данных, выделение памяти вручную или освобождение других системных ресурсов.
Блок операторов defer
:
defer {
// Statements
}
Случаи использования оператора defer
В проекте есть много ситуаций, когда мы можем использовать операторы defer
. Ниже приведены несколько реальных случаев использования в быстрой разработке iOS для идеального использования оператора defer
, выполняющего код очистки, такой как закрытие файлов, закрытие соединений с базой данных, ручное выделение памяти или освобождение других системных ресурсов.
1. Разблокировать блокировку (NSLock)
Наиболее распространенный вариант использования оператора defer в Swift — разблокировка блокировки. defer
может обеспечить обновление этого состояния, даже если код имеет несколько путей. Это устраняет любые опасения по поводу того, что вы забудете разблокировать, что может привести к утечке памяти или взаимоблокировке.
var balance = 1000.0 // shared resource let lock = NSLock() struct Bank { func withdraw(amount: Double) { lock.lock() // defer will ensure lock is unlock before control is transferred defer { lock.unlock() } if balance > amount { balance -= amount print("balance is \(balance)") } else { print("insufficent balance") } } }
2. Очистить выделение памяти вручную
Если вы получаете доступ к C API и создаете объекты CoreFoundation, выделяете память или читаете и записываете файлы с помощью fopen, FileHandle. Вы можете убедиться, что используете оператор defer для правильной очистки во всех случаях с Dealloc, Free, Close, Deallocate, CloseFile.
a) UnsafeMutablePointer:-UnsafeMutablePointer
не обеспечивает автоматического управления памятью или гарантий выравнивания. Вы несете ответственность за управление жизненным циклом любой памяти, с которой вы работаете, с помощью небезопасных указателей, чтобы избежать утечек или неопределенного поведения. Используйте блок defer, чтобы освободить его.
do { // allocate memory let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count) pointer.initialize(repeating: 0, count: count) // deallocate memory using defer defer { pointer.deinitialize(count: count) pointer.deallocate() } pointer.pointee = 4 pointer.advanced(by: 1).pointee = 2 let bufferPointer = UnsafeBufferPointer(start: pointer, count: count) for bufPointer in bufferPointer.enumerated() { print("value \(bufPointer)") } }
b) UIGraphicsEndImageContext:-Если мы используем для создания графического контекста на основе растрового изображения, используя UIGraphicsBeginImageContextWithOptions с указанными параметрами. Вызывая UIGraphicsEndImageContext внутри оператора defer
, мы обеспечиваем завершение графического контекста перед выходом.
private func drawRoundedImageWithCOrner() -> UIImage? { let bounds = CGRect(x: 0, y: 0, width:200, height: 200) UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0) defer { UIGraphicsEndImageContext() } UIColor.black.setFill() let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: 10, height: 10)) path.addClip() UIRectFill(bounds) let image = UIGraphicsGetImageFromCurrentImageContext() return image }
3. Обновить макет просмотра
Если мы обновляем ограничения программно, мы можем поместить layoutIfNeeded()
в оператор defer
. Это позволит нам обновлять ограничения, не беспокоясь о том, что вы забудете вызвать layoutIfNeeded().
Аналогичным образом метод setNeedsLayout()
можно использовать внутри defer
для обновления представления, что гарантирует, что метод всегда будет выполняться перед выходом из области видимости.
private func updateViewContstraints(_ blueView: UIView) { defer { self.view.layoutIfNeeded() } NSLayoutConstraint(item: blueView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 80.0).isActive = true }
4. Фиксация изменений
a) CATransaction:-оператор defer
может использоваться для фиксации всех изменений, сделанных с помощью CATransaction
. Это гарантирует, что анимационная транзакция всегда будет зафиксирована, даже если после оператора defer
есть условный код, который возвращается раньше.
private func shakeAnimateView(_ redView: UIView){ CATransaction.begin() defer { CATransaction.commit() } let animate: CABasicAnimation = CABasicAnimation(keyPath: "position") animate.duration = 0.1 animate.repeatCount = 21 animate.autoreverses = true let from_point:CGPoint = CGPointMake(redView.center.x - 5, redView.center.y) let from_value:NSValue = NSValue(cgPoint: from_point) let to_point:CGPoint = CGPointMake(redView.center.x + 5, redView.center.y) let to_value:NSValue = NSValue(cgPoint: to_point) animate.fromValue = from_value animate.toValue = to_value redView.layer.add(animate, forKey: "position") }
b) AVCaptureSession commitConfiguration.Вызывая commitConfiguration()
внутри инструкции defer
, мы гарантируем, что изменения конфигурации AVCaptureSession будут зафиксированы. перед выходом. Может быть много операторов do
–catch
, которые приводят к раннему выходу при возникновении ошибки.
private func setupSessionInput(for position: AVCaptureDevice.Position) { var captureSession: AVCaptureSession = AVCaptureSession() do { guard let device = return AVCaptureDevice.default(AVCaptureDevice.DeviceType.builtInWideAngleCamera,for: AVMediaType.video, position: position) else { } let input = try AVCaptureDeviceInput(device: device) try device.lockForConfiguration() captureSession.beginConfiguration() defer { captureSession.commitConfiguration() } device.autoFocusRangeRestriction = .near device.focusMode = .continuousAutoFocus device.exposureMode = .continuousAutoExposure if let currentInput = captureSession.inputs.filter({$0 is AVCaptureDeviceInput}).first { captureSession.removeInput(currentInput) } captureSession.usesApplicationAudioSession = false captureSession.addInput(input) device.unlockForConfiguration() } catch(let error) { print(error.localizedDescription) return } }
Разница между отсрочкой и возвратом
Оператор defer
выполняется независимо от того, как вы выходите, и работает независимо от того, выходите ли вы из текущей области, что может не включать return
. defer
работает с телом функции, блоком while, блоком if, блоком do и так далее. В вашем методе может быть более одного return
, или вы можете выдать ошибку, или получить break
, или когда вы просто дойдете до последней строки области видимости, defer
будет выполняться во всех возможных случаях. Оператор defer
выполняется после возврата.
struct checkCounter: Sequence, IteratorProtocol { var count: Int mutating func next() -> Int? { if count == 0 { return nil } else { defer { count -= 1 } return count } } }
В приведенном выше коде функция next() сначала возвращает count
, а затем уменьшает значение счетчика на 1.
Отложить с отказом
Ключевое слово fallthrough
не проверяет условия кейса для кейса switch
, в котором оно приводит к падению выполнения. Ключевое слово fallthrough
просто заставляет выполнение кода переходить непосредственно к операторам внутри следующего блока case (или default
case), как в стандартном поведении оператора C switch
. В приведенном ниже фрагменте кода блок case:
является концом области этого блока, а оператор fallthrough
не поддерживает область действия переключателя, поэтому случай 2 также будет выполнен.
defer { print("outer defer") } let counter = 1 switch counter { case 0: print("case 0") case 1: print("case 1") defer { print("case 1 defer") } fallthrough case 2: print("case 2") default: print("default") } // Output case 1 case 1 defer case 2
Использование нескольких операторов отсрочки
Если несколько операторов
defer
появляются в одной и той же области, каждый операторdefer
фактически выполняется в порядке "первый пришел - последний вышел" (FILO). Это означает, что последний операторdefer
в исходном коде выполняется первым, а первый операторdefer
выполняется последним.
Мы можем увидеть несколько примеров ниже, чтобы понять несколько операторов defer
:
-------------------------------------------------------------------- Example 1 -------------------------------------------------------------------- func testDefer() { defer { print("First defer") } defer { print("Second defer") } defer { print("Third defer") } print("Do some work here") } // Output Do some work here Third defer Second defer First defer -------------------------------------------------------------------- Example 2 -------------------------------------------------------------------- func multipleDefer() { defer { print("Defer 1") defer { print("defer 2") } defer { print("Defer 3") defer { print("Defer 4") defer { print("Defer 5") } } defer { print("Defer 6") } } } } // Output Defer 1 Defer 3 Defer 6 Defer 4 Defer 5 defer 2
Ограничения отсрочки
Любой оператор Swift может быть включен в тело оператора defer
, но есть одно исключение. Оператор defer
не может содержать код, передающий управление из оператора defer
. Это означает, что defer
не может включать операторы, такие как операторы break
или return
, или выдавать какие-либо ошибки в теле оператора defer
, поскольку эти операторы вызовут немедленное прекращение выполнения этого блока defer
.
let isDeferExecute: Bool? defer { guard let _ = isDeferExecute else { // ERROR 'return' cannot transfer control out of a defer statement return } print("check defer with return")
Спасибо за чтение, вы можете следить за мной на Medium для получения обновленных статей.
Если у вас есть какие-либо комментарии, вопросы или рекомендации, не стесняйтесь публиковать их в разделе комментариев ниже! 👇 и, пожалуйста, поделитесь и хлопайте 👏👏 если вам понравился этот пост.