“This is the second day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”
Xcode13 added the Optimize Object Lifetimes compilation option in the Build Setting, which is disabled by default. Apple recommended Setting this option to YES to reduce the Swift Object lifecycle. So you can use memory more efficiently.
Swift, the ARC.
Before changing the compiler setting to YES, let’s take a look at ARC in Swift. There are the following:
- The life cycle of an object starts from
The init ()
toEnd of last use
. - At the end of the life cycle,
ARC
Will objectdealloc
. ARC
throughReference counting
To track down the subjectThe life cycle
.Swift, the compiler
By insertingretain/release
Operation to control reference counting.- When the object’s
Reference counting
for0
When,Swift runtime
Will objectdealloc
.
And OC
Let’s look at the following code
class Traveler{
var name: String
var destination: String?
init(name:String) {
self.name = name
}
}
func test(){
let travel1 = Traveler(name: "LiLy") // 1
// Retain
let travel2 = travel1 // 2
//Release // 3
travek2.destination = "Big Sur" // 4
//Release
print("Done traveling")
}
Copy the code
The compiler inserts a retain operation at the start of the reference and a release operation at the last use. From this, we can analyze:
- 1,
Travler object
At initialization,Reference counting
for1
. - 2, in
travl2
referencetrvel1
When theTravler object
forretain
Operation, at this point,Reference counting
for2
. - 3, in
Last time using Travel1
When theTravler object
forrelease
Operation. At this point,Reference counting
for1
. - 4, in
Travel2 for the last time
When theTravler object
forrelease
Operation, at this point,Reference counting
for0
.
At this point, the Travler object’s life cycle begins with its initialization and ends with its last use.
When we run this function with optimization turned on, the result is
Traveler deinit ........
Done traveling
Copy the code
We can see that Traveler is released before print(“Done traveling”), which guarantees the shortest life of the object. This is different from C++ or OC, where objects are destroyed only after the close parenthesis is executed.
The impact it might have
Weak and undirected references
In most cases, this is fine, but if there are weak or unown references, you need to pay special attention, as shown in the following example:
class Traveler{
var name: String
var account: Account?
init(name:String) {
self.name = name
}
}
class Account {
weak var traveler: Traveler?
var points: Int
init(points: Int, traveler: Traveler?){
self.traveler = traveler
self.points = points
}
func printSummary(){
if let travel = traveler {
print("\(travel.name) has \(points) points")
}
}
}
func test(){
let travel = Traveler(name: "LiLy")
let account = Account(points: 1000, traveler: travel)
travel.account = account // 2
account.printSummary()
}
Copy the code
Travel strongly references the Account object, and Account weakly references the Travel object.
We notice that since the account reference to travel is a weak reference, in the // 2 code, at this point, the Travel object has been freed, and when the // 2 function is executed, the travel object is nil, the condition is not true, and the traveler score is not printed, a silent bug will occur.
withExtendedLifetime
WithExtendedLifetime can be used to extend the lifetime of an object and prevent potential errors.
func test(){
let travel = Traveler(name: "LiLy")
let account = Account(points: 1000, traveler: travel)
travel.account = account
withExtendedLifetime(travel, {
account.printSummary()
})
}
Copy the code
- will
travel
The life cycle of theaccount.printSummary()
Performed.
Or use defer, which extends to the end of the entire function.
func test(){
let travel = Traveler(name: "LiLy") // 1
let account = Account(points: 1000, traveler: travel)
defer {withExtendedLifetime(travel, {})}
travel.account = account
account.printSummary()
}
Copy the code
That’s not a good way to do it, it increases our maintenance costs, and it defeats the purpose of reducing the object’s life cycle.
Redesign with strong references
If you can limit access to objects to only strong references, you can prevent object lifecycle accidents. Here, the printSummary() function is moved back to the Traveler class and weak references are hidden in the Account class, The printSummary() function must now be called through Travel, and since the Account is a strong reference in Traveler, potential errors can be eliminated
class Traveler{
var name: String
var account: Account?
init(name:String) {
self.name = name
}
func printSummary(){
if let account = account {
print("\(name) has \( account.points) points")
}
}
}
class Account {
private weak var traveler: Traveler?
var points: Int
init(points: Int, traveler: Traveler?){
self.traveler = traveler
self.points = points
}
}
func test(){
let travel = Traveler(name: "LiLy")
let account = Account(points: 1000, traveler: travel)
travel.account = account
travel.printSummary() // 2
}
Copy the code
Redesign to avoid weak/unown references
Add a middle class, store necessary information in the middle class, break weak or UNOwn references, and use the middle class to break references between objects. After the redesign, the class declaration is as follows
The reference relationship between them is shown in the figure
deinit
After optimization is enabled, the life cycle of the object will be shortened. If the project deinit method on the object does depend on the external object, the dependent external object may have been released, causing some logic errors. That’s one thing to watch out for.
conclusion
This article describes Xcode 13’s optimization of the Swift object lifecycle, the problems that can arise when this optimization is enabled, and the corresponding suggestions based on the problems themselves.
Reference for this article:
WWDC 21: Swift ARC in Swift: Basics and beyond
If you feel that there is a harvest please follow the way to a love three even: 👍 : a praise to encourage. 🌟 : Collect articles, easy to look back! . 💬 : Comment exchange, mutual progress! .