• Stars
    star
    303
  • Rank 137,655 (Top 3 %)
  • Language
    Swift
  • License
    Other
  • Created over 6 years ago
  • Updated almost 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

πŸ’‰ Vaccine - Make your apps immune to recompile-disease

Vaccine

CI Status Version Carthage Compatible License Platform Swift

Description

Vaccine Icon

Vaccine is a framework that aims to make your apps immune to recompile-disease. Vaccine provides a straightforward way to make your application ready for code injection, also known as hot reloading. It provides extensions on application delegates, NSObject and view controllers.

Before you go any further, make sure that you have InjectionIII installed and have understood the core concept for code injection and its limitations. For more information about InjectionIII, head over to https://github.com/johnno1962/InjectionIII.

Vaccine does not cut-out the need to ever recompile, but it opens up for faster iteration and seeing your application change instantly. There will be scenarios where you will have to recompile your application to see the changes appear. Worth noting is that code injection only works in the simulator and has no effect when running it on a device.

For additional information about how you can incorporate injection into your workflow, check out these articles:

Usage

The following examples are not meant to be best practices or the defacto way of doing code injection. The examples are based on personal experiences when working on projects that use InjectionIII.

Example project

The easiest way to try Vaccine with InjectionIII is to run the example project.
Follow the steps below:

  1. Install InjectionIII from the Mac App Store
  2. git clone [email protected]:zenangst/Vaccine.git
  3. Run pod install in Example/VaccineDemo/
  4. Open and run VaccineDemo.xcworkspace
  5. Select the demo project when InjectionIII wants you to select a folder.
  6. Start having fun 🀩

General tips

To get the most bang for the buck, your view controllers should be implemented with dependency injection, that way you can provide dummy material that is relevant to your current context. It works well when you want to try out different states of your user interface.

Loading the injection bundle

For InjectionIII to work, you need to load the bundle located inside the application bundle. You want to do this as early as possible, preferably as soon as your application is done launching.

// Loads the injection bundle and registers 
// for injection notifications using `injected` selector.
Injection.load(then: applicationDidLoad)
         .add(observer: self, with: #selector(injected(_:)))

Application delegate

To get the most out of code injection, you need be able to provide your application with a new instance of the class that you are injecting. A good point of entry for injecting code is to reinitialize your app at the application delegate level. It that increases the likely-hood of getting the desired effect of code injection as your root objects are recreated using the newly injected code. It also provides with a point of entry for displaying the target view controller(s) that you are modifying. So what it means in practice is that you can push or present the relevant view controller directly from your application delegate cutting out the need to manually recreating the view controller stack by manually navigating to the view controller you are editing. Working with InjectionIII is very similar to how playground-driven works, without having to wait for the playground to load or recompile your app as a framework.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

  var window: UIWindow?

  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
                   Injection.load(then: applicationDidLoad).add(observer: self,
                                                with: #selector(injected(_:)))
    return true
  }

  @objc open func injected(_ notification: Notification) {
    applicationDidLoad()
    // Add your view hierarchy creation here.
  }

  private func applicationDidLoad() {
    let window = UIWindow(frame: UIScreen.main.bounds)
    window.rootViewController = ViewController()
    window.makeKeyAndVisible()
    self.window = window
  }
}

When the code gets injected, applicationDidLoad will be invoked. It cleans and recreates the entire view hierarchy by creating a new window.

View controllers

Injecting view controllers is really where InjectionIII shines the most. Vaccine provides extensions to make this easy to setup and maintain. When injection notifications come in, Vaccine will filter out view controllers that do not fill the criteria for being reloaded. It checks if the current view controller belongs to a child view controller, if that turns out to be true, then it will reload the parent view controller to make sure that all necessary controllers are notified about the change.

Note Vaccine also supports adding injection using swizzling on views, view controllers and table and collection view data sources. This features is enabled by default but can be disabled by setting swizzling to false when loading the bundle.

Injection.load(then: ..., swizzling: false)

