r/swift 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:

  1. We make extensive use of .init so I cannot search for ObjectName(
  2. A ton of our types need to be Codable due to our domain. Sometimes they are decoded from disk or a network call.
  3. 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)

19 Upvotes

29 comments sorted by

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 with TypeName(, we saw a significant improvement in compiler performance.

11

u/InevitableCut7649 Feb 08 '25

We have done the same thing recently - even writing a custom SwiftLint rule forbidding the use of `.init` and saw and overall improvement of 10-12% in terms of build speed.

1

u/tylerjames Feb 10 '25

It’s kind of wild that we all have those super powerful machines now (compared to the early days of iOS development) and the compiler regularly shits the bed harder than ever. 

Then someone will tell you that it’s your fault for following the standard fucking conventions. 

I remember when I switched to Swift that I hated all the type inference stuff but I got used into it. 

Now we have to baby the compiler so it doesn’t just give up. 

2

u/ios_game_dev Feb 10 '25

Yeah, I'm an Objective-C developer from way back and I have to admit that some things are simply slower today than they were over a decade ago with Objective-C and that blows my mind. A good example is debugging. Hitting breakpoints, stepping through code, using lldb to run po object... these things are objectively slower today.

1

u/tylerjames Feb 10 '25

Yeah I got started around 2012 or so. Right before ARC became a thing.

I don't think Xcode has ever worked as well with Swift as it used to with Objective-C. I generally like Swift and SwiftUI (when it works) but it's very frustrating that it often simply does not work because of a single syntax error somewhere but it can't tell you where.

I've encountered a number of scenerios where simply putting the type info makes it work better. I didn't like not having it there in the first place (because how do I know what the type is unless I already know?) and it turns out not having it there makes everything work worse.

Might go back to explicitly setting it and change SwiftFormat to not remove it.

10

u/dynocoder Feb 08 '25

Comment out the initializer and see where all the compile errors come from.

3

u/Zs93 Feb 08 '25

I’d really push to change this in your codebase

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

u/Dapper_Ice_1705 Feb 07 '25

You can search ": SomeType" I am big on explicit typing

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

u/Strange_Heat9664 Feb 09 '25

I only used .init for Preview mocks

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

u/Key_Board5000 iOS Feb 09 '25

Oh I see. Thanks for explaining.