Rupert  Beatty

Rupert Beatty

1667657520

Inject: Hot Reloading for Swift Applications!

Inject

Hot reloading workflow helper that enables you to save hours of time each week, regardless if you are using UIKit, AppKit or SwiftUI.

TLDR: A single line of code change allows you to live code UIKit screen:

https://user-images.githubusercontent.com/26660989/161756368-b150bc25-b66f-4822-86ee-2e4aed713932.mp4

Read detailed article about this

The heavy lifting is done by the amazing InjectionForXcode. This library is just a thin wrapper to provide the best developer experience possible while requiring minimum effort.

I've been using it for years.

What is hot reloading?

Hot reloading is a technique allowing you to get rid of compiling your whole application and avoiding deploy/restart cycles as much as possible, all while allowing you to edit your running application code and see changes reflected as close as possible to real-time.

This makes you significantly more productive by reducing the time you spend waiting for apps to rebuild, restart, re-navigate to the previous location where you were in the app itself, re-produce the data you need.

This can save you literal hours off development time, each day!

Does it add manual overhead to my workflows?

Once you configured your project initially, it's practically free.

You don’t need to add conditional compilation or remove Inject code from your applications for production, it's already designed to behave as no-op inlined code that will get stripped by LLVM in non-debug builds.

Which means that you can enable it once per view and keep using it for years to come.

Integration

Initial project setup

To integrate Inject just add it as SPM dependency:

via Xcode