When injecting a view controller, the following things will happen:

  • Removes the current injection observer
  • Remove views and layers
  • Invokes viewDidLoad to correctly set up your view controller again
  • Invokes layout related methods on all available subviews of the controller's view.
  • Invoke sizeToFit on all views that haven't received a size

What you need to do in your view controllers is to listen to the incoming notifications and deregister when it is time to deallocate. Registering should be done in viewDidLoad as the observer will temporarily be removed during injection.

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()
    Injection.add(observer: self, with: #selector(injected(_:)))
    // Implementation goes here.
  }
}

When a view controller gets injected, it will invoke everything inside viewDidLoad, so any changes that you make to the controller should be rendered on screen.

Views

Injection views are similar to view controllers, except that they don't have a conventional method that you override to build your custom implementation. Usually, you do everything inside the initializer. To make your view injection friendly, you should move the implementation from the initializer into a separate method that you can call whenever that view's class is injected.

class CustomView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    addInjection(with: #selector(injected(_:)))
    loadView()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  private func loadView() {
    // Your code goes here.
  }

  @objc open func injected(_ notification: Notification) {
    loadView()
  }
}

If you enable swizzling when loading the injection bundle, then the initializer for all views will be switch out in order to evaluate if your view conforms to injection. It does this by checking if the view responds to the loadView selector. This removes the need to manual add injection related code into your views. Note that loadView needs @objc in order for injection to properly find and invoke the method when the view gets injected. Views that do not respond to the selector will be ignored.

class CustomView: UIView {
  override init(frame: CGRect) {
    super.init(frame: frame)
    loadView()
  }
  
  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  @objc private func loadView() {
    // Your code goes here.
  }
}

If you feel like this is a lot of code to write for all views that you create, I recommend creating an Xcode template for creating views.

Auto layout constraints

Adding additional constraints can quickly land you in a situation where your layout constraints are ambiguous. One way to tackle this issue is to gather all your views constraints into an array, and at the top of your setup method, you simply set these constraints to be deactivated. That way you can add additional constraints by continuing to inject, and the latest pair are the only ones that will be active and in use.

When using swizzling, the framework will try and resolve the layoutConstraints from your view and deactivate them in order to avoid conflict with any new constraints that you may apply in your loadView() method. Which means that you can remove the call to NSLayoutConstraint to deactivate the current constraints.

Note: Using layoutConstraints is optional, if your view does not use stored constraints, then Vaccine will recursively deactivate all constraints on all of its subviews when the view gets injected.

class CustomView: UIView {
  private var layoutConstraints = [NSLayoutConstraint]()
  
  private func loadView() {
    NSLayoutConstraint.deactivate(layoutConstraints)
    // Your code goes here.
  }
}

Installation

Vaccine is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Vaccine'

Vaccine is also available through Carthage. To install just write into your Cartfile:

github "zenangst/Vaccine"

Vaccine can also be installed manually. Just download and drop Sources folders in your project.

Author

Christoffer Winterkvist, [email protected]

Credits

Contributing

We would love you to contribute to Vaccine, check the CONTRIBUTING file for more info.

License

Vaccine is available under the MIT license. See the LICENSE file for more info.

More Repositories

1

Hue

🎨 Hue is the all-in-one coloring utility that you'll ever need.
Swift
3,463
star
2

Gray

πŸŒ“ Tailor your macOS Mojave experience
Swift
1,325
star
3

Spots

🎍 Spots is a cross-platform view controller framework for building component-based UIs
Swift
1,314
star
4

Blueprints

πŸŒ€ Blueprints - A framework that is meant to make your life easier when working with collection view flow layouts.
Swift
990
star
5

KeyboardCowboy

⌨️ The missing keyboard shortcut utility for macOS
Swift
740
star
6

Syncalicious

🍫 Syncalicious
Swift
356
star
7

Family

🚸 A child view controller framework that makes setting up your parent controllers as easy as pie.
Swift
250
star
8

Tailor

πŸ‘”A super fast & convenient object mapper tailored for your needs
Swift
242
star
9

Versions

