VNet: Use NSObject instead of C struct in XPC message #44868
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Edoardo asked if we need to worry about the size of the C struct that's passed in the XPC message between the client and the daemon service (#44751 (comment)). This might be the case if the client uses a different version of tsh where the struct happens to have, say, new fields. It didn't occur to me to check this. But after a quick test it turned out that XPC is smart enough to reject messages where the size of the struct doesn't match (check the linked comment for more details).
However, this also means that instead of getting a clear error on the daemon side when incompatible tsh versions talk to each other, we'll see the error only when running Console.app. To make sure this doesn't happen in the future, I replaced the C struct with an
NSObject
, which by necessity must implementNSSecureCoding
(docs forNSSecureCoding
).To that end, now that I know a little more about properties in Obj-C, I also cleaned up how they're defined and accessed.
A quick primer on properties in Obj-C
Each property is backed by an instance variable, automatically created by the compiler when you declare a property through
@property …
inside@interface
. This is similar to any other object-oriented language where if you want to expose an instance variable you define some kind of a getter (and perhaps a setter).@property foo
defines an ivar_foo
.By default, properties have the following attributes:
atomic
: "Property atomicity is not synonymous with an object’s thread safety". As far as I understand it, a setter for example can perform multiple operations on the underlying ivar and this attribute makes sure that the ivar doesn't change for the duration of the setter.readwrite
: both accessors are generated for the property, unlikereadonly
which creates only the getter.strong
: uses a strong reference for the ivar. It has to do with reference counting, the linked docs includes an explanation, I also found a pretty entertaining one using balloons to demonstrate the concept.As far as I understand, this has changed over time, e.g. before Arc
strong
wasn't the default attribute. The code I based the implementation of the launch daemon on is also pretty old, as well as half of the SO answers you can find about Obj-C. As such, I ended up including a bunch of cruft that can be removed. A couple of examples:nonatomic
if they know they don't have to worry about thread safety because it makes accessing properties "faster". But we only ever send a single message, so we don't have to worry about it at all.copy
forNSString
properties in caseNSMutableString
is provided to the setter. But this doesn't matter if the property isreadonly
since there's no setter.[[foo bar] baz]
can be justfoo.bar.baz
or[foo.bar baz]
ifbaz
is a method call.@implementation
blocks was added circa 2012, so people used to add properties because there was no other choice. But a property is needed only if we need to make the ivar public. Here's an example of defining private ivars inside@implementation
:teleport/lib/vnet/daemon/service_darwin.m
Lines 46 to 49 in b03f33c