@NSCopying currently does not affect initializers


(Torin Kwok) #1

Hello guys,

Note: This issue has been originally presented in swift-users mailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

I have no idea whether it's a bug or special consideration. After all, as a special consideration, it seems too strange that this behavior has not been obviously documented.

Best Regards,
Torin Kwok


(Torin Kwok) #2

To better demonstrate the problems I’m describing, I gave out a simple demo running in Playground to reproduce that. Apart from that, I also attached a Playground file that contains identical content in this mail, for the convenience of examination.

import Foundation

class Person: NSObject,
  NSCopying {

  var firstName: String
  var lastName: String
  var job: String?

  init( firstName: String, lastName: String, job: String? = nil ) {
    self.firstName = firstName
    self.lastName = lastName
    self.job = job

    super.init()
    }

  /// Conformance to <NSCopying> protocol
  func copy( with zone: NSZone? = nil ) -> Any {
    let theCopy = Person.init( firstName: firstName, lastName: lastName )
    theCopy.job = job

    return theCopy
    }

  override var description: String {
    return "\(firstName) \(lastName)" + ( job != nil ? ", \(job!)" : "" )
    }

  }

let johnAppleseed = Person( firstName: "John", lastName: "Appleseed", job: "CEO" )
var refJohnAppleseed = johnAppleseed

// assigning wihtout copying semantic:
refJohnAppleseed.job = "Engineer"

// `cloneJohnAppleseed` and `johnAppleseed` have the identical `job` ...
refJohnAppleseed
johnAppleseed
// ... and the assertion **would not** fail:
assert( refJohnAppleseed === johnAppleseed )

// Assigning a copy of johnAppleseed to clonedJohnAppleseed,
// which was returned by `copy( zone: ) -> Any`
var clonedJohnAppleseed = johnAppleseed/* refJohnAppleseed is also okay */.copy() as! Person

clonedJohnAppleseed.job = "Designer"
johnAppleseed
// Alright you see, setting the job of `clonedJohnAppleseed` doesn't affect the
// job stored in `johnAppleseed`.

//: Up to now, everything goes right. However, when we begin introducing a new class consuming instances of `Person` class...

class Department: NSObject {

  // Here, we're expecting that `self.employee` would automatically
  // store the deeply-copied instance of `Person` class
  @NSCopying var employee: Person

  init( employee _ExternalPerson: Person ) {

    // CAUTION! That's the key point:
    // `self.employee` has been marked with `@NSCopying` attribute
    // but what would take place here is only the shallow-copying.
    // In the other words, `self.employee` will share identical underlying
    // object with `_ExternalPerson`.
    self.employee = _ExternalPerson
    super.init()

    // Assertion will definitely fail since Swift do not actually
    // copy the value assigned to this property even though
    // `self.employee` has been marked as `@NSCoyping`:

    /* assert( self.employee !== employee ) */
    }

  override var description: String {
    return "A Department: [ ( \(employee) ) ]"
    }

  }

let isaacNewton = Person( firstName: "Isaac", lastName: "Newton", job: "Mathematician" )
let lab = Department.init( employee: isaacNewton )

isaacNewton.job = "Astronomer"
lab.employee
// Oops! Setting the job of `isaacNewton` affects the job stored in `lab.employee`.
// That's an unexpected behavior as we have declared `employee` property as
// `@NSCopying`. Obviously, `@NSCopying` semantic became effectless implicitly
// within the initializer of `Department` class.

// For the moment, we're required to explictly invoke `copy()` method on instances
// that wanna be copied to make sure that classes' properties are able to store
// deeply-copied results during the initialization:

/* self.employee = _ExternalPerson.copy() as! Person */

//: What indeed makes me confusing is that...

// @NSCopying semantic does work properly within the scope other than initializers:

lab.employee = isaacNewton
isaacNewton.job = "Physicist"
lab.employee
// That's it! If we assigned external instance to `lab.employee`,
// `@NSCopying` semantic would be well respected.

nscopying_semantic_demo.playground.zip (12.7 KB)

···

On 28 Jan 2017, at 12:34, Torin Kwok <torin@kwok.im> wrote:

Hello guys,

Note: This issue has been originally presented in swift-users mailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

I have no idea whether it's a bug or special consideration. After all, as a special consideration, it seems too strange that this behavior has not been obviously documented.

Best Regards,
Torin Kwok


(Jean-Daniel) #3

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

···

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution <swift-evolution@swift.org> a écrit :

Hello guys,

Note: This issue has been originally presented in swift-users mailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.


(Torin Kwok) #4

Yep, I also admit the design of forbidding calling a setter before full
class initialization is reasonable and what's really annoying is the
inconsistency.

