1667657520
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:
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.
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!
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
To integrate Inject
just add it as SPM dependency:
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.
dependencies: [
.package(
name: "Inject",
url: "https://github.com/krzysztofzablocki/Inject.git",
from: "1.0.5"
)
]
pod 'Inject'
If anyone in your project wants to use injection, they only need to:
/Applications
/Applications/Xcode.app
After choosing the project in Injection app, launch the app
π InjectionIII connected /Users/merowing/work/SourceryPro/App.xcworkspace
π Watching files under /Users/merowing/work/SourceryPro
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.
Just 2 steps to enable injection in your SwiftUI
Views
.enableInjection()
at the end of your body definition@ObserveInjection var inject
to your view structRemember 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
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.
You need to add -weak_framework SwiftUI to Other Linker Flags for iOS 12 to work.
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.
Author: krzysztofzablocki
Source Code: https://github.com/krzysztofzablocki/Inject
License: MIT license
1661872140
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 |
Memory management is automatic for the cursor interface.
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)
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.
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
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
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.
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!
julia> user_input="1';select 'powned';"
julia> escape_value(conn, user_input)
"'1'';select ''powned'';'"
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.
# 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
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
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 :) :)
Author: NCarson
Source Code: https://github.com/NCarson/Postgres.jl
License: View license
1623125100
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