About us
Our services

Capabilities

Legacy Modernization
Data Platforms
AI & Advanced Analytics

Industries

Automotive
Finance
Manufacturing

Solutions

Databoostr

Data Sharing & Monetization Platform

Cloudboostr

Multicloud Enterprise Kubernetes

Looking for something else?

Contact us for tailored solutions and expert guidance.

Contact
Case studies
Resources

Resources

Blog

Read our blog and stay informed about the industry’s latest trends and technology.

Ready to find your breaking point?

Stay updated with our newsletter.

Subscribe

Insights

Ebooks

Explore our resources and learn about building modern software solutions from experts and practitioners.

Read more
Careers
Contact
Blog
Software development

Capturing objects in closures: Why you’re doing it wrong? – Part 2

Andrii Biehunov
Expert Software Engineer
October 21, 2025
•
5 min read

Table of contents

Heading 2
Heading 3
Heading 4
Heading 5
Heading 6

Schedule a consultation with software experts

Contact us

Choose your closure context wisely

 In the first part of this article , we defined several simple principles of capturing objects in Closures. According to these principles, the closure code associated with a particular instance of a certain type should be considered separately from the code, which is associated either with the global scope or with the type itself. We also came to the conclusion that the pattern with “weakifying”  self and “strongifying” inside the closure should be limited to the code which really depends on self while the other code should be executed independently. But let’s take a closer look at such instance-dependent code. The following proves that the situation is not so obvious.

func uploadFile(url: URL, completion: @escaping (Error?) -> Void) {

 // Getting file content data from URL

 //...

 // Creating file info

 let fileInfoID = self.databaseController.createRecord(data: fileInfo)

 self.remoteFileManager.uploadBinaryData(fileContentData) { [weak self] error in

   guard let strongSelf = self else {

     completion(UploadError.dataError)

     return

   }



   if error != nil {

     strongSelf.databaseController.removeRecord(recordID: fileInfoID)

     completion(error)

   } else {

     // Wait while server will make needed changes to file info

     strongSelf.startTrackingOnServer(recordID: fileInfoID,

                                      completion: completion)

   }

 }

}

The code above creates a record in the database that contains the file info and loads the file to the server. If the upload is successful, the method will notify the server that changes to the file info should be made. Otherwise, in case of an error, we should remove the file info from the database. Both actions depend on  self , so neither of them can be performed if the object referenced by  self had been deallocated before the completion was called. Therefore, calling the completion at the beginning of the closure with the appropriate error in this case seems to be reasonable.

However, such approach breaks the closure logic. If the error occurs, but  self was deallocated before the call closure, we would leave the record about the file that hasn’t been uploaded. Hence, capturing a weak reference to  self is not completely correct here. However, since it is obvious that we cannot capture  self as a strong reference to prevent a retain cycle – what should be done instead in that case?

Let’s try to separate the required actions from the optional ones. An object referenced by  self may be deallocated, but we have to remove the record from the database. With that said, we shouldn’t associate the database with the  self object, but rather use it separately:

func uploadFile(url: URL, completion: @escaping (Error?) -> Void) {

 // Getting file content data from URL

 //...

 // Creating file info

 let fileInfoID = self.databaseController.createRecord(data: fileInfo)

 self.databaseController.uploadBinaryData(fileContentData) { [weak self, databaseController] error in

   if error != nil {

     databaseController.removeRecord(recordID: fileInfoID)

     completion(error)

   } else if let strongSelf = self {

     // Wait while server will make needed changes to file info

     strongSelf.startTrackingOnServer(recordID: fileInfoID, completion: completion)

   } else {

     databaseController.removeRecord(recordID: fileInfoID)

     completion(UploadError.dataError)

   }

 }

}

Pay attention to the closure capture list. It is where we explicitly specify the  databaseController property. This will create a separate local variable inside the closure with the same name referencing this property. Since we didn’t add any modifier to it, the  databaseController is captured by a strong reference. While  self is still a weak reference, there won’t be any retain cycle – which is exactly what we need. As a result, the code is now consistent.
We remove the record from the database in case of an error or in case further action cannot be performed because  self got deallocated (also treating this case as an error).
So, what is the key difference between this code and the previous one? Previously, we were treating  self as the only source of our actions inside the closure. Because of that, the weak reference semantic of  self forced all actions to be optional.