However, making @NSCopying attribute not subjects to the fact that
setters would not be invoked in initializers perhaps is viable too. In
the other words, assigning a value to a property whether or not by
calling a setter has no influence on whether @NSCopying semantic'd work:
copying should always take place after a property has been declared as
@NSCopying.

Jean-Daniel writes:

···

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution <swift-evolution@swift.org> a écrit :

Hello guys,

Note: This issue has been originally presented inswift-usersmailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

--
Torin Kwok (郭桐)
OpenPGP/GnuPG: https://keybase.io/kwok


(Rod Brown) #5

I agree that there is an issue here.

While I understand that the initialiser avoids the full setter for direct access, I would expect the attribute to mean that the substituted direct access still applied the attribute you marked the API with. I would consider the fact that it doesn't work as a dangerous gap in the API.

It is also concerning if we consider how this will work with Property Behaviours that are planned for Swift in the future. If we made NSCopying a property behaviour, the direct access would mean it too would not be invoked at initial access so I'm not sure how the best way to get around this is - should we do compiler magic to copy in the initialiser, or should we warn if we don't detect a call to copy() or copy(with:) in the initialiser?

I think we at least need to do something here. It's a very convoluted piece of logic to say the @NSCopying attribute doesn't work in an initialiser and it's hardly intuitive despite the fair reasoning.

Rod

···

On 29 Jan 2017, at 4:47 pm, Torin Kwok via swift-evolution <swift-evolution@swift.org> wrote:

Yep, I also admit the design of forbidding calling a setter before full
class initialization is reasonable and what's really annoying is the
inconsistency.

However, making @NSCopying attribute not subjects to the fact that
setters would not be invoked in initializers perhaps is viable too. In
the other words, assigning a value to a property whether or not by
calling a setter has no influence on whether @NSCopying semantic'd work:
copying should always take place after a property has been declared as
@NSCopying.

Jean-Daniel writes:

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution <swift-evolution@swift.org> a écrit :

Hello guys,

Note: This issue has been originally presented inswift-usersmailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

--
Torin Kwok (郭桐)
OpenPGP/GnuPG: https://keybase.io/kwok
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Douglas Gregor) #6

I agree that there is an issue here.

While I understand that the initialiser avoids the full setter for direct access, I would expect the attribute to mean that the substituted direct access still applied the attribute you marked the API with. I would consider the fact that it doesn't work as a dangerous gap in the API.

It is also concerning if we consider how this will work with Property Behaviours that are planned for Swift in the future. If we made NSCopying a property behaviour, the direct access would mean it too would not be invoked at initial access so I'm not sure how the best way to get around this is - should we do compiler magic to copy in the initialiser, or should we warn if we don't detect a call to copy() or copy(with:) in the initialiser?

I think we should be doing the compiler magic to call copy(with:) in the initializer, because that seems like the most direct way to maintain the @NSCopying contract without changing the underlying direct-storage model.

I think we at least need to do something here. It's a very convoluted piece of logic to say the @NSCopying attribute doesn't work in an initialiser and it's hardly intuitive despite the fair reasoning.

I agree that we need to do something here. It feels like it’s just a bug—that this is the only way that @NSCopying makes sense in an attribute. Might even be a good starter bug for someone who wants to dip their tows into the type checker!

  - Doug

···

On Jan 28, 2017, at 10:43 PM, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

Rod

On 29 Jan 2017, at 4:47 pm, Torin Kwok via swift-evolution <swift-evolution@swift.org> wrote:

Yep, I also admit the design of forbidding calling a setter before full
class initialization is reasonable and what's really annoying is the
inconsistency.

However, making @NSCopying attribute not subjects to the fact that
setters would not be invoked in initializers perhaps is viable too. In
the other words, assigning a value to a property whether or not by
calling a setter has no influence on whether @NSCopying semantic'd work:
copying should always take place after a property has been declared as
@NSCopying.

Jean-Daniel writes:

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution <swift-evolution@swift.org> a écrit :

Hello guys,

Note: This issue has been originally presented inswift-usersmailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

--
Torin Kwok (郭桐)
OpenPGP/GnuPG: https://keybase.io/kwok
_______________________________________________
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


(Torin Kwok) #7

Thanks Doug, I’m writing a proposal about it.

- Torin

···

On 31 Jan 2017, at 07:20, Douglas Gregor <dgregor@apple.com> wrote:

On Jan 28, 2017, at 10:43 PM, Rod Brown via swift-evolution <swift-evolution@swift.org> wrote:

I agree that there is an issue here.

While I understand that the initialiser avoids the full setter for direct access, I would expect the attribute to mean that the substituted direct access still applied the attribute you marked the API with. I would consider the fact that it doesn't work as a dangerous gap in the API.

It is also concerning if we consider how this will work with Property Behaviours that are planned for Swift in the future. If we made NSCopying a property behaviour, the direct access would mean it too would not be invoked at initial access so I'm not sure how the best way to get around this is - should we do compiler magic to copy in the initialiser, or should we warn if we don't detect a call to copy() or copy(with:) in the initialiser?

I think we should be doing the compiler magic to call copy(with:) in the initializer, because that seems like the most direct way to maintain the @NSCopying contract without changing the underlying direct-storage model.

I think we at least need to do something here. It's a very convoluted piece of logic to say the @NSCopying attribute doesn't work in an initialiser and it's hardly intuitive despite the fair reasoning.

I agree that we need to do something here. It feels like it’s just a bug—that this is the only way that @NSCopying makes sense in an attribute. Might even be a good starter bug for someone who wants to dip their tows into the type checker!

  - Doug

Rod

On 29 Jan 2017, at 4:47 pm, Torin Kwok via swift-evolution <swift-evolution@swift.org> wrote:

Yep, I also admit the design of forbidding calling a setter before full
class initialization is reasonable and what's really annoying is the
inconsistency.

However, making @NSCopying attribute not subjects to the fact that
setters would not be invoked in initializers perhaps is viable too. In
the other words, assigning a value to a property whether or not by
calling a setter has no influence on whether @NSCopying semantic'd work:
copying should always take place after a property has been declared as
@NSCopying.

Jean-Daniel writes:

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution <swift-evolution@swift.org> a écrit :

Hello guys,

Note: This issue has been originally presented inswift-usersmailling list <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004552.html>. And then I post it again here at the suggestion <https://lists.swift.org/pipermail/swift-users/Week-of-Mon-20170123/004561.html> of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 <rdar://21383959>) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

--
Torin Kwok (郭桐)
OpenPGP/GnuPG: https://keybase.io/kwok
_______________________________________________
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


(Torin Kwok) #8

I have given out a proposal about it: Compensate for the inconsistency of @NSCopying's behavior. Please give it some reviews. Thanks.

Best,
Torin

Thanks Doug, I’m writing a proposal about it.

- Torin

···

On Tue, Jan 31, 2017 at 2:34 PM +0800, "Torin Kwok via swift-evolution" <swift-evolution@swift.org> wrote:

On 31 Jan 2017, at 07:20, Douglas Gregor wrote:

On Jan 28, 2017, at 10:43 PM, Rod Brown via swift-evolution wrote:

I agree that there is an issue here.

While I understand that the initialiser avoids the full setter for direct access, I would expect the attribute to mean that the substituted direct access still applied the attribute you marked the API with. I would consider the fact that it doesn't work as a dangerous gap in the API.

It is also concerning if we consider how this will work with Property Behaviours that are planned for Swift in the future. If we made NSCopying a property behaviour, the direct access would mean it too would not be invoked at initial access so I'm not sure how the best way to get around this is - should we do compiler magic to copy in the initialiser, or should we warn if we don't detect a call to copy() or copy(with:) in the initialiser?

I think we should be doing the compiler magic to call copy(with:) in the initializer, because that seems like the most direct way to maintain the @NSCopying contract without changing the underlying direct-storage model.

I think we at least need to do something here. It's a very convoluted piece of logic to say the @NSCopying attribute doesn't work in an initialiser and it's hardly intuitive despite the fair reasoning.

I agree that we need to do something here. It feels like it’s just a bug—that this is the only way that @NSCopying makes sense in an attribute. Might even be a good starter bug for someone who wants to dip their tows into the type checker!

  - Doug

Rod

On 29 Jan 2017, at 4:47 pm, Torin Kwok via swift-evolution wrote:

Yep, I also admit the design of forbidding calling a setter before full
class initialization is reasonable and what's really annoying is the
inconsistency.

However, making @NSCopying attribute not subjects to the fact that
setters would not be invoked in initializers perhaps is viable too. In
the other words, assigning a value to a property whether or not by
calling a setter has no influence on whether @NSCopying semantic'd work:
copying should always take place after a property has been declared as
@NSCopying.

Jean-Daniel writes:

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution a écrit :

Hello guys,

Note: This issue has been originally presented inswift-usersmailling list . And then I post it again here at the suggestion of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 ) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

--
Torin Kwok (郭桐)
OpenPGP/GnuPG: https://keybase.io/kwok
_______________________________________________
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


(Torin Kwok) #9

Oh sorry, perhaps I confused the standard procedure.

Best,
Torin Kwok

I have given out a proposal about it: Compensate for the inconsistency of @NSCopying's behavior. Please give it some reviews. Thanks.

Best,
Torin

Thanks Doug, I’m writing a proposal about it.

- Torin

···

On Wed, Feb 1, 2017 at 3:34 PM +0800, "Torin Kwok via swift-evolution" <swift-evolution@swift.org> wrote:
On Tue, Jan 31, 2017 at 2:34 PM +0800, "Torin Kwok via swift-evolution" <swift-evolution@swift.org> wrote:

