Vaccine - Make your apps immune to recompile-disease

Overview

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.

Comments
  • Using in a MacOs Project

    Using in a MacOs Project

    Hi there,

    I tried running Vaccine with a MacOS and got the following exception when building:

    ~/../Pods/Vaccine/Source/Shared/CollectionView+Extensions.swift:13:25: Use of unresolved identifier 'UICollectionView'
    
    Swizzling.swizzle(UICollectionView.self, // <----- Use of unresolved identifier 'UICollectionView'
                            originalSelector: originalSelector,
                            swizzledSelector: swizzledSelector)
    

    At the top of the file, there is a compiler directive like this:

    #if os(macOS)
    import Cocoa
    #else
    import UIKit
    #
    

    But UICollectionView is only part of UIKit.

    It seems like CollectionView is aliased to be NSCollectionView on mac and UICollectionView on ios. Would it make sense to use CollectionView.self here?

    opened by yanshiyason 4
  • Injection always says it is already loaded

    Injection always says it is already loaded

    Hi,

    I'm trying this library, but when I load it, it always says that the injection in already loaded.

    The problem is that my main bundle path is like this: /users/username/library/developer/coresimulator/devices/****/data/containers/bundle/application/****/appname.app and this line is always false: https://github.com/zenangst/Vaccine/blob/b529121ab3034c4156e023e20f08f3e473050feb/Source/Shared/Injection.swift#L89

    If I remove this check, everything goes well.

    Is this the intended result?

    Many thanks

    Environment: Mojave 10.14.3 (18D109) Xcode 10.1 (10B61)

    opened by eduardbosch 2
  • Removing the observer is not necessary

    Removing the observer is not necessary

    Hi!

    According to the documentation for NotificationCenter.removeObserver:

    If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.

    Since this framework's deployment target is iOS 9.2 & macOS 10.11, I believe there is no need to remove observers as NotificationCenter is only holding weak references to them.

    Removing the need to call Injection.remove(self) in deinit should make it easier for the user.

    opened by inket 2
  • Improve using Vaccine with nibs/xibs

    Improve using Vaccine with nibs/xibs

    • Refactors how view controllers are swizzled, it now shares the implemention with macOS
    • Use subviewsRecursive() to look for table and collection views
    • Add matching on cell when evaluating if a view controller should respond to injection
    • Add method for handling nib-based view controllers
    opened by zenangst 1
  • Improve removing constraints from views

    Improve removing constraints from views

    Refactors how constraints are removed from when views are injected. It makes a special case to take care of UITableViewCell's because it has a contentView that should act as the root view.

    opened by zenangst 1
  • Implement support for animated injections on macOS

    Implement support for animated injections on macOS

    Implement animations when injecting view controllers and collection view items on macOS. It syncs the source code between macOS, iOS, and tvOS. Also, it improves injecting collection view items on the macOS platform by going a different path when those kinds of view controllers are injected.

    opened by zenangst 1
  • Improve/injection with xcode10

    Improve/injection with xcode10

    • Adds dirty view controller matching as a fallback if view controller cannot be determined using type checking
    • Remove .bundle from pattern check to see if injection is already loaded.
    opened by zenangst 1
  • Feature layout constraints deactivation by convention

    Feature layout constraints deactivation by convention

    • Improves the load signature by adding then label for the closure. It improves readability at the call-site.
    • Swizzling and animations are now defaults set to true when loading the injection bundle.
    • When a view is injected, .layoutConstraints will be deactivated before loadView is invoked. This saves you from adding it to your code-base.
    • Updates the README to include the latest changes to the framework.

    Loading the application bundle now looks like this:

    Injection.load(then: loadApp)
    
    opened by zenangst 1
  • Improve the search pattern in isLoaded property

    Improve the search pattern in isLoaded property

    Injection.isLoaded is now more precise as it includes .bundle in the range that is searches for. Before this change, it might skip to load the bundle if any other bundle included injection in its name.

    opened by zenangst 1
  • Adds view injection via swizzling

    Adds view injection via swizzling

    Adds view injection via swizzling. If swizzling is enabled when loading the bundle. The initializers for views will now be swizzled to add injection via convention. If you implement @objc func loadView() on your views. Injection will be added to run that method when the view gets injected.

    opened by zenangst 1
  • Fix Vaccine demo

    Fix Vaccine demo

    This updates the Vaccine demo to use the new signature for checking if the current object was injected. Fixes https://github.com/zenangst/Vaccine/issues/20

    opened by zenangst 1
  • Crash when reload

    Crash when reload

    I'm using a Child controller inserted in a view controller with all code needed (vc.addChild() (...)) I'd added the Injection.addViewController(self) command in viewDidLoad()

    When i'm using injection, the app crash with

    Thread 1: "child view controller:<xzy.TestTable: 0x7fb5cfc8d650> should have parent view controller:<UINavigationController: 0x7fb5d806fc00> but actual parent is:<xzy.VCAddServiceStep2: 0x7fb5f0043c00>" 
    

    I don't understand this error message because the child is added on VCAddServiceStep2 and not on a UINavigationController

    Any idea to fix this crash ?

    opened by skrew 3
  • Could not locate compile command

    Could not locate compile command

    Hello, when running the demo app, I'm getting this error:

    Could not locate compile command for /Users/xxxxx/Desktop/Vaccine/Example/VaccineDemo/VaccineDemo/Sources/Controllers/ListViewController.swift.
    This could be due to one of the following:
    1. Injection does not work with Whole Module Optimization.
    2. There are restrictions on characters allowed in paths.
    3. File paths in the simulator paths are case sensitive.
    Try a build clean then rebuild to make logs available.
    

    I've tried cleaning and rebuilding multiple times.

    I'm on macOS BigSur version 11.1 (20C69), XCode version 12.2 (12B45b), and have tried InjectionIII install from App Store as well as from the InjectionIII github repo as its Readme suggested I do so for macOS BigSur.

    opened by himeshmyk 0
