简体   繁体   中英

Why aren't mutating members triggering breakpoints in Xcode?

I have encountered something that i can't seem to figure out from reading the documentations for Xcode or the LLDB. When the application reaches a line that has a breakpoint, in my mind the LLDB should break on that line if there's a breakpoint present. In cases where accessing the code on that line while performing a mutating function will not trigger the breakpoint.

Consider the following example:

class DataSource {

    private init(){

        data = []

        for i in 0..<10 {

            data.append(i) // mutating func
        }
    }

    static let sharedInstance = DataSource()

    var data: [Int]! // Set a breakpoint on this line
}

class Worker {

    func work(){

        print("Append item")

        DataSource.sharedInstance.data.append(23) // mutating func

        print("Get first item")

        print(DataSource.sharedInstance.data[0]) // subscript - TRIGGERS BREAKPOINT

        print("Drop first")

        print(DataSource.sharedInstance.data.dropFirst()) // func - TRIGGERS BREAKPOINT

        print("Remove first")

        print(DataSource.sharedInstance.data.removeFirst()) // mutating func

        print("Remove at 0")

        print(DataSource.sharedInstance.data.remove(at: 0)) // mutating func
    }
}

.data[0] and .dropFirst() are triggering the breakpoint, the other function calls are not. The only difference i can see is that those functions not breaking are mutating functions.

While the breakpoint is not triggered, a watchpoint added on the same line will trigger every time.

Can someone please explain this behavior?

On macOS, your breakpoint resolves to:

(lldb) break list
Current breakpoints:
1: source regex = "Set a breakpoint", exact_match = 0, locations = 3, resolved = 3, hit count = 3
  1.1: where = mutable`mutable.DataSource.(in _788A7EC739C0395377AC3966BEDD9D35).init() -> mutable.DataSource + 40 at mutable.swift:15, address = 0x0000000100001b08, resolved, hit count = 1 
  1.2: where = mutable`mutable.DataSource.data.getter : Swift.ImplicitlyUnwrappedOptional<Swift.Array<Swift.Int>> + 80 at mutable.swift:15, address = 0x0000000100001e00, resolved, hit count = 2 
  1.3: where = mutable`mutable.DataSource.data.setter : Swift.ImplicitlyUnwrappedOptional<Swift.Array<Swift.Int>> + 96 at mutable.swift:15, address = 0x0000000100001e70, resolved, hit count = 0 

That makes sense, the definition of the "data" ivar produces a setter, getter and init method and those functions get associated with this line.

But if you look at the code for mutable accesses, for instance the append call, none of these methods is used, instead the assembly looks like:

(lldb) dis -c 5 -s 0x10000203b
mutable`Worker.work():
    0x10000203b <+235>: callq  0x100002cf0               ; type metadata accessor for mutable.DataSource at mutable.swift
    0x100002040 <+240>: movq   %rax, -0xf0(%rbp)
    0x100002047 <+247>: callq  0x100001d50               ; mutable.DataSource.sharedInstance.unsafeMutableAddressor : mutable.DataSource at mutable.swift
    0x10000204c <+252>: movq   (%rax), %rax
    0x10000204f <+255>: movq   %rax, %rcx

It is going through the type metadata to access the variable it is going to change. That's a generic function and isn't triggered by the definition of the "data" ivar so it isn't assigned to that definition line. That access must know it can skip the getter or setter, but you'll have to get somebody else to explain why it does that...

If you are curious about this you can try rephrasing your question to be about the choices Swift makes for data access and somebody more familiar with the implementation of the language might weigh in.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM