> On Sep 19, 2016, at 3:18 AM, Adrian Zubarev via swift-evolution 
> <swift-evolution@swift.org> wrote:
> 
> I think I just found a solution to my problem:
> 
> /// - Parameter child: The new `child` to add to the `children` array.
> public mutating func add(_ child: Element) {
> 
>    let clonedChildReference = Reference(cloning: child.reference)
>    let index = self.reference.children.endIndex
>              
>    self.mutableInsert(clonedChildReference, at: index, isNotOwnReference: 
> child.reference !== self.reference)
> }
>          
> /// Warning: Pass only clonded nodes of type Element to this function!
> private mutating func mutableInsert(_ node: XML.Node, at index: Int, 
> isNotOwnReference: Bool) {
> 
>    // * If `self.reference` is NOT uniquely referenced and `node` is a String,
>    //   we should rebuilt own reference.
>    // * If `self.reference` is NOT uniquely referenced and `node` is a 
> Reference
>    //   `isNotOwnReference` is true, we should rebuilt own reference.
>    // * If `self.reference` is NOT uniquely referenced and `node` is a clone  
>    //   reference to `self.reference` where is `isNotOwnReference` is false, 
> we
>    //   should check if there are more than **two** strong references to 
> rebuild
>    //   own reference, otherwise it's an implementation artifact and we can 
> keep
>    //   old reference (any `node` of type Reference is cloned before it's 
> added  
>    //   to the child array).
>    let isNotKnownUniquelyReferenced = 
> !isKnownUniquelyReferenced(&self.reference)
>              
>    var shouldRebuildReference = false
>              
>    switch node.kind {
>                  
>    case .element(_):
>       let hasMoreThanTwoStrongReferences = (CFGetRetainCount(self.reference) 
> - 1) > 2
>       shouldRebuildReference = (isNotKnownUniquelyReferenced && 
> isNotOwnReference) || hasMoreThanTwoStrongReferences
>                  
>    case .text(_):
>       shouldRebuildReference = isNotKnownUniquelyReferenced
>    }
>              
>    if shouldRebuildReference {
>                  
>       self.reference = Reference(cloning: self.reference, wholeTree: true)
>    }
> 
>    self.reference.insert(node, at: index)
> }
> I’m using CFGetRetainCount(self.reference) to catch that implementation 
> artifact.
> 
If people are resorting to CFGetRetainCount, then we have a problem. The 
optimizer is not under any obligation to bump the refcount to 2.

There must be a better way to handle this. Rather than passing an
'isNotOwnReference' flag, I think you should determine whether a clone
is needed before passing the node into mutableInsert.

You effectively want an API like root.addSelf().

-Andy
> 
> 
> 
> -- 
> Adrian Zubarev
> Sent with Airmail
> 
> Am 19. September 2016 um 09:59:24, Adrian Zubarev 
> (adrian.zuba...@devandartist.com <mailto:adrian.zuba...@devandartist.com>) 
> schrieb:
> 
>> Hello Dave,
>> 
>> thank you for trying to help me. I’ll try to explain the issue with some 
>> more details.
>> 
>> First here is some code:
>> 
>> extension XML {
>>       
>>     public struct Element {
>> 
>>         // public for testing   
>>         public var reference: Reference
>> 
>>         public var name: String { return self.reference.name }
>> 
>>         public var children: [Element] {
>>               
>>             return self.reference.children.flatMap {
>>                   
>>                 guard case .element(let element) = $0.kind else { return nil 
>> }
>>                 return Element(wrapping: element)
>>             }
>>         }
>>           
>>         public init(name: String) {
>>               
>>             self.reference = Reference(name: name)
>>         }
>> 
>>         public mutating func add(_ child: Element) {
>>               
>>             self.mutableInsert(Reference(cloning: child.reference), at: 
>> self.reference.children.endIndex)
>>         }
>>           
>>         // Ignore XML.Node, it's a String or Reference
>>         // Parameter `Node` is assumed to be a clone of a reference passed 
>> to `add` or `insert` method.
>>         private mutating func mutableInsert(_ node: XML.Node, at index: Int) 
>> {
>>               
>>             // Clone own reference all way up to the root
>>             if !isKnownUniquelyReferenced(&self.reference) {
>>                   
>>                 self.reference = Reference(cloning: self.reference, 
>> wholeTree: true)
>>             }
>>               
>>             // Extract a reference or just insert a string as a child
>>             guard case .element(let nodeReference) = node.kind else {
>>                   
>>                 self.reference.insert(node, at: index)
>>                 return
>>             }
>>               
>>             // Check for possible debelopment bug
>>             if nodeReference === self.reference {
>>                   
>>                 fatalError("wrong usage of `mutableInsert` function")
>>             }
>>               
>>             self.reference.insert(nodeReference, at: index)
>>         }
>>           
>>         ...
>>     }
>> }
>> 
>> extension XML.Element {
>>       
>>     // public for testing
>>     public class Reference : XML.Node {
>>           
>>         let name: String
>> 
>>         private(set) weak var parent: Reference?
>> 
>>         private(set) var children: [XML.Node]
>> 
>>         var kind: XML.Node.Kind { return .element(self) }
>> 
>>         ...
>>     }
>> }
>> Now lets focus on the problem.
>> 
>> Every Element is baked with it’s own Reference to be able to traverse the 
>> tree from any of it’s node all way up to the root for example.
>> 
>> Lets look again at the scenario I already described:
>> 
>> var root = XML.Element(name: "root")
>> var elem = XML.Element(name: "elem")
>> 
>> ObjectIdentifier(root.reference) // 0x000060000026ab40
>> ObjectIdentifier(elem.reference) // 0x000060800026bb00
>> 
>> isKnownUniquelyReferenced(&root.reference) // true
>> isKnownUniquelyReferenced(&elem.reference) // true
>> 
>> root.add(elem)
>> 
>> isKnownUniquelyReferenced(&root.reference) // true
>> 
>> root.add(root)
>> 
>> // The reference of root has changed even if the second child   
>> // was cloned and added as a new object to the reference.
>> // 0x000060000026ab40 <-- was thrown away
>> isKnownUniquelyReferenced(&root.reference) // true
>> 
>> ObjectIdentifier(root.reference) // 0x000060000026c680 <— new one
>> The way I’m adding children to the tree is that every passed element of type 
>> XML.Element stores a Reference, which will be cloned and added as a new 
>> standalone object to the children array.
>> 
>> The same happens when we try adding root as it’s own child. We copy root 
>> struct which contains the same reference, then we clone it inside add 
>> method, then we pass the new object to the mutableInsert function. At that 
>> point we don’t need the old reference anymore, I’m speaking of 
>> root.add(root). The problem here is that at that time root.reference has 2 
>> strong references which I cannot escape.
>> 
>> I could workaround the problem if I knew the reference counter value, 
>> because I could check if the passed Element contains the same reference 
>> first. And if it does and we have exactly 2 strong references, I don’t need 
>> to recreate root.reference here.
>> 
>> But I couldn’t find any API for that. :/
>> 
>> 
>> 
>> -- 
>> Adrian Zubarev
>> Sent with Airmail
>> 
>> Am 19. September 2016 um 05:50:57, Dave Abrahams via swift-users 
>> (swift-us...@swift.org <mailto:swift-us...@swift.org>) schrieb:
>> 
>>> 
>>> on Sun Sep 18 2016, Adrian Zubarev <swift-users-AT-swift.org> wrote:
>>> 
>>> > Dear Swift community,
>>> >
>>> > currently I’m building a value type XML library which is baked behind
>>> > the scene with a reference type to manage graph traversing between
>>> > nodes. I also like to COW optimize the xml graph, but I run into one
>>> > single problem atm.
>>> >
>>> > Image this xml tree:
>>> >
>>> > <root>
>>> > <item/>
>>> > </root>
>>> > It’s just a root element with one single child. As for value types it
>>> > should be totally fine to do something like this:
>>> >
>>> > // The given xml tree
>>> > var root = XML.Element(name: "root")
>>> > let item = XML.Element(name: "item")
>>> > root.add(item)
>>> >
>>> > // The problematic behavior
>>> > root.add(root)
>>> > If this would be a simple value type without any references behind the
>>> > scenes you could imagine that the result of the last code line will
>>> > look like this:
>>> >
>>> > <root>
>>> > <item/>
>>> > <root>
>>> > <item/>
>>> > </root>
>>> > </root>
>>> 
>>> Yep, that's exactly the right answer for a tree with value semantics.
>>> The simplest way to implement this tree is to use an Array for the child
>>> nodes.
>>> 
>>> > Basically we copied the whole tree and added it as the second child
>>> > into the original root element.
>>> >
>>> > As for COW optimization this is a problem, just because the passed
>>> > root is a copy of a struct that contains the exact same reference as
>>> > the original root element.
>>> 
>>> I don't understand why that's a problem.
>>> 
>>> > isKnownUniquelyReferenced(&self.reference) will result in false inside
>>> > the add method.
>>> 
>>> ...as it should.
>>> 
>>> > Is there any chance I could force my program to decrease the reference
>>> > counter of that last item after I’m sure I don’t need it?!
>>> 
>>> Which last item? When are you sure you don't need it? What result do
>>> you hope for?
>>> 
>>> > A few more details: inside the add method I’m always cloning the
>>> > passed reference just because graphs aren’t that trivial and otherwise
>>> > I could possibly end up with a cycle graph, which would be really
>>> > bad. After that job I’m sure that I don’t need the passed reference
>>> > anymore and I need a way to escape from it.
>>> >
>>> > I’d appreciate any suggestions and help. :)
>>> 
>>> It's not clear what you want to acheive nor can I picture the code
>>> you're using to acheive it, so it's hard to give useful feedback.
>>> 
>>> Sorry,
>>> 
>>> --
>>> -Dave
>>> 
>>> _______________________________________________
>>> swift-users mailing list
>>> swift-us...@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-users
>> 
> 
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution@swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to