Skip to content

Latest commit

Β 

History

History
314 lines (179 loc) Β· 10.8 KB

Building-Faster-in-Xcode.md

File metadata and controls

314 lines (179 loc) Β· 10.8 KB

Building Faster in Xcode

πŸ“… 2019.12.17 (TUE) WWDC2018 | Session : 408 | Category : Performance

πŸ”— Building Faster in Xcode - WWDC 2018 - Videos - Apple Developer

Parallelizing Your Build

Increasing build efficiency

Xcode's Target and Dependencies

Xcode configure your projects through the use of targets. And targets specify the output or the product that you would like to build

Target specifies a product to build

  • iOS App
  • Framework
  • Unit Tests

Target dependency requires another target

  • Explicit via Target Dependencies
  • Implicit via Link Binary with Libraries

Each of these targets are building in in order, sequentially. And they each have to wait until the previous target is done building.

β†’ Waste of potential hardware utilization. Waste of time as a developer.

Parallelized Build Timeline

How do we get from the long, serialized build timeline to the better parallelized build time?

Scheme Editor β†’ Edit Scheme β†’ Build Action β†’ Build Options β†’ Parallelize Build

Examine Tests Dependencies

"Do Everything'

This testing way too many components. It's better to simply break up our tests so that it's testing each individual component.

They can built as soon as their respective components are done,


Reduce Dependency Exposure

"Nosy Neighbors"

Shaders taraget produces a meta library, which is essentially just a bundle of GPU code that's going to run on our graphics card.

And Utitilies target just produces a normal frame, which is just CPU code

So there's already a little bit of suspect depedency here.

Utitilites target actually has a build phase in it that's genrating some inforation that's used by both targets.

So it's best to break out that into its own target. And we're going to see that this small incremental change actually has a large and significant impact on our overall build timeline.

So new green box that just moved in is our new code target.

So we are able to shrink our utilities target down because we moved that work into Code Gen.

And since Code Gen has no other dependencies, it can move to the very front of build process.

It can also be built in parallel with our Physics target, which is the red box on the bottom.


Examine Unused Dependencies

"Forgotten Ones"

Throughout the evolution or the lifecycle of our products and our code, we tend to move code around and delete things. And we get things like dead code.

We get the same thing that happens with our dependencies. Sometimes we simply forget to clean them up.

So in these cases, it's actually safe to just remove that dependency

And this last change tightens up or build graph even further by allowing the Utilities target to be built right after the CodeGen target instead of having to wait for all of the Physics target to be done.

πŸ†• Parallelized Target Build Process

Run Script Phases

Reducing the work on rebuilds

Run script phases allow you to customize your build process to meet your needs.

β†’ Allows you to customize your build process for your exact needs.

Build Phases

  • Script Body

You can either put your entire script contents here or reference another script that's within your project.

Throughout the entirely of your run script phase, there are a set of build settings that are available to you. β†’ Source group.

This gives you a convenient way to not have to provide absolute paths or try to do some relative path hacks to get your stuff work

  • Input Files

As this is one of the key pieces of information that the Xcode Build System will use to determine if your run scripts should actually run or not.

So this should include any file that your run script phase, the script content, is actually going to read or look at during its process.

  • File Lists

Some of you may have a lot of inputs into your run script phase. So this task might seem a little bit daunting. Xcode 10 provide you the ability to maintain this list in an external file.

  • Output Files

Output files are another key piece of information that's used throughout your build process. Xcode will use this information to determine if your run script phase actually needs to run.

  • Output File Lists

When the Run Script Phase if Run

  • No input files declared Xcode build system will need to run to your run script in every single build It's important to declare your inputs
  • Input files changed
  • Output files missing

Source-Level Improvements

Remove this build time workaround... Reducing the work on rebuilds

Previous version of Xcode, for some projects, turning on the Whole Module Compilation mode, even for debug builds, produced a faster overall build than when used in Default Incremental Modes.

And this improve build time because Swift's compiler was able to share work across files in a way that the Incremental Mode was not.

But is also meant you were giving up your incremental builds and wold rebuild the entire target's worth of Swift files every time.

In Xcode 10, the incremental build improved to have some of that same work sharing across files. So you should no longer need to use Whole Module mode to get good build times.

"When a build takes a long time, there is often a key piece of information yo u can add to make it better."

Dealing with complex expressions

Increasing build efficiency

Provide Types in Complex Closoures

    func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? { 
    	return [i, j, k].reduce(0) {
    		soFar, next in
    
    
    	}
    }

If you have a closure with a single expression in its body, then the compiler will use that expression to help determine the type of the closure.

Sometimes this is really convenient, other times it can lead to code like this.

    func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? { 
    	return [i, j, k].reduce(0) {
    		(soFar: Int?, next: Int) -> in
    		soFar != nil && next != nil ? soFar! + next! :
    			(soFar != nil ? soFar! : (next != nil ? next! : nil)) 
    		}
    }

this expression is getting so large, with so many independent pieces, the Swift Compiler will report that it's not able to compile this in reaonable amount of time.

error: the compiler is unable to type-check this expression in reasonable time

Break Apart Complex Expressions

    func sumNonOptional(i: Int?, j: Int?, k: Int?) -> Int? {
    	return [i, j, k].reduced(0) {
    		soFar, next in
    		if let soFar = soFar {
    			if let next = next { return soFar + next }
    			return soFar
    		} else {
    			return next
    		}	
    	}
    }

We already know that this callback for reduce is going to be operating just on optional integers, there's no need to put single expression in that closure. And it's perfectly okay to break it up into separate, more readable staements.

Use AnyObject Methods and Properties Sparingly

AnyObject is convenient type that describes any class instance.

If you try to call a method or access a property on a value of type AnyObject, Swift will allow you to do so, as long as that method is visible somewhere in you project.

However this does come at a cost. Because compiler doen't know which method you're going to call, it has to go search out any possible implementations throughout your project and the frameworks you import and assume that they might be the none that's going to be used.

Define a protocol instead

    weak var delegate: MyOperationDelegate?
    func reportSuccess() {
    	delegate?.myOperationDidSucceed(self{
    }
    
    protocol MyOperationDelegate: class {
    	func myOperationDidSucceed(_ operation: MyOperation)
    }

UnderStanding Dependencies in Swift

Reducing the work on rebuilds

Swift Dependency Rules

  • Complier must be conservative
  • Changes in function bodies do not affect the file's interface
  • Dependencies within a module are per-file
  • Dependencies across targets are for the whole target

Limiting Your Obejctive-C/Swift

Reducing the work on rebuilds

If we can shrink the content in these header, then we know that there's fewer chances for things to change, therefore less need to rebuild.

If you don't need to expose these interacting with Interface Builder, markk these private and watch as property and method vanish from the generated header.

When you migrate so Swift 4 to keep on a rule fro Swift 3 which eposes internal methods and properies to Objetive-C automatically on any subclass of NSObject/

This will only be inferred for methods and properties that satisfy protocol requirements or those that override methods that com from Objective-C.

This means that if any of these headers change, the Swift cod in your target has to be recompile because it might depend on something that changed.

β†’ Use categories to break up your interface

Summary

Related: Building Your App with Xcode