By capturing the object as weak reference we’re saying: “Hey, I don’t need to force this object to live until the closure is executed. It may be deallocated before and that’s fine for me”. However, we forgot about one important thing. Namely, it’s not our real intention to make  self optional in the closure. Instead, we had to use weak  self reference in order not to produce the retain cycle, while some of our actions are required (removing redundant file info from database in case of an error).

Based on this example, we can draw some important conclusions. Even if the object is associated with  self (is its property), we should not treat self as a root object from which we take other objects inside the closure to perform calls on them. Instead, the properties of the  self object may be captured independently if needed.

Let’s take a look at a more generic, yet clear example.

func uploadFile(url: URL, completion: @escaping (Error?) -> Void) {

 // Some preparations

 // ...

 self.someAsyncAction(parameter) { [weak self] in

   guard let strongSelf = self else {

     return

   }



   // ...

   // These calls should be performed

   strongSelf.someObject.requiredToBeCalled()

   strongSelf.someObject.requiredValue = someValue



   // While these ones have sense only if self object still exists

   strongSelf.otherObject.mayBeCalled()

   strongSelf.otherObject.someOptionalValue = someOtherValue



   // currentItem represents selected object that is required to be updated

   // on closure call. Selection may be change several times before our

   // asynchronous action completion

   strongSelf.anotherObject.currentItem.someProperty.requiredUpdate()

 }

}

According to what we’ve just learned,  self should not be used in such way for all calls. Therefore, let’s make some corrections to our code. In this example some calls are required and some are optional. We can safely use  self for all optional calls. For each required one we should determine which object needs to be captured by a strong reference from the call chain like the following:  strongSelf.oject0.object1...objectN.action() . For the first two calls such principle object is obviously the  someObject property. The first one is a call of  requiredToBeCalled() method on it.
The second one assigns value to its  requiredValue property. Consequently, instead of getting it as a property of  self , we should directly capture  someObject in the closure. The next two lines manipulate with the  otherObject property.

As seen in our example, these calls are optional. Meaning, they may be omitted if the object pointed by  self is deallocated (they don’t make sense without  self ). The last line is a bit trickier. It has several properties in a call chain. Since the object on which the call is performed is represented by  someProperty , we may want to capture it directly. However, the actual value returned by  anotherObject.currentItem may (by definition) change. That is, the call to  self.anotherObject.currentItem inside the closure may return a different object from the one it was returning before  someAsyncAction() was called.

Thus, in case of capturing  someProperty , we may potentially use an object which is out of date and is returned by some old  currentItem , while the actual one will remain unchanged. Of course, for the same reason we should not capture the  currentItem object itself. So, the right choice here is the  anotherObject property which is the source of the actual  currentItem object. After rewriting the example according to our corrections, we will receive the following:

func uploadFile(url: URL, completion: @escaping (Error?) -> Void) {

 // Some preparations

 // ...

 self.someAsyncAction(parameter) { [weak self, someObject, anotherObject] in

   // ...

   // These calls should be performed

   someObject.requiredToBeCalled()

   someObject.requiredValue = someValue



   // While these ones have sense only if self object still exists

   if let strongSelf = self {

     strongSelf.otherObject.mayBeCalled()

     strongSelf.otherObject.someOptionalValue = someOtherValue

   }



   // currentItem represents selected object that is required to be updated

   // on closure call. Selection may be change several times before our

   // asynchronous action completion

   anotherObject.currentItem.someProperty.requiredUpdate()

 }

}

In general, when we have a call chain as follows  self.oject0.object1...objectN.action() to determine which object from the chain should be captured, we should find  objectK that conforms to the following rule:

There are two ways of calling our  action() inside our closure:

 1. Capture  self and use it as a root (or source) object (full call chain).
 2. Using  objectK in closure directly (call subchain) should have exactly the same effect.

