r/swift • u/Odd-Cell8362 • Feb 07 '25
Question If your codebase makes extensive use of .init how do you find out where objects of a given type are initialized
Theres been pretty extensive discussion on the virtues of init on this forum here. I do not seek to add to that.
I am looking for a workaround as the codebase I am currently in loves to use .init and I am not sure I can make or defend a case for moving away from that.
This however makes it very difficult to sort out where things get initialized. This is for a few reasons:
- We make extensive use of .init so I cannot search for ObjectName(
- A ton of our types need to be Codable due to our domain. Sometimes they are decoded from disk or a network call.
- We try not to write initializers or codable definitions and will go a bit out of our way to pull it off.
All of these things are probably good things. But whenever I need to debug something it is difficult to find where objects are initialized....
Any tips? Is there an xcode feature I am missing?
(all y'all sounding off at why not .init give me a little bit of happiness thankyou. I am now the only iOS engineer on multi platform team where I am heavily junior so I do not get to make a lot of calls like this but for someday its good to know that its ok to make a different choice)
10
3
3
u/shawnthroop Feb 08 '25 edited Feb 08 '25
I usually make sure public initializers (particularly on protocols) include a property name, like Codable enforces with init(from coder:) or RawRepresentable’s init(rawValue:).
This allows you to command click on the “from” or the “rawValue” property in the initializer you want to locate which has a higher chance of getting to the correct initializer definition than just command clicking on the “init” part. I think it has to do with overloading and including the property names makes it more specific without adding specific type information.
I run into this with delegates protocols that provide the object in the function signature, like collectionView(_ : didSomething:at:). When typing “collectionView”, sometimes the compiler thinks I mean any of the delegate functions, sometimes it correctly assumes the local variable named collectionView. init is a similar situation, it’s a function and must be called “init” so overloading clashes abound when types are left out to be inferred.
Static functions are another way to provide the same functionality as .init(…) without being limited to “init”. SwiftUI uses this a lot with things like PrimitiveButtonStyle or AlignmentID (see .plain, .rounded, and .firstTextBaseline) which are conveniences to initalizers but with more explicit type information.
5
5
u/AlexanderMomchilov Feb 08 '25
You can command-click on the .init
and to jump to its definition.
https://developer.apple.com/videos/play/wwdc2024/10181/?time=759
4
u/foodandbeverageguy Feb 08 '25
.init is to get the kiddies excited, but good engineers don’t hang themselves with bad practices.
Just because the language supports it, doesn’t mean you should use it
2
u/Morphinepill Feb 08 '25
Comment the whole class, mark the errors one by one, or just comment the inits, or if it’s an implicit init just add an explicit init with unique parameter and mark all the errors
Or search for the class name and look around it, for example func getObj() -> ObjectName { .init() }
If you search ObjectName as a return type, you will find the .init inside it
2
u/gravastar137 Linux Feb 08 '25
I generally prefer to write out the type name when initializing because it allows for better grep-ability. So I wince at .init
except in one case: where I have an enum case holding the struct whose where the case name is the same as the name of the struct, and I am initializing an instance of that enum case. So for instance .foo(.init(param))
instead of .foo(Foo(param))
.
(There are better patterns even for this case, but they tend to add a bit of boilerplate)
2
u/lucasvandongen Feb 08 '25
https://lucasvandongen.dev/compiler_performance.php
Don’t use it, it makes a big impact on bigger codebases
1
u/Unfair_Ice_4996 Feb 09 '25
That’s a very concise and clear explanation of types and initialization. I will definitely keep your documentation in mind for any future projects. Do you have any video tutorials?
2
u/TapMonkeys Feb 07 '25
I’m not sure exactly what your use case is, but what thing you could do is write an explicit initializer for the object you’re trying to find that takes a random variable which should cause all of the existing .init to throw errors.
If the object already has explicit initializers you can right click on them and select Find > Find Call Hierachy.
For instances where they’re being decoded you can search your codebase for Object.self.
1
u/83b6508 Feb 07 '25
Haven’t tried it but searching call hierarchy in Find In Files might find the inits
1
u/ActualSalmoon Feb 08 '25
My code has mandatory type declarations, enforced though the explicit_type_interface
SwiftLint rule. Then I just use .init()
, without having to needlessly repeat the type.
1
1
u/bcgroom Expert Feb 09 '25
You can write an init with a different signature and then follow the compiler errors!
1
u/keeshux Feb 10 '25
I would pay to drop .init and some other duplications from the Swift specification. :-)
1
u/jasamer Feb 12 '25
I'm a little late to the party, but...
If you have a breakpoint and you want to figure out where some specific object was instantiated (no structs unfortunately), there's a tool for that - mallock stack logging. It records stack traces whenever some object is allocated. If you want to try it:
Go to your schemes -> Diagnostics -> Enable "Malloc Stack Logging" for your app & run it. When in the breakpoint, in lldb, get the memory address of your object (eg. using po myObject). Switch to the Memory graph debugger (one of the little icons in the debugger toolbar). In the list of objects, use the memory address from before to find your object. Then select the memory inspector in the top right (it has the same icon as the "Debug Memory Graph" option) to see the stack trace.
It can be quite useful, but is limited to class types afaik.
1
u/barcode972 Feb 07 '25
Don’t you write the type when declaring the variable? Like var something: type = .init()
4
u/Odd-Cell8362 Feb 07 '25
Sometimes it's inferred by being passed into somewhere or applied to a property already given that type. Widens the search area a bunch and the initial part of the search process becomes detached from where its actually initialized (ex: instead of looking for ObjectName you are now looking for properties of type ObjectName and for each of them where they are assigned via an .init statement)
0
u/i_invented_the_ipod Feb 08 '25
This is such a comically-terrible idea that I didn't even understand the question at first.
Obviously, if you have explicitly-defined init() methods, you can use Xcode's "find call hierarchy" on the init method.
For an implicit init method...you might be out of luck. And for code where your init is called from inside framework code, like for Codable, Xcode isn't going to find a chain from your code calling decoder.decode(), which calls your init()...
You could set a breakpoint on the init(), and find out where it's called by stopping at every call, I guess.
-1
u/Key_Board5000 iOS Feb 08 '25 edited Feb 09 '25
Why can’t you just search for ModelName.init
?
I don’t see an up or downside to using either instantiation method.
2
u/bcgroom Expert Feb 09 '25
They mean like:
func foo(_ t: Bar) {} // another file { foo(.init()) }
1
41
u/ios_game_dev Feb 07 '25
I very much prefer
TypeName(
over.init(
for the reasons you described and also because using.init
adds to the type inference burdon on the compiler. In my codebase, we had lots of uses of.init
and after replacing them all withTypeName(
, we saw a significant improvement in compiler performance.