Releases(0.15.1)
Owner
Christoffer Winterkvist
random hero at @finkoslo by day, cocoa vigilante by night, dad at dawn. my life is awesome. Previously @hyperoslo
Christoffer Winterkvist
This app is a sample app that recognizes specific voice commands such as "make it red", "make it blue", "make it green", and "make it black" and change the background color of the view in the frame.

VoiceOperationSample This app is a sample app that recognizes specific voice commands such as "make it red", "make it blue", "make it green", and "mak

Takuya Aso 3 Dec 3, 2021
Will Powell 1.2k Dec 29, 2022
Disease diagnosis service based on api linkage for education chatbot provided by Saltlux

AIDoctor-P-Project κ°€μ²œλŒ€ν•™κ΅ 2021-2ν•™κΈ° Pν”„λ‘œμ νŠΈμž…λ‹ˆλ‹€. μ†”νŠΈλ£©μŠ€μ—μ„œ 제곡된 ꡐ윑용 챗봇 api 연동을 기반으둜 ν•œ μ§ˆλ³‘ 진단 μ„œλΉ„μŠ€ Splash Login UserMain Disease Detail ### Hospital Detail ChatBo

λ³΄λ‹ˆ 0 Dec 18, 2021
PJAlertView - This library is to make your own custom alert views to match your apps look and feel

PJAlertView - This library is to make your own custom alert views to match your apps look and feel

prajeet 6 Nov 10, 2017
A simple library to make authenticating tvOS apps easy via their iOS counterparts.

Voucher The new Apple TV is amazing but the keyboard input leaves a lot to be desired. Instead of making your users type credentials into their TV, yo

Riz 516 Nov 24, 2022
Bolts is a collection of low-level libraries designed to make developing mobile apps easier.

Bolts Bolts is a collection of low-level libraries designed to make developing mobile apps easier. Bolts was designed by Parse and Facebook for our ow

null 5.7k Dec 29, 2022
Bolts is a collection of low-level libraries designed to make developing mobile apps easier.

Bolts in Swift Bolts is a collection of low-level libraries designed to make developing mobile apps easier. Bolts was designed by Parse and Facebook f

null 1.3k Nov 11, 2022
A simple library to make authenticating tvOS apps easy via their iOS counterparts.

Voucher The new Apple TV is amazing but the keyboard input leaves a lot to be desired. Instead of making your users type credentials into their TV, yo

Riz 516 Nov 24, 2022
Tools and helpers to make building apps faster and safer.

The UBFoundation framework provides a set of useful tools and helpers to make building apps faster and safer.

Ubique 7 Dec 19, 2022
The demo project to show how to organize code to make SwiftUI apps easy to be test.

TestableApp I combined the idea to use functional programming instead of an loader instance in ModelView(I prefer to think of it as a service) and Res

VictorK 2 Jan 7, 2022
Approov Integration Examples 0 Jan 26, 2022
Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux.

Katana is a modern Swift framework for writing iOS applications' business logic that are testable and easy to reason about. Katana is strongly inspire

Bending Spoons 2.2k Jan 1, 2023
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms

SwiftUI-compatible framework for building browser apps with WebAssembly At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

TokamakUI 2k Dec 30, 2022
SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms

SwiftUI-compatible framework for building browser apps with WebAssembly At the moment Tokamak implements a very basic subset of SwiftUI. Its DOM rende

TokamakUI 2k Dec 29, 2022
Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux.

Katana is a modern Swift framework for writing iOS applications' business logic that are testable and easy to reason about. Katana is strongly inspire

Bending Spoons 2.2k Dec 17, 2022
Android/iOS Apps created to practice with different iOS/Android Tech. These apps were built to have similar feature sets using native Android/iOS.

AgilityFitTodayApp Android/iOS Apps created to practice with different iOS/Android Tech. These apps were built to have similar feature sets using nati

Lauren Yew 1 Feb 25, 2022
Stub your network requests easily! Test your apps with fake network data and custom response time, response code and headers!

OHHTTPStubs OHHTTPStubs is a library designed to stub your network requests very easily. It can help you: test your apps with fake network data (stubb

Olivier Halligon 4.9k Dec 29, 2022
Twinkle is a Swift and easy way to make any UIView in your iOS or tvOS app twinkle.

Twinkle ✨ Twinkle is a Swift and easy way to make any UIView in your iOS or tvOS app twinkle. This library creates several CAEmitterLayers and animate

patrick piemonte 600 Nov 24, 2022
Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts.

Blueprints is a collection of flow layouts that is meant to make your life easier when working with collection view flow layouts. It comes

Christoffer Winterkvist 982 Dec 7, 2022
SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them type-safe to use.

SwiftGen is a tool to automatically generate Swift code for resources of your projects (like images, localised strings, etc), to make them type-safe to use.

null 8.3k Jan 5, 2023