That is, if we were to substitute the call chain  self.oject0.object1...objectK...objectN.action() (capturing  self ) in the closure with the subchain  objectK...objectN.action() (capturing objects pointed by  objectK at the moment of the closure definition) the effect of the call will be the same. In case there are several objects conforming to this rule, it’s better to choose the one that is the closest to the action (method call or property change). This will avoid redundant dependencies in the closure. For example, if in the call chain  self.object0.object1.object2.object3.action() we have  object0, object1, object2 conforming to the rule, it’s better to use  object2.object3.action() in closure rather than  object0.object1.object2.object3.action() since the longest chain means more semantic dependencies - the source of our action will be  object0 from which we get the next  object1 and so on instead of using  object2 directly).

Bring it all together

Let’s now summarize our knowledge about the closure context. In cases where retain cycles may occur, we should be very careful with what we capture inside the closure. We should definitely not use the “weakify” - ”strongify” pattern in all cases as a rule of thumb. There is no “golden rule” here. Instead, we have a set of principles for writing the closure code that we should follow not only to resolve a retain cycle problem, but also to keep the closure implementation consistent. These are the following:

 1. Determine the  instance that can cause a retain cycle (  self or any other object the capturing of which can cause a retain cycle).

 2. The code that is not related to this particular  instance should be considered. Such code may perform actions on other objects or even types (including the type of our  instance ). Therefore, the code should be executed regardless of whether the  instance exists inside the closure or not.

 3. For the code which relates to the  instance we should define which part of it is  optional (may be omitted if  instance is deallocated) and which part is  required (should be called independently of  instance existence).

 4. For the  optional code we may apply the “weakify” - ”strongify” pattern to the  instance . That way, we’ll try to obtain a strong reference to that  instance inside the closure using captured weak reference. And we’ll perform optional actions only if it still exists.

 5. For performing  required code we cannot apply reference to the  instance . Instead, for each call chain like  instance.property0.property1...propertyN.requiredAction() we need to define what property to use for capturing the corresponding object in the closure. In most cases, however, it’s simple. For instance, in the example mentioned earlier for  self.someObject.requiredToBeCalled() call we choose  someObject to be captured.

Please note that the proposed solution isn’t only limited to capturing  self in closures. The principles listed above may be applied to any object that may cause a retain cycle inside the closure.

But let’s point out that we’re not defining strict rules. There are no such rules when it comes to closure context. What we’ve done here is we deduced some principles based on common use cases of closures. There may be other, much more complicated examples in real code. Sometimes it’s really challenging to choose what objects to retain inside the closure, especially when refactoring existing code. The main goal of this article is to give useful tips on how to deal with the closure context, how to have the right mindset when choosing the objects that should be used inside the closure and a correct reference semantic for them.

Grape Up guides enterprises on their data-driven transformation journey

Ready to ship? Let's talk.

Check our offer
Blog

Check related articles

Read our blog and stay informed about the industry's latest trends and solutions.

Software development

Capturing objects in closures: why you’re doing it wrong? – Part 1

The basics of closures

Many modern programming languages have a closure concept. Closures are self-contained blocks of functionalities which can be passed around and called. Additionally, they can work with environmental data (variables, functions, types etc.) captured from the outer lexical context in which the closure is defined. A closure may be bound to a variable (constant, parameter, etc.) and called through it.

With that said, there are two main aspects: the code of the closure itself and the data captured from the context. When talking about closures, we usually mean the code. However, the context isn’t any less important and, most often, it is the trickiest part of closures.

Retain cycles

Each programming language or runtime environment may determine a different way of handling and storing the closure’s context. Nevertheless, special attention should be paid to languages which use the reference counting semantic to deal with objects. With such languages, we have a retain cycle problem. Let’s quickly refresh and go through what we already know about reference counting as well as retain cycles, and how to deal with them.

In reference counting environment each object has associated a reference counter which shows how many objects use it by a strong reference. If the object is assigned to a variable representing a strong reference to the object, its reference (retain) count is increased. When such variable goes out of scope, the reference count decreases. As soon as the count comes to a 0, the object is deallocated. Therefore, in case the sequence of the objects cyclically reference each other, all of those objects will never be deallocated. Why? Because even if there will be no references which would point to a chosen object outside the cycle, each of these objects will still have a strong reference from inside the cycle which “retains” them. Such situation is known as “a memory leak” and is called a retain cycle.