❇️Helping you find inner peace when comparing version numbers in Swift.
Swift
208
star
10

MarvinXcode

πŸ”¨A collection of nifty commands for your everyday workflow in Xcode
Swift
125
star
11

Differific

β›½ Differific - a fast and convenient diffing framework.
Swift
124
star
12

Coda-2-Modes

Modes for Coda 2
AppleScript
61
star
13

UserInterface

πŸš₯ UserInterface - a collection of convenience extensions specifically tailored to building user interfaces in Swift.
Swift
51
star
14

ToTheTop

πŸ”To the top - A small macOS application to help you scroll to the top.
Swift
36
star
15

Zcode

Work around Apples restriction with running Xcode 6.4 on El Capitan Developer Preview 2
Swift
25
star
16

NSString-ZENInflections

Returns camelCased, UpperCamelCased, dashed-case, snake_cased representations of an NSString
Objective-C
25
star
17

Inflection

The Optimus Prime of string inflection
Swift
22
star
18

MouseDef

🐭Move and resize windows by holding down modifier keys
Swift
21
star
19

ChangeMarks

Change Marks helps you to keep track of your most recent changes by giving them a different background color.
Objective-C
14
star
20

OSX-Configuration

My personal OS X configurations
Shell
10
star
21

ZenCode

πŸ”¨ZenCode for Xcode - A collection of nifty commands for your everyday workflow in Xcode.
Swift
9
star
22

Apps

Swift
7
star
23

Voodoo

πŸ’€ Voodoo is a set of Sourcery templates to make you do more with less.
Swift
7
star
24

Houston

Swift
6
star
25

WindowFlex

Helps you flex your Xcode window muscles
Objective-C
6
star
26

Storage

Swift
5
star
27

AXEssibility

Swift
5
star
28

Mapper

An object mapper for Swift
Swift
4
star
29

Marvin-Xcode-Extension

πŸ”¨A collection of nifty commands for your everyday workflow in Xcode 8
Swift
4
star
30

Xcode-Templates

πŸ”¨ Xcode templates
Swift
4
star
31

Goldfish

Something something secret ... goldfish.
Swift
3
star
32

TextMate-1-Bundle

Commands and Snippets I use everyday at work
3
star
33

coda-command-line

Coda 2 command line tool
Objective-C
3
star
34

XcodeConfiguration

3
star
35

C.mode

C mode for Coda 2
AppleScript
3
star
36

DirectoryObserver

Observing the file-system, easy as 1 2 3!
Swift
3
star
37

GoldenRetriever

Swift
2
star
38

dotfiles

My dotfiles
Vim Script
2
star
39

xcode-fish

Find and open xcworkspace or xcproject in Xcode.
Shell
2
star
40

LaunchArguments

Swift
2
star
41

xcode-espresso-tribute-theme

A light theme inspired by the Espresso - The Web Editor
2
star
42

xcode-ios-project

Ruby
2
star
43

MachPort

Swift
2
star
44

ZENFramework

Just another PHP MVC framework
PHP
2
star
45

HideDockAndStageManager

Swift
2
star
46

Windows

Swift
2
star
47

NSString-ZENVersions

Helping you find inner peace when comparing version numbers.
Objective-C
2
star
48

ApacheConf.mode

Apache configuration mode for Coda 2
1
star
49

theme-oh-my-sushi

oh-my-sushi
Shell
1
star
50

SidebarFlex

Objective-C
1
star
51

Retina

Swift
1
star
52

KeyCodes

Swift
1
star
53

StageManagerAppWindowGroupingBehavior

Swift
1
star
54

Swift.mode

1
star
55

Ruby.mode

Ruby mode for Coda 2
AppleScript
1
star
56

DockManager

Swift
1
star
57

xc

Swift
1
star
58

haml.mode

HAML mode for Coda 2
1
star
59

ERB.mode

Ruby ERB mode for Coda 2
AppleScript
1
star
60

emolator

TBD
1
star
61

DefaultKeyBinding

Custom keyboard shortcuts for OS X
1
star