- Creating Swift Package Manager tools from your existing codebase
- By Paul Samuels
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: iWeslie
- Proofreader: LoneyIsError, iWeslie
The Swift Package Manager (SPM) is great for writing quick tools, and you can even extract existing code from your application. The trick is to realize that you can symlink folders into SPM projects, which means that with some work you can create a command-line tool that wraps parts of production code.
Why are you doing this?
Although it is very project-dependent, a common use case is to create support, debugging, and continuous integration (CI) validation tools. For example, many applications use remote data for functional purposes, and the application needs to convert the remote data to a custom type and use business rules to perform useful operations on the data. There will be multiple points of failure in this process that show the application crashing or behaving incorrectly, so the solution is to start and debug with an attached debugger, and the Swift Package manager will be a good tool to help find and potentially prevent problems.
Matters needing attention
You can’t use UIKit framework code because this technique only works with Foundation library code. While this may sound restrictive, ideally neither business logic nor data manipulation code should introduce anything about the UIKit framework.
Having dependencies makes the technique more difficult, but you can still use it, but with more configuration in package.swift.
How are you going to do that?
It depends on the structure of your project. I have a sample project here. This is a small iOS project that shows a list of posts from a blog (you don’t need to look at the project itself, the project itself is not important). The blog posts in the project come from dummy JSON data, which is not particularly well structured, so the application needs to customize the decoding. To keep it lightweight, I build the simplest wrapper as follows:
- Read from standard input
- Use production parsing code
- Prints decoding results or errors
You can add more and more to it like crazy, but this simple tool will quickly give us feedback on whether the production code can accept some JSON or display any errors that might occur without starting the emulator.
The basic structure of the sample project is as follows:
. └ ─ ─ SymlinkedSPMExample ├ ─ ─ AppDelegate. Swift ├ ─ ─ Base. Lproj │ └ ─ ─ LaunchScreen. The storyboard ├ ─ ─ the Info. The plist ├ ─ ─ ├── class ├─ BlogPost. Swift └ ─ ─ BlogPostsRequest.swift
Copy the code
I purposely created a Types directory that contains only the code I want to reuse. To create a command line tool that utilizes sub-production code, we can do the following:
mkdir -p tools/web-api
cd tools/web-api
swift package init --type executable
Copy the code
Now we have a working project. First let’s link the production code:
cd Sources
ln -s. /.. /.. /SymlinkedSPMExample/WebService/Types WebServicecd.Copy the code
You need to use relative paths for this link, or it will crash when you move it to another computer
The project structure now looks like this:
. ├ ─ ─ SymlinkedSPMExample │ ├ ─ ─ AppDelegate. Swift │ ├ ─ ─ Base. Lproj │ │ └ ─ ─ LaunchScreen. The storyboard │ ├ ─ ─ the Info. The plist │ ├── class │ ├─ class │ ├─ class │ ├─ BlogPost. Swift │ └ ─ ─ BlogPostsRequest. Swift └ ─ ─ the tools └ ─ ─ web - API ├ ─ ─ Package. The swift ├ ─ ─ the README. Md ├ ─ ─ Sources │ ├ ─ ─ WebServer - >.. /.. /.. / SymlinkedSPMExample/WebService/Types / │ └ ─ ─ web - API │ └ ─ ─ main. Swift └ ─ ─ TestsCopy the code
Now I need to update the package. swift file to create a new target for the code and add a dependency so that the Web-API executable can use the production code
Package.swift
/ / swift - tools - version: 4.0
import PackageDescription
let package = Package(
name: "web-api",
targets: [
.target(name: "web-api", dependencies: [ "WebService" ]),
.target(name: "WebService"),])Copy the code
Now that the SPM knows how to build projects, let’s use production parsing code to write the code mentioned earlier.
main.swift
import Foundation
import WebService
do {
print(try JSONDecoder().decode(BlogPostsRequest.self, from: FileHandle.standardInput.readDataToEndOfFile()).posts)
} catch {
print(error)
}
Copy the code
With that in mind, we can now start running JSON through the tool to see if the production code handles it:
Here’s what it looks like when we try to send valid JSON with this tool:
$ echo '{ "posts" : [] }' | swift run web-api
[]
$ echo '{ "posts" : [ { "title" : "Some post", "tags" : [] } ] }' | swift run web-api
[WebService.BlogPost(title: "Some post", tags: [])]
$ echo '{ "posts" : [ { "title" : "Some post", "tags" : [ { "value" : "cool" } ] } ] }' | swift run web-api
[WebService.BlogPost(title: "Some post", tags: ["cool"]]Copy the code
Here is an example of the error message we get when we enter invalid JSON:
$ echo '{}' | swift run web-api
keyNotFound(CodingKeys(stringValue: "posts", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"posts\", intValue: nil) (\"posts\").", underlyingError: nil))
$ echo '{ "posts" : [ { } ] }' | swift run web-api
keyNotFound(CodingKeys(stringValue: "title", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "posts", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"title\", intValue: nil) (\"title\").", underlyingError: nil))
$ echo '{ "posts" : [ { "title" : "Some post" } ] }' | swift run web-api
keyNotFound(CodingKeys(stringValue: "tags", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "posts", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"tags\", intValue: nil) (\"tags\").", underlyingError: nil))
Copy the code
- The first example is wrong because it doesn’t
posts
- The second example is also wrong because
posts
There is notitle
- The third example is still wrong because
posts
There is notags
In practice, I will pipe out a live or temporary breakpointcurl
Results, not handwritten JSON code.
This is really cool, because I can see that the production code didn’t parse some of these examples, and I can see the message that explains why the error occurred. Without this tool, I would need to run the application manually and find a way to get a different JSON payload to run the parsing logic.
conclusion
This article introduced the basic techniques for creating tools using production code through SPM. You can actually run it and create some nifty workflows such as:
- Add this tool to the Continuous integration pipeline of Web-API as a step to ensure that deployments that crash mobile clients do not occur.
- Expand the tool to apply business rules (from production code) to see if errors have been introduced at the feed, parse, or business rule level.
I’ve already started using this idea in my own projects, and I’m glad it helped me and the rest of my team.
If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.