Usually, languages in a reference counting environment provide a special type of references called weak references which don’t increase the retain count of the object they point to. Instead, when the object is deallocated, each weak reference is nilled in runtime. The right way to avoid retain cycles is to use weak references. In order to break the retain cycle in the previously mentioned object sequence it would be enough to make one object (for simplicity, let’s assume the last one in sequence) hold the weak reference to the next object (the first one).

However, let’s go back to closures and their context. We will use Swift to show certain examples of the code, but keep in mind that all solutions are applicable to any language that has closures and a reference counting environment. In case a closure uses objects from the closure’s outer context, that closure will capture the reference to those objects. The most common case when a retain cycle happens is when the object stores a closure while the closure (directly or through the reference chain with other objects) references the object itself using a strong reference. Take a look at the following example:

database.observeChanges(recordID) { snapshot in

 // Some code...

 database.stopObserving(recordID)

 // Doing other stuff...

}

In the example above, the closure is retained inside the method which will be called later. On the other hand, the closure retains a  database object since the object is used inside it.

Note that in simple situations when the closure is not stored by the object, (like closures passed to  forEach() method of  Sequences in Swift) or when the closure is removed from the object after the call (like with  DispatchQueue.async() method), the retain cycle does not occur. Therefore, we’ll not consider such cases and instead we will focus on situations when the retain cycle takes place. The language/environment should provide a mechanism to deal with retain cycles for objects captured by the closure. That is, we should have a way to specify how the object should be captured – by means of weak or strong reference. In Swift closures, we have a capture list that allows us to specify how each object should be captured. Let’s take a look at the following example.

let completion = { [weak dataBaseAccessor, someController] (error: Error?) -> Void in

 otherController.someAction()

 // Doing other stuff

}

The capture list allows to explicitly specify what variables are captured and what reference type (weak or strong) should be used to capture them. In the example above, the capture list is defined at the beginning of the closure header:  [weak dataBaseAccessor, someController] . In the given code,  dataBaseAccessor is captured by weak reference. While  someController and  otherController (which is not explicitly specified in the capture list) are captured by strong reference. Note that apart from  weak and  strong references, Swift has a concept of  unowned reference for the capture list. But its usage is strictly limited to the case when the captured object’s lifetime is guaranteed not to be shorter than the lifetime of the closure. Otherwise, its usage is unsafe. We won’t consider this type of references here.

Let’s show an example with retain cycles caused by closures. Commonly, in the example below we avoid the retain cycle by specifying that  self should be captured as  weak reference.

class DatabaseController: DatabaseAccessorDelegate {

 // Some code ...

 var requestCompletion: (([String : Any]?, Error?) -> Void)?

 func requestData(query: Query, completion: @escaping ([String : Any]?, Error?) -> Void) {

   requestCompletion = completion

   dataBaseAccessor.performQuery(query)

 }



 // DatabaseAccessorDelegate

 func accessor(_ acessor: DatabaseAccessor, didRetrieveData data: [String : Any], error: Error?) {

   requestCompletion?(data, error)

 }

}



class ViewController {

 let databaseController = DatabaseController()

 var cachedData: [String : Any]?

 // Other code ...

 @IBAction func dataRequestButtonAction(_ sender: UIButton) {

   databaseController.requestData(query: query) { [weak self] dataSnapshot, error in

     guard let strongSelf = self else {

       return

     }



     strongSelf.cachedData = dataSnapshot

     strongSelf.updateUI()

   }

 }

}

When calling a  databaseController.requestData() in completion  self has  weak modifier. To ensure that  self won’t be deallocated during the completion execution, we assign it to strong reference  strongSelf at the beginning. Otherwise, if  self was already deallocated before the completion closure called, we’d simply exit the closure. Why is this trick needed here? Let’s show the relations between our objects in terms of retaining/not retaining.  ViewController retains the  DatabaseController . In turn, the  DatabaseController retains the completion closure in  requestData() . If completion retained the  ViewController via  self variable, we would get the retain cycle here.