Open your project, click on File β†’ Swift Packages β†’ Add Package Dependency…, enter the repository url (https://github.com/krzysztofzablocki/Inject.git) and add the package product to your app target.

via SPM package.swift

dependencies: [
    .package(
      name: "Inject",
      url: "https://github.com/krzysztofzablocki/Inject.git",
      from: "1.0.5"
    )
]

via Cocoapods Podfile

pod 'Inject'

Individual Developer setup (once per machine)

If anyone in your project wants to use injection, they only need to:

  • You must add "-Xlinker -interposable" (without the double quotes) to the "Other Linker Flags" of all targets in your project for the Debug configuration (qualified by the simulator SDK to avoid complications with bitcode), refer to InjectionForXcode documentation if you run into any issues
  • Download newest version of Xcode Injection from it's GitHub Page
  • Unpack it and place under /Applications
  • Make sure that the Xcode version you are using to compile our projects is under the default location: /Applications/Xcode.app
  • Run the injection application
  • Select open project / open recent from it's menu and pick the right workspace file you are using

After choosing the project in Injection app, launch the app

  • If everything is configured correctly you should see similar log in the console:
πŸ’‰ InjectionIII connected /Users/merowing/work/SourceryPro/App.xcworkspace
πŸ’‰ Watching files under /Users/merowing/work/SourceryPro

Workflow integration

You can either add import Inject in individual files in your project or use @_exported import Inject in your project target to have it automatically available in all its files.

SwiftUI

Just 2 steps to enable injection in your SwiftUI Views

  • call .enableInjection() at the end of your body definition
  • add @ObserveInjection var inject to your view struct

Remember you don't need to remove this code when you are done, it's NO-OP in production builds.

If you want to see your changes in action, you can enable an optional Animation variable on Inject.animation that will be used when ever new source code is injected into your application.

Inject.animation = .interactiveSpring()

Using Inject is demoed in this example app

UIKit / AppKit

For standard imperative UI frameworks we need a way to clean-up state between code injection phases.

I create the concept of Hosts that work really well in that context, there are 2:

  • Inject.ViewControllerHost
  • Inject.ViewHost

How do we integrate this? We wrap the class we want to iterate on at the parent level, so we don’t modify the class we want to be injecting but we modify the parent callsite.

Eg. If you have a SplitViewController that creates PaneA and PaneB , and you want to iterate on layout/logic code in PaneA, you modify the callsite in SplitViewController:

paneA = Inject.ViewHost(
  PaneAView(whatever: arguments, you: want)
)

That is all the changes you need to do, your app now allows you to change anything in PaneAView except for its initialiser API and the changes will be almost immediately reflected in your App.

Make sure to call initializer inside Inject.ViewControllerHost(...) or Inject.ViewHost(...). Inject relies on @autoclosure to reload views when hot-reload happens. Example:

// WRONG
let viewController = YourViewController()
rootViewController.pushViewController(Inject.ViewControllerHost(viewController), animated: true)

// CORRECT
let viewController = Inject.ViewControllerHost(YourViewController())
rootViewController.pushViewController(viewController, animated: true)

Remember you don't need to remove this code when you are done, it's NO-OP in production builds.

iOS 12

You need to add -weak_framework SwiftUI to Other Linker Flags for iOS 12 to work.

The Composable Architecture

If like myself you love PointFree Composable Architecture, you’d probably want to inject reducer code, this isn’t possible in vanilla TCA because reducer code is a free function which isn’t as straightforward to replace with injection, but our fork at The Browser Company supports it.

Download Details:

Author: krzysztofzablocki
Source Code: https://github.com/krzysztofzablocki/Inject 
License: MIT license

#swift #ios #hot #reload #swiftui 

Inject: Hot Reloading for Swift Applications!

Postgres.jl: Postgres Database interface for The Julia Language

Postgres

Postgres Database Interface for the Julia language.

Basic Usage

julia> using Postgres
julia> conn = connect(PostgresServer, db="julia_test", host="localhost")
julia> #conn = connect(PostgresServer, "postgresql://localhost/julia_test")
julia> #empty strings will cause the server to use defaults.
julia> #connect(interface, user, db, host, passwd, port)
julia> #conn = connect(PostgresServer, "", "julia_test", "localhost", "", "")
julia> curs = cursor(conn)
julia> df = query(curs, "select 1 from generate_series(1,5) as s")
5x1 DataFrames.DataFrame
| Row | x1 |
|-----|----|
| 1   | 1  |
| 2   | 1  |
| 3   | 1  |
| 4   | 1  |
| 5   | 1  |

Iteration

Memory management is automatic for the cursor interface.

Buffered (Normal) Cursor

julia> execute(curs, "select 1 from generate_series(1, 10)")
julia> for res in curs; println(res); end;
10x1 DataFrames.DataFrame
| Row | x1 |
|-----|----|
| 1   | 1  |
| 2   | 1  |
| 3   | 1  |
| 4   | 1  |
| 5   | 1  |
| 6   | 1  |
| 7   | 1  |
| 8   | 1  |
| 9   | 1  |
| 10  | 1  |
julia> for res in curs; println(res); end;
# nothing (memory already freed from server)

Streamed (Paged) Cursor

julia> streamed = cursor(conn, 3)
julia> execute(streamed, "select 1 from generate_series(1, 10)")
julia> for res in streamed; println(res); end;
3x1 DataFrames.DataFrame
| Row | x1 |
|-----|----|
| 1   | 1  |
| 2   | 1  |
| 3   | 1  |
3x1 DataFrames.DataFrame
| Row | x1 |
|-----|----|
| 1   | 1  |
| 2   | 1  |
| 3   | 1  |
3x1 DataFrames.DataFrame
| Row | x1 |
|-----|----|
| 1   | 1  |
| 2   | 1  |
| 3   | 1  |
1x1 DataFrames.DataFrame
| Row | x1 |
|-----|----|
| 1   | 1  |
0x1 DataFrames.DataFrame

Each iteration allocs and frees memory.

Result Interface

Cursor must be closed (or unreachable) to release server resources.

julia> using Postgres.Results
julia> result = execute(curs, "select 1, null::int, 'HI'::text, 1.2::float8  
            from generate_series(1, 5)")
5x4{Int32, Int32, UTF8String, Float64} PostgresResult
julia> result[1,1]     # array
Nullable(1)

julia> result[1, :]    # row; also row(curs, 1)
4-element Array{Any,1}:
 Nullable(1)      
 Nullable{Int32}()
 Nullable("HI")   
 Nullable(1.2) 

# columns are a lot faster to create
julia> result[:, 1]    # columns; also column(curs, 1)
5-element DataArrays.DataArray{Int32,1}:
 1
 1
 1
 1
 1
#row iteration
julia> for row in result; println(row); end
Any[Nullable(1),Nullable{Int32}(),Nullable("HI"),Nullable(1.2)]
# ...
close(curs) # free postgres resources

Transactions

julia> begin_!(curs)
INFO: BEGIN 
julia> rollback!(curs)
INFO: ROLLBACK 
julia> commit!(curs)
WARNING: WARNING:  there is no transaction in progress
INFO: COMMIT 
# transaction already ended by rollback

Base Types supported as Julia Types:

julia> for v in values(Postgres.Types.base_types)
            println(v)
       end

text -> UTF8String
varchar -> UTF8String
bpchar -> UTF8String
unknown -> UTF8String
bit -> BitArray{1}
varbit -> BitArray{1}
bytea -> Array{UInt8,1}
bool -> Bool
int2 -> Int16
int4 -> Int32
int8 -> Int64
float4 -> Float32
float8 -> Float64
numeric -> BigFloat
date -> Date
json -> UTF8String
jsonb -> UTF8String

Others supported as UTF8String.

Extended Types

Automatically determined on connection start up.

julia> types = collect(values(conn.pgtypes))
julia> enum_test = filter(x->x.name==:enum_test, types)[1]
enum_test ∈ Set(UTF8String["happy","sad"])
# pg def:
# Schema β”‚   Name    β”‚ Internal name β”‚ Size β”‚ Elements β”‚
#────────┼───────────┼───────────────┼──────┼──────────┼
# public β”‚ enum_test β”‚ enum_test     β”‚ 4    β”‚ happy   ↡│
#        β”‚           β”‚               β”‚      β”‚ sad      β”‚

julia> domain_test = filter(x->x.name==:domain_test, types)[1]
(domain_test <: int4) -> Int32
# pg def:
# Schema β”‚    Name     β”‚  Type   β”‚ Modifier β”‚               Check                β”‚
#────────┼─────────────┼─────────┼──────────┼────────────────────────────────────┼
# public β”‚ domain_test β”‚ integer β”‚          β”‚ CHECK (VALUE >= 0 AND VALUE <= 10) β”‚

Enum types will use PooledDataArrays!

Escaping

julia> user_input="1';select 'powned';"
julia> escape_value(conn, user_input)
"'1'';select ''powned'';'"

Error Info

julia> try query(curs, "select xxx")
        catch err PostgresServerError
           println(err.info)
       end
PostgresResultInfo(
            msg:ERROR:  column "xxx" does not exist
LINE 1: select xxx
               ^
            severity:ERROR
            state:syntax_error_or_access_rule_violation
            code:42703
            primary:column "xxx" does not exist
            detail:
            hint:
            pos:8
)

see Appendix A. in the Postgres manual for error code/state lists.

Copy Support

# Commands use the same interface as selects.
# Messages are passed through to Julia as you are used to seeing them in psql.
julia> println(query(curs, """
    drop table if exists s; 
    drop table if exists news; 
    create table s as select 1 as ss from generate_series(1,10)"""))
NOTICE:  table "news" does not exist, skipping
INFO: SELECT 10 10
0x0 DataFrames.DataFrame

julia> df = query(curs, "select * from s")
julia> copyto(curs, df, "s")
INFO: COPY 10 10
0x0{} PostgresResult

julia> copyto(curs, df, "news", true)
INFO: table 'news' not found in database. creating ...
INFO: CREATE TABLE 
INFO: COPY 10 10
0x0{} PostgresResult

Custom Types

julia> using Postgres.Types

julia> type Point
        x::Float64
        y::Float64
       end

# find the oid (600 in this case) in the pg_type table in Postgres.
# Then instance the type.
julia> base_types[600] = PostgresType{Point}(:point, Point(0, 0))
point -> Point

# create the _in_ function from the database
julia> function Postgres.Types.unsafe_parse{T <: Point}(::PostgresType{T}, value::UTF8String)
    x, y = split(value, ",")
    x = parse(Float64, x[2:end])
    y = parse(Float64, y[1:end-1])
    Point(x, y)
end
unsafe_parse (generic function with 15 methods)

# create the _out_ function to the database
julia> Postgres.Types.PostgresValue{T <: Point}(val::T) =
    Postgres.Types.PostgresValue{T}(base_types[600], "($(val.x),$(val.y))")
Postgres.Types.PostgresValue

#reload conn so it picks up the new type
julia> close(conn)
PostgresConnection(@ 0 : not_connected)
julia> conn = connect(PostgresServer, db="julia_test", host="localhost")
PostgresConnection(@ 0x0b41b818 : ok)
julia> curs = cursor(conn)
Postgres.BufferedPostgresCursor(
    PostgresConnection(@ 0x0b41b818 : ok),
    Nullable{Postgres.Results.PostgresResult}())

julia> p1 = Point(1.1, 1.1)
Point(1.1,1.1)
julia> start = repr(PostgresValue(p1))
"'(1.1,1.1)'::point"
julia> p2 = query(curs, "select $start")[1][1]
Point(1.1,1.1)
julia> p1.x == p2.x && p1.y == p2.y
true

Control-C cancels the query at the server

julia> query(curs, "select 1 from generate_series(1, (10^9)::int)")
# oops; this will take forever
^CINFO: canceling statement due to user request
ERROR: PostgresError: No results to fetch
 in fetch at /home/xxx/.julia/v0.4/Postgres/src/postgres.jl:383
  in query at /home/xxx/.julia/v0.4/Postgres/src/postgres.jl:405

#no need to chase down zombie process with ps or top :) :)

Download Details:

Author: NCarson
Source Code: https://github.com/NCarson/Postgres.jl 
License: View license

#julia #postgres 

Postgres.jl: Postgres Database interface for The Julia Language

10 Steps to enabling auto-reload for Spring Boot in IntelliJ

Tired of stopping and rerunning your Spring Boot MVC project every time you make changes? Skip the entire rebuild with Spring Developer tools!

With a combination of adding spring-boot-devtools, configuring IntelliJ correctly, and installing a browser extension, working on MVC projects has never been easier.

For reference, here is the Spring Docs on enabling the automatic restart. Below is my experience setting it up and trying it out!

Here are 10 Steps to enabling auto-reload for Spring Boot in IntelliJ:

#reload #restart #mvc #spring-boot #devtools

10 Steps to enabling auto-reload for Spring Boot in IntelliJ