在当今去中心化应用(DApps)蓬勃发展的时代,将区块链功能集成到移动应用中已成为一种趋势,iOS 作为全球领先的移动操作系统,其开发者社区对于如何使用 Swift 语言与以太坊这样的主流区块链进行交互充满了好奇与需求,本文将带你一步步了解,如何在 iOS 应用中通过 Swift 语言实现与以太坊网络的连接、数据查询和交易签名。
为什么选择 Swift 与以太坊交互?
将 Swift 与以太坊结合,主要基于以下几点优势:
- 原生体验:使用 Swift 开发可以充分利用 iOS 的原生 UI 框架(如 SwiftUI、UIKit),为用户提供流畅、无缝的交互体验,避免使用 WebView 等混合方案带来的性能和兼容性问题。
- 强大的社区与工具:Swift 语言本身拥有成熟的生态系统,通过

web3.swift等库,开发者可以方便地调用以太坊的 JSON-RPC 接口,实现账户管理、交易发送、智能合约交互等功能。 - 隐私与安全:关键操作(如交易签名)可以在本地设备上完成,私钥无需离开手机,极大地增强了应用的安全性和用户隐私。
核心概念:iOS 与以太坊如何对话?
iOS 应用本身无法直接与以太坊区块链通信,它们需要一个中间层——以太坊节点,最常见的方式是连接到一个 JSON-RPC 接口。
- JSON-RPC:这是一个简单的远程过程调用协议,以太坊节点(如 Geth, Nethermind, 或 Infura 这样的第三方服务)通过这个接口暴露其功能,如
eth_getBalance(查询余额)、eth_sendTransaction(发送交易)等。 - Web3 库:手动构建和解析 JSON-RPC 请求非常繁琐,我们使用
web3.swift这样的第三方库,它将底层的 JSON-RPC 调用封装成易于使用的 Swift 对象和方法,让开发者可以像调用普通函数一样与以太坊交互。
实战准备:环境搭建
在开始编码之前,我们需要准备好开发环境。
- 安装 Xcode:确保你已经安装了最新版的 Xcode。
- 选择以太坊节点服务:
- Infura:最流行的选择之一,它提供了一个稳定可靠的云端节点服务,你只需注册一个账号,创建一个新的项目,即可获得一个 RPC URL,这对于开发和测试来说非常方便。
- Alchemy:与 Infura 类似,是另一个高质量的节点服务提供商。
- 本地节点:对于高级开发者,可以在自己的电脑或服务器上运行一个以太坊全节点(如 Geth),但这需要大量的存储空间和持续的网络连接。
- 添加依赖包:我们将使用 Swift Package Manager (SPM) 来集成
web3.swift库。- 在 Xcode 中,选择你的项目 -> 点击 "Add Packages" 按钮。
- 输入
web3.swift的仓库地址:https://github.com/attaswift/web3.swift - 选择合适的版本并添加。
核心功能实现
下面,我们通过几个核心场景来展示如何使用 web3.swift。
连接到以太坊网络并查询账户余额
这是最基础的操作,用于验证连接是否成功。
import web3swift
import BigInt // web3.swift 依赖 BigInt 处理以太坊上的大整数
// 1. 定义你的 Infura RPC URL
let rpcURL = "https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID"
guard let url = URL(string: rpcURL) else { fatalError("Invalid RPC URL") }
// 2. 创建一个 Web3 实例
let web3 = Web3.new(url)
// 3. 定义要查询的地址 (例如以太坊创始人 Vitalik Buterin 的地址)
let address = EthereumAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")!
// 4. 创建一个 balance 任务
let balanceResult = web3.eth.getBalance(address: address)
// 5. 处理结果
do {
// balanceResult 是一个 Promise,我们需要在它的回调中处理结果
let balance = try balanceResult.wait()
// 余额是以 'wei' 为单位的,我们需要将其转换为 'ether'
let etherBalance = Double(balance) / pow(10.0, 18.0)
print("Address: \(address.address)")
print("Balance: \(etherBalance) ETH")
} catch {
print("Failed to get balance: \(error)")
}
发送一笔交易
发送交易比查询余额复杂,因为它需要用户签名,在 iOS 中,最佳实践是使用 本地签名,即用户在设备上通过自己的私钥对交易进行签名,然后将签名后的交易发送到网络。
注意:在实际应用中,绝对不要将用户的私钥硬编码在代码中,应该从 iOS 的 Keychain 中安全地读取。
import web3swift
import BigInt
// 假设我们已经从 Keychain 中获取了发送方的私钥
let privateKey = "YOUR_PRIVATE_KEY_HERE" // 仅作示例,实际应用中应从安全存储读取
guard let credentials = try? EthereumPrivateKey(privateKey: privateKey) else {
fatalError("Invalid private key")
}
let fromAddress = credentials.address
// 接收方地址
let toAddress = EthereumAddress("0x...")!
// 交易信息
let to = toAddress
let amount = BigUInt("1000000000000000000")! // 1 ETH in wei
let gasPrice = BigUInt("20000000000")! // 20 Gwei
let gasLimit = BigUInt("21000") // 标准转账的 gas 限制
// 创建交易对象
var transaction = EthereumTransaction(
to: to,
value: amount,
data: Data(), // 可选,用于调用智能合约
nonce: nil, // web3.swift 会自动查询 nonce
v: BigUInt(0),
r: BigUInt(0),
s: BigUInt(0),
chainID: BigUInt(1) // 主网 ChainID
)
// 估算 gas (可选但推荐)
do {
let estimatedGas = try web3.eth.estimateGas(for: transaction)
transaction.gasLimit = estimatedGas
print("Estimated Gas: \(estimatedGas)")
} catch {
print("Failed to estimate gas: \(error)")
}
// 1. 创建交易
let transactionToSend = try transaction.sign(with: credentials, chainID: BigUInt(1))
// 2. 发送交易
web3.eth.sendRaw(transaction: transactionToSend).promise
.done { txHash in
print("Transaction sent successfully! Hash: \(txHash.hex())")
}
.catch { error in
print("Failed to send transaction: \(error)")
}
与智能合约交互
与智能合约交互是 DApp 的核心,你需要合约的 ABI (Application Binary Interface) 和 地址。
假设我们有一个简单的存储合约,有一个 store(uint256) 和一个 retrieve() 函数。
import web3swift
import BigInt
// 合约地址
let contractAddress = EthereumAddress("0x...")!
// 合约 ABI (JSON 格式)
let contractABI = """
[
{
"constant": false,
"inputs": [{"name": "x", "type": "uint256"}],
"name": "store",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "retrieve",
"outputs": [{"name": "retVal", "type": "uint256"}],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
"""
// 创建合约实例
guard let contract = web3.contract(Web3.Utils.abiDecode(json: contractABI)!, at: contractAddress, options: nil) else {
fatalError("Failed to create contract instance")
}
// 1. 调用 'retrieve' (常量/视图函数,不需要 gas)
let callOptions = TransactionOptions.defaultOptions
let retrieveFunction = contract.read("retrieve")!
retrieveFunction.call(options: callOptions).promise
.done { result in
if let retVal = result["0"] as? BigUInt {
print("Retrieved value: \(retVal)")
}
}
.catch { error in
print("Failed to call retrieve: \(error)")
}
// 2. 调用 'store' (写入函数,需要签名和发送)
let storeFunction = contract.write("store")!
let storeValue = BigUInt(42)
let transaction = storeFunction(arguments: [storeValue])
transaction.send(options: callOptions).promise
.done { txHash