Such approach is often described in terms of “weakifying” and ”strongifying”. Suppose you need to use some object inside closure but it can lead to retain cycle. Capture weak reference to the object when defining closure (“weakifying”), then check if it still exists (reference not nil) at the beginning of closure. If it’s still there assign it to a reference with strong semantic (“strongifying”). Otherwise exit the closure. This pattern is what we actually do in 99% of cases when it comes to closures with potential retain cycles.

Well, programmers try to find (and in most cases reasonably) the “golden rule” for almost anything. However, when it comes to closure context it’s not so simple as it seems to be. The pattern is often overestimated and overused. It’s not a silver bullet (as it is considered to be by many programmers). As a matter of fact, it may completely break the logic of your closure when used unwarily.

A Closer Look

Our previous example is a simple case so the pattern described is pretty useful and works well. But let’s go further. As a matter of fact, some areas are more complicated than they look. To demonstrate this, we’ll extend a bit our implementation of the  ViewController .

class ViewController {

 // Other code ...



 @IBAction func dataRequestButtonAction(_ sender: UIButton) {

   databaseController.requestData(query: query) { [weak self] dataSnapshot, error in

     guard let strongSelf = self else {

       return

     }

     strongSelf.cachedData = dataSnapshot

     strongSelf.updateUI()



     let snapshot = dataSnapshot ?? [:]

     NotificationCenter.default.post(name: kDataWasUpdatedNotification,

                                     object: nil,

                                     userInfo: [kDataSnapshotKey : snapshot])

   }

 }

}

We are now notifying about the data that is being retrieved using notification mechanism. In this case, if  self was deallocated we’re just returning from closure. But this will skip posting the notification. So despite the data was successfully retrieved observers of the notification won’t get it just because the  ViewController instance was deallocated. Thus, the approach with exiting from the closure creates an unnecessary and – what is more important – implicit dependency between the presence of the  ViewController in memory and sending the notification. In this case, we should try to obtain  self and, if it’s there, proceed with calls on it. And then independently of the  self object state, post the notification. See improved code below:

@IBAction func dataRequestButtonAction(_ sender: UIButton) {

 databaseController.requestData(query: query) { [weak self] dataSnapshot, error in

   if let strongSelf = self {

     strongSelf.cachedData = dataSnapshot

     strongSelf.updateUI()

   }



   let snapshot = dataSnapshot ?? [:]

   NotificationCenter.default.post(name: kDataWasUpdatedNotification,

                                   object: nil,

                                   userInfo: [kDataSnapshotKey : snapshot])

 }

}

This allows us to make an important step towards understanding how context in closures is used. We should separate code that depends on  self (and may cause the retain cycle) from code that doesn't depend on it.

It’s all about associating

Choosing which part of the closure code really depends on  self (or generally on the instance of the type) and which doesn’t, is trickier than it seems to be. Let’s refactor our example a bit. Let’s suppose we have a delegate that should also be notified. Also, assume we need to notify about data changes in several places across our code. The right thing to do is to place the code for notifying clients into a separate method.

@IBAction func dataRequestButtonAction(_ sender: UIButton) {

 databaseController.requestData(query: query) { [weak self] dataSnapshot, error in

   guard let strongSelf = self else {

     return

   }

   strongSelf.cachedData = dataSnapshot

   strongSelf.updateUI()



   let snapshot = dataSnapshot ?? [:]

   strongSelf.notifyUpdateData(snapshot: snapshot)

 }

}



func notifyUpdateData(snapshot: [String : Any]) {

 delegate?.controller(self, didUpdateCahcedData: snapshot)

 NotificationCenter.default.post(name: kDataWasUpdatedNotification,

                                 object: nil,

                                 userInfo: [kDataSnapshotKey : snapshot])

}