On 31 Jan 2017, at 07:20, Douglas Gregor wrote:

On Jan 28, 2017, at 10:43 PM, Rod Brown via swift-evolution wrote:

I agree that there is an issue here.

While I understand that the initialiser avoids the full setter for direct access, I would expect the attribute to mean that the substituted direct access still applied the attribute you marked the API with. I would consider the fact that it doesn't work as a dangerous gap in the API.

It is also concerning if we consider how this will work with Property Behaviours that are planned for Swift in the future. If we made NSCopying a property behaviour, the direct access would mean it too would not be invoked at initial access so I'm not sure how the best way to get around this is - should we do compiler magic to copy in the initialiser, or should we warn if we don't detect a call to copy() or copy(with:) in the initialiser?

I think we should be doing the compiler magic to call copy(with:) in the initializer, because that seems like the most direct way to maintain the @NSCopying contract without changing the underlying direct-storage model.

I think we at least need to do something here. It's a very convoluted piece of logic to say the @NSCopying attribute doesn't work in an initialiser and it's hardly intuitive despite the fair reasoning.

I agree that we need to do something here. It feels like it’s just a bug—that this is the only way that @NSCopying makes sense in an attribute. Might even be a good starter bug for someone who wants to dip their tows into the type checker!

  - Doug

Rod

On 29 Jan 2017, at 4:47 pm, Torin Kwok via swift-evolution wrote:

Yep, I also admit the design of forbidding calling a setter before full
class initialization is reasonable and what's really annoying is the
inconsistency.

However, making @NSCopying attribute not subjects to the fact that
setters would not be invoked in initializers perhaps is viable too. In
the other words, assigning a value to a property whether or not by
calling a setter has no influence on whether @NSCopying semantic'd work:
copying should always take place after a property has been declared as
@NSCopying.

Jean-Daniel writes:

Le 28 janv. 2017 à 05:34, Torin Kwok via swift-evolution a écrit :

Hello guys,

Note: This issue has been originally presented inswift-usersmailling list . And then I post it again here at the suggestion of Jordan Rose:

It might be reasonable to change this behavior, but it probably deserves a bit of discussion on swift-evolution; it's not 100%, for-sure a bug.
--- the original content follows this line ---

I encountered a strange behavior when I declared a property with the @NSCopying attribute:

// `Person` class inherits from `NSObject` class and conforms to `NSCopying` protocol
@NSCopying var employee: Person
and then assigned an external instance of Person class protocol to this property within the designated init methods:

// Designated initializer of `Department` class
init( employee externalEmployee: Person ) {
self.employee = externalEmployee
super.init()

// Assertion would fail since Swift do not actually copy the value assigned to this property
// even though `self.employee` has been marked as `@NSCoyping`
// assert( self.employee !== externalEmployee )
}
If I indeed require the deep copying behavior during the init process, instead of taking advantage of @NSCopying attribute, I would have to invoke the copy() method manually:

init( employee externalEmployee: Person ) {
// ...
self.employee = externalEmployee.copy() as! Person
// ...
}
In fact, what really makes me confusing is that @NSCopying semantic does work properly within the other parts of the class definition such as normal instance methods, or external scope. For instance, if we're assigning an external instance of Person to the self.employee proper of Department directly through setter rather than initializer:

department.employee = johnAppleseed
then self.employee property and johnAppleseed variable will no longer share the same underlying object now. In the other words, @NSCopying attribute makes sense.

After I looked through a great deal of results given by Google, and dicussions on StackOverflow, I finally end up with nothing helpful — the vast majority of articles, documentations as well as issues talking about this similar topics only focus on the basic concepts and effects of @NSCopying itself but do not mentioned this strange behavior at all — besides one radar descriping the same problem (rdar://21383959 ) and a final conclusion mentioned in a guy's Gist comment: ... values set during initialization are not cloned ...

That is, @NSCopying semantic has no effect in initializers.

Then, what I want to figure out is the reason why @NSCopying semantic will become effectless implicitly whithin initializers of a class, and the special considerations behind this behavior, if any.

--- END ---

Jordan:

Your observation is correct: @NSCopying currently does not affect initializers. This is because accessing a property in an initializer always does direct access to the storage rather than going through the setter.
I have tested the identical logic in Objective-C and the NSCopying semantic works perfectly within Obj-C's class initializer.

This is because Obj-C guarantee that all ivars are zero initialized and does not enforce initializer safety (but forcing initialization of ivars before calling other methods).

Calling a setter (like any other method) before full class initialization is unsafe as the setter may be overridden or simply customized, and may need to access to the class or subclasses ivars.

That said, I’m not sure what is the best way to solve that inconsistency.

--
Torin Kwok (郭桐)
OpenPGP/GnuPG: https://keybase.io/kwok
_______________________________________________
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