Поведение и общий вариант использования с примерами

Оператор 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 будут зафиксированы. перед выходом. Может быть много операторов docatch, которые приводят к раннему выходу при возникновении ошибки.

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 для получения обновленных статей.

Если у вас есть какие-либо комментарии, вопросы или рекомендации, не стесняйтесь публиковать их в разделе комментариев ниже! 👇 и, пожалуйста, поделитесь и хлопайте 👏👏 если вам понравился этот пост.