Skip to content

Tips & Tricks

Wojciech Kulik edited this page Nov 25, 2024 · 40 revisions

⏱️  Improve Build Time

xcodebuild CLI tool is slower than Xcode. This section provides a workaround to improve the build time.

The issue is caused by the fact that xcodebuild tries to connect to the Apple servers before building the project, which can take 20 seconds or more. Usually, those requests are not necessary, but they slow down each build.

The workaround blocks developerservices2.apple.com domain when the xcodebuild tool is running by modifying /etc/hosts. Below you can find three ways to enable the workaround.

Warning

Keep in mind that disabling access to developerservices2.apple.com for xcodebuild may cause some issues with the build process. It will disable things like registering devices, capabilities, and other network-related features. Therefore, it's best to use it when you are working just on the code and don't need updating project settings.

 

1. Manual (script)

Enable workaround:

sudo bash -c "echo '127.0.0.1 developerservices2.apple.com' >>/etc/hosts"

Disable workaround:

sudo sed -i '' '/developerservices2\.apple\.com/d' /etc/hosts

 

2. Manual (network sniffer)

If you use some tool to sniff network traffic like Proxyman or Charles Proxy, you can block requests to https://developerservices2.apple.com/* and automatically return some error like 999 status code. It will prevent xcodebuild from further calls.

 

3. Automatic (xcodebuild.nvim integration)

In this approach the Apple server will be blocked only when the xcodebuild command (triggered by the plugin) is running. However, it requires a passwordless sudo permission for the script.

Caution

Giving passwordless sudo access to that file, potentially opens a gate for malicious software that could modify the file and run some evil code using root account. The best way to protect that file is to create a local copy, change the owner to root, and give write permission only to root. The same must be applied to the parent directory. The script below automatically secures the file.

 

👉 Enable integration that automatically blocks Apple servers

Update your config with:

integrations = {
  xcodebuild_offline = {
    enabled = true,
  },
}

 

👉 Run the following command to install & protect the script

DEST="$HOME/Library/xcodebuild.nvim" && \
  SOURCE="$HOME/.local/share/nvim/lazy/xcodebuild.nvim/tools/xcodebuild_offline" && \
  ME="$(whoami)" && \
  sudo install -d -m 755 -o root "$DEST" && \
  sudo install -m 755 -o root "$SOURCE" "$DEST" && \
  sudo bash -c "echo \"$ME ALL = (ALL) NOPASSWD: $DEST/xcodebuild_offline\" >> /etc/sudoers"

 

More details about this issue can be found here: #201

 

🔄  Auto Refresh Files

If you switch between Neovim and Xcode you most likely want a feature that automatically refreshes changed files.

The only reliable solution I found is:

vim.opt.autoread = true

-- refresh files if changed outside
vim.fn.timer_start(2000, function()
  vim.cmd("silent! checktime")
end, { ["repeat"] = -1 })

 

📝  Templates for Swift Files

Often, you want to have templates for specific files in your project or at least you want to have a header (like when using Xcode).

I created a small function that fills in a new file with a predefined template. Based on the file name suffix it can use the relevant template.

This function is also able to replace some placeholders like {date}, {filename}, and {name}. If you place {cursor} in your template, the cursor will be automatically moved there.

vim.api.nvim_create_autocmd({ "BufNewFile", "BufReadPost" }, {
  group = "bufcheck",
  pattern = "*.swift",
  callback = function(ev)
    local lines = #vim.api.nvim_buf_get_lines(ev.buf, 0, -1, false)

    if lines > 1 then
      return
    end

    local filename = string.match(ev.file, "([^/]*)%.swift")
    local name = filename

    -- TODO: make sure this path leads to your folder with templates!!
    local basepath = os.getenv("HOME") .. "/.config/YOUR_CONFIG_PATH/templates/"
    local templates = { "View", "ViewModel", "Builder", "Router", "Tests", "Spec" }

    local template
    local cursor

    for _, templateSuffix in ipairs(templates) do
      if vim.endswith(filename, templateSuffix) then
        template = vim.fn.readfile(basepath .. string.lower(templateSuffix) .. ".txt")
        name = string.gsub(name, templateSuffix, "")
        break
      end
    end

    template = template or vim.fn.readfile(basepath .. "empty.txt")

    for i = 1, #template do
      template[i] = string.gsub(template[i], "{date}", os.date("%d/%m/%Y"))
      template[i] = string.gsub(template[i], "{filename}", filename)
      template[i] = string.gsub(template[i], "{name}", name)

      if cursor == nil and string.find(template[i], "{cursor}") then
        cursor = { i, tonumber(string.find(template[i], "{cursor}")) + 1 }
      end
      template[i] = string.gsub(template[i], "{cursor}", " ")
    end

    vim.api.nvim_buf_set_lines(ev.buf, 0, -1, false, template)

    if cursor then
      vim.api.nvim_win_set_cursor(0, cursor)
    end

    vim.cmd("w")
  end,
})

Sample template templates/viewmodel.txt

//
//  {filename}.swift
//
//
//  Created by YOUR_NAME on {date}.
//  Copyright © 2024 YOUR_COMPANY. All rights reserved.
//

import Foundation

protocol {filename}Protocol: ObservableObject {
    var someProperty: String { get }
}

final class {filename}: {filename}Protocol {
    @Published var someProperty: String = ""

    init() {
        {@cursor@}
    }
}

 

🐦‍🔥  Using Swift Dev Snapshot

If you want to try out new features from sourcekit-lsp, you need to switch to Swift Development snapshot. Below you can find steps required to do that:

  1. Download the latest snapshot for Xcode from here
  2. Install it.
  3. Switch to it from Xcode (menu bar -> Xcode -> Toolchains)
  4. Run (make sure that the path matches your version)
    sudo cp `xcode-select -p`/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/*/lib/darwin/libclang_rt.*.a /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-03-07-a.xctoolchain/usr/lib/clang/17/lib/darwin/
  5. Switch your LSP config to point to the new sourcekit-lsp:
    lspconfig["sourcekit"].setup({
      capabilities = capabilities,
      on_attach = on_attach,
      cmd = {
        "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-03-07-a.xctoolchain/usr/bin/sourcekit-lsp",
      },
      -- ...
    },
  6. Add additional flags to build and test from Neovim using the latest toolchain. Update your xcodebuild.nvim setup options:
    commands = {
      extra_build_args = "-parallelizeTargets -toolchain swift-latest",
      extra_test_args = "-parallelizeTargets -toolchain swift-latest",
    },
  7. Clean & build the project from Xcode.
  8. Open project in Neovim as usual. New features like smart rename should be available.

 

🔥  Hot Reload

In Neovim you can't use SwiftUI previews.
However, you can integrate tools that add a hot reload feature for iOS apps. Here are two essential tools that you need:

 

🐞  View Hierarchy

There is no easy way to debug view hierarchy outside of Xcode.
However, there is a paid alternative. If you are interested, check out this issue: #213.