At first glance, in the code above all the calls depend on  self (called on it) so the approach to make an exit from the closure if  self is nil seems to be applicable. Although, in essence our code after refactoring does exactly the same what it had done before. Therefore, if  self is nil a notification will be missed. How to deal with this situation? The problem lies in the wrong criteria which has been chosen to decompose the code. We’ve combined the delegate callback invocation and the posting notification into a single method because these actions are semantically close to each other. However, we didn’t pay attention to the dependency on a particular instance (expressed via the calling method using  self ). The delegate has a strict dependency on the instance because it is a property of the  ViewController (a part of its state). Sending the notification is not related to any instance of the  ViewController , but it rather relates to the type itself. That is, the proper place for the delegate call which is an instance method, while the notification related code should be placed in the class method.

@IBAction func dataRequestButtonAction(_ sender: UIButton) {

 databaseController.requestData(query: query) { [weak self] dataSnapshot, error in

   let snapshot = dataSnapshot ?? [:]

   if let strongSelf = self {

     strongSelf.cachedData = dataSnapshot

     strongSelf.updateUI()

     strongSelf.notifyDelegateDataWasUpdated(snapshot: snapshot)

   }



   ViewController.notifyDataUpdate(snapshot: snapshot)

 }

}



func notifyDelegateDataWasUpdated(snapshot: [String : Any]) {

 delegate?.controller(self, didUpdateCahcedData: snapshot)

}



class func notifyDataUpdate(snapshot: [String : Any]) {

 NotificationCenter.default.post(name: kDataWasUpdatedNotification,

                                 object: nil,

                                 userInfo: [kDataSnapshotKey : snapshot])

}

The example shows that associating the code with the instance or the type itself plays an important role in the case of using it inside closures.

Generally, when choosing between placing the code to the type method or to the instance one, use the following “rules of thumb”.

 1. If the task done by your code is not associated with any particular instance, place your code in the type (class/static) method. Such code does not manipulate (neither accesses nor alters) the state of any particular instance. This means that such task will make sense in case:

  •  no instance of that type exists;
  •  the method is dealing with multiple instances of the same type in the same manner.

 2. Otherwise, place the code into the instance method.

 3. If the code has mixed associations (one part doesn’t depend on any instance while another one does), the task should be decoupled into smaller tasks. This rule should be then applied to each task separately.

In this case, you will be able to decompose the code.
These rules are also helpful when dealing with closures. That is, static functions are not associated with a particular instance, so such code should be called independently on the one that relates to the  self object.

Let’s sum up what we have at this point. In general, inside the closure we should distinct the code that does not relate to the  self instance (which retains the closure) from the rest, so that when performing, it should not rely on the existence of the object referenced by  self . These rules describe how we can determine what should be associated with the particular instance and what should not. Thanks to that, they are helpful even if we’re not going to extract this code to a static method, but rather use it inline inside the closure.

However, it’s only the tip of the iceberg. The true complexity of the proper closure context usage resides in the code that depends on an instance of the particular type. We’ll focus on such code in  the second part of our article .

Read more
View all
Connect

Interested in our services?

Reach out for tailored solutions and expert guidance.

Stay updated with our newsletter

Subscribe for fresh insights and industry analysis.

About UsCase studiesContactCareers
Capabilities:
Legacy ModernizationData PlatformsArtificial Intelligence
Industries:
AutomotiveFinanceManufacturing
Solutions:
DataboostrCloudboostr
Resources
BlogInsights
© Grape Up 2025
Cookies PolicyPrivacy PolicyTerms of use
Grape Up uses cookies

This website uses cookies to improve its user experience and provide personalized content for you. We use cookies for web analytics and advertising. You can accept these cookies by clicking "OK" or go to Details in order to manage your cookies preferences more precisely. To learn more, check out our Privacy and Cookies Policy

Accept allDetails
Grape Up uses cookies

Essential website cookies are necessary to provide you with services available through the website, autosave your settings and preferences, and to enhance the performance and security of the website - you have the right not to accept them through your web browser's settings, but your access to some functionality and areas of our website may be restricted.

Analytics cookies: (our own and third-party : Google, HotJar) – you can accept these cookies below:

Marketing cookies (third-party cookies: Hubspot, Facebook, LinkedIn) – you can accept these cookies below:

Ok