Administrator
发布于 2025-03-07 / 1159 阅读

SDK ios集成文档

一、IOS

1.下载导入SDK

解压后,将文件夹放到项目文件,Embed 设置为 Embed & sign,最小版本为ios 16.0

2.初始化

1. 在AppDelegate.swift文件中通过initWith初始化

swift

import UIKit
import WaylProxy

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(
        _: UIApplication,
        didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {

        WaylProxySDK.initWith(self)

        return true
    }
}


import SwiftUI

@main
struct App: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

object-c

#import "AppDelegate.h"
#import <waylproxy/waylproxy-Swift.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    // 异步调用 WaylProxy 的初始化方法
    [WaylProxySDK initWith:self completion:^{
        NSLog(@"WaylProxy 初始化");
    }];
    
    // Override point for customization after application launch.
    return YES;
}


#pragma mark - UISceneSession lifecycle


- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
    // Called when a new scene session is being created.
    // Use this method to select a configuration to create the new scene with.
    return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}


- (void)application:(UIApplication *)application didDiscardSceneSessions:(NSSet<UISceneSession *> *)sceneSessions {
    // Called when the user discards a scene session.
    // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
    // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}

@end

2. 或者在入口文件

因为sdk需要向服务器发送请求,所以要在app有网络访问权限后再调用初始化sdk

swift

import SwiftUI
import WaylProxy

@main
struct App: App {
    // 如果不想使用UIApplicationDelegateAdaptor,请用 Proxy.shared.initial()
    // 不要在init中调用 Proxy.shared.initial() 会阻塞进程
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    WaylProxySDK.shared.initial()
                }
        }
    }
}

3. 在info.plist文件添加uuid

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.wayl.proxy.UUID</key>
    <string>****************</string>
</dict>
</plist>

3.功能使用

启动代理,传入需要的端口号,启动成功以后将监听该本地端口的所有请求

// swift
let proxy = WaylProxySDK.shared
let result = await proxy.start(port: 10808)

// 或者不传入端口,由sdk自己随机生成一个端口
let result = await proxy.start()

// object-c
[[WaylProxySDK shared] startWithPort:10808 completion:^(Result *result) {
    dispatch_async(dispatch_get_main_queue(), ^{

    });
}];
// 或者不传入端口,由sdk自己随机生成一个端口 因为object-c不能重载方法,因此方法名是 startNoPort
[[WaylProxySDK shared] startNoPort: ^(Result *result) {
    dispatch_async(dispatch_get_main_queue(), ^{

    });
}];

也可以调用该方法先拿到一个空闲端口

getFreePorts()

停止代理服务

stop()

清除代理服务,该方法会清除掉所有的代理数据,(清理以后,需要先调用初始化方法,才可以再次调用启动方法)

clear()

获取sdk版本号

getVersion()

4.云配置

如果使用云配置,需要在info.plist中添加如下配置,如果使用gitee则添加\"GITEE\",如果使用腾讯云则添加\"TENCENT\"等等,写入有效的下载地址,腾讯云和阿里云要可以共有读

<key>com.wayl.proxy.config_cloud</key>      
<string>
{
	\"GITEE\": [
		\"https://gitee.com/api/v5/repos/*****/****/contents/sdk.json?access_token=********&ref=master\"
	],
	\"TENCENT\": [{
		\"url\": \"https://*******.cos.ap-shanghai.myqcloud.com/sdk.json\"
	}],
	\"ALIYUN\": [{
		\"url\": \"https://*****.oss-cn-shanghai.aliyuncs.com/sdk.json\"
	}]
}
</string>

5.接口文档

swift

import CryptoKit
import DeviceCheck
import Foundation
import Network
import UIKit
import WebKit
import _Concurrency
import _StringProcessing
import _SwiftConcurrencyShims

@objcMembers public class Result : NSObject, @unchecked Sendable {

    /// 表示操作是否成功
    public let success: Bool

    /// 包含结果数据的对象
    public let data: WaylProxy.ResultData?

    /// 相关的消息
    public let message: String?

    /// 初始化一个 `Result` 实例
    /// - Parameters:
    ///   - success: 操作是否成功
    ///   - data: 结果数据
    ///   - message: 相关消息
    public init(success: Bool, data: WaylProxy.ResultData?, message: String?)

    /// 转换为字典
    /// - Returns: 包含 success, data 和 message 的字典
    public func toDictionary() -> [String : Any]
}

@objcMembers public class ResultData : NSObject {

    /// 端口号
    public var port: NSNumber?

    /// 用字典初始化 `ResultData` 实例
    /// - Parameter dictionary: 包含端口号的字典
    public init(dictionary: [String : Any])

    /// 转换为字典
    /// - Returns: 包含 port 的字典
    public func toDictionary() -> [String : Any]
}

@objc public protocol WaylProxyDelegate {

    /// 当安装状态发生变化时回调
    @objc func onInitStatusChanged(_ newStatus: Bool)

    /// 当验证状态发生变化时回调
    @objc func onValidationStatusChanged(_ newStatus: Bool)

    /// 当代理服务启动状态发生变化时回调
    @objc func onStartStatusChanged(_ newStatus: Bool)
}

@objc public class WaylProxySDK : NSObject {

    /// 当前 SDK 版本号
    @objc public static let sdkVersion: String

    /// 单例对象,用于访问 WaylProxy
    @objc public static let shared: WaylProxy.WaylProxySDK

    /// 代理(监听器)对象,用于接收状态更新回调
    @objc weak public var delegate: (any WaylProxy.WaylProxyDelegate)?

    /// 对外只读 初始化状态
    @objc public var initiated: Bool { get }

    /// 对外只读 套餐验证状态
    @objc public var isValidated: Bool { get }

    /// 对外只读 代理开启状态
    @objc public var isStarted: Bool { get }

    /// 异步初始化方法,仅供 Swift 使用
    /**
     * [Swift] 异步初始化代理
     *
     * 该方法用于在 Swift 环境中异步初始化代理服务。
     * 它接收一个代理对象,该代理必须遵守 `WaylProxyDelegate` 协议,并在异步过程中更新状态。
     *
     * **Swift 使用示例:**
     * ```swift
     * WaylProxySDK.initWith(delegate)
     * ```
     *
     * **注意:** 该方法仅供 Swift 调用,不适用于 Objective-C。
     */
    public class func initWith(_ delegate: AnyObject)

    /// 异步初始化代理服务,仅供 Swift 使用
    /**
     * [Swift] 异步初始化代理服务
     *
     * 此方法在 Swift 环境中用于初始化代理服务,检查必要的配置和网络状态,
     * 并返回一个 `Result` 对象表示初始化结果。
     *
     * **Swift 使用示例:**
     * ```swift
     * let result = WaylProxySDK.shared.initial()
     * print(result.message ?? "未知错误")
     * ```
     *
     * **注意:** 该方法仅供 Swift 调用,不适用于 Objective-C。
     */
    @discardableResult
    public func initial() -> WaylProxy.Result

    /// 异步启动代理服务,仅供 Swift 使用
    /**
     * [Swift] 异步启动代理服务
     *
     * 该方法在 Swift 中用于启动代理服务,会检查代理是否已初始化和验证,
     * 如果条件满足,则在指定端口启动代理服务,并返回一个 `Result` 对象表示启动结果。
     *
     * **Swift 使用示例:**
     * ```swift
     * let result = await WaylProxySDK.shared.start(port: 10808)
     * print(result.message ?? "未知错误")
     * ```
     *
     * **注意:** 该方法仅供 Swift 调用,不适用于 Objective-C。
     */
    public func start(port: Int) async -> WaylProxy.Result

    /// 异步启动代理服务(自动选择端口),仅供 Swift 使用
    /**
     * 该方法在 Swift 中用于启动代理服务,会自动选择一个可用端口启动服务,并返回启动结果。
     *
     * **返回:**
     * - `Result` 对象,包含代理启动状态和使用的端口信息。
     *
     * **Swift 使用示例:**
     * ```swift
     * let result = await WaylProxySDK.shared.start()
     * print(result.message ?? "未知错误")
     * ```
     *
     * **注意:** 该方法仅供 Swift 调用,不适用于 Objective-C。
     */
    public func start() async -> WaylProxy.Result

    /// 停止代理服务
    /**
     * 停止当前的代理服务,并重置代理状态。
     *
     * **Swift 调用示例:**
     * ```swift
     * WaylProxySDK.shared.stop()
     * ```
     *
     * **Objective-C 调用示例:**
     * ```objc
     * [[WaylProxySDK shared] stop];
     * ```
     */
    @objc public func stop()

    /// 获取可用端口
    /**
     * 获取当前可用的端口信息。
     *
     * - Returns: `Result` 对象,包含可用端口的信息。
     *
     * **Swift 调用示例:**
     * ```swift
     * let result = WaylProxySDK.shared.getFreePorts()
     * print(result.message ?? "未知错误")
     * ```
     *
     * **Objective-C 调用示例:**
     * ```objc
     * Result *result = [[WaylProxySDK shared] getFreePorts];
     * NSLog(@"可用端口信息: %@", result.message);
     * ```
     */
    @objc public func getFreePorts() -> WaylProxy.Result

    /// 清理代理状态
    /**
     * 清理代理的所有状态信息,包括重置标志位和清除钥匙串中的数据。
     *
     * **Swift 调用示例:**
     * ```swift
     * WaylProxySDK.shared.clear()
     * ```
     *
     * **Objective-C 调用示例:**
     * ```objc
     * [[WaylProxySDK shared] clear];
     * ```
     */
    @objc public func clear()

    /// 获取 SDK 版本号
    /// - Returns: 当前 SDK 的版本号字符串
    @objc public func getVersion() -> String
}

object-c

@class ResultData;
@class NSString;
SWIFT_CLASS("_TtC9WaylProxy6Result")
@interface Result : NSObject
/// 表示操作是否成功
@property (nonatomic, readonly) BOOL success;
/// 包含结果数据的对象
@property (nonatomic, readonly, strong) ResultData * _Nullable data;
/// 相关的消息
@property (nonatomic, readonly, copy) NSString * _Nullable message;
/// 初始化一个 <code>Result</code> 实例
/// \param success 操作是否成功
///
/// \param data 结果数据
///
/// \param message 相关消息
///
- (nonnull instancetype)initWithSuccess:(BOOL)success data:(ResultData * _Nullable)data message:(NSString * _Nullable)message OBJC_DESIGNATED_INITIALIZER;
/// 转换为字典
///
/// returns:
/// 包含 success, data 和 message 的字典
- (NSDictionary<NSString *, id> * _Nonnull)toDictionary SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
@end

@class NSNumber;
SWIFT_CLASS("_TtC9WaylProxy10ResultData")
@interface ResultData : NSObject
/// 端口号
@property (nonatomic, strong) NSNumber * _Nullable port;
/// 用字典初始化 <code>ResultData</code> 实例
/// \param dictionary 包含端口号的字典
///
- (nonnull instancetype)initWithDictionary:(NSDictionary<NSString *, id> * _Nonnull)dictionary OBJC_DESIGNATED_INITIALIZER;
/// 转换为字典
///
/// returns:
/// 包含 port 的字典
- (NSDictionary<NSString *, id> * _Nonnull)toDictionary SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
@end

SWIFT_PROTOCOL("_TtP9WaylProxy17WaylProxyDelegate_")
@protocol WaylProxyDelegate
/// 当安装状态发生变化时回调
- (void)onInitStatusChanged:(BOOL)newStatus;
/// 当验证状态发生变化时回调
- (void)onValidationStatusChanged:(BOOL)newStatus;
/// 当代理服务启动状态发生变化时回调
- (void)onStartStatusChanged:(BOOL)newStatus;
@end

SWIFT_CLASS("_TtC9WaylProxy12WaylProxySDK")
@interface WaylProxySDK : NSObject
/// 当前 SDK 版本号
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, copy) NSString * _Nonnull sdkVersion;)
+ (NSString * _Nonnull)sdkVersion SWIFT_WARN_UNUSED_RESULT;
/// 单例对象,用于访问 WaylProxy
SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly, strong) WaylProxySDK * _Nonnull shared;)
+ (WaylProxySDK * _Nonnull)shared SWIFT_WARN_UNUSED_RESULT;
/// 代理(监听器)对象,用于接收状态更新回调
@property (nonatomic, weak) id <WaylProxyDelegate> _Nullable delegate;
/// 对外只读 初始化状态
@property (nonatomic, readonly) BOOL initiated;
/// 对外只读 套餐验证状态
@property (nonatomic, readonly) BOOL isValidated;
/// 对外只读 代理开启状态
@property (nonatomic, readonly) BOOL isStarted;
/// 防止外部初始化
/// 此初始化方法为私有,防止外部直接创建 WaylProxySDK 实例。
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
+ (nonnull instancetype)new SWIFT_UNAVAILABLE_MSG("-init is unavailable");
/// 异步初始化方法的包装,仅供 Objective-C 使用
/// [Objective-C] 异步初始化代理的包装方法
/// 该方法为 Objective-C 提供了异步初始化代理服务的接口。
/// 传入一个代理对象(必须遵守 <code>WaylProxyDelegate</code> 协议)和一个完成回调,
/// 在初始化完成后回调执行。
/// <em>Objective-C 使用示例:</em>
/// \code
/// [WaylProxySDK initWith:self completion:^{
///    NSLog(@"WaylProxySDK 初始化");
/// }];
///
/// \endcode<em>注意:</em> 该方法仅供 Objective-C 调用,不适用于 Swift。
+ (void)initWith:(id _Nonnull)delegate completion:(void (^ _Nonnull)(void))completion SWIFT_METHOD_FAMILY(none);
/// 异步初始化代理服务的包装方法,仅供 Objective-C 使用
/// [Objective-C] 异步初始化代理服务的包装方法
/// 为 Objective-C 提供异步初始化代理服务的接口,
/// 通过回调返回一个 <code>Result</code> 对象表示初始化结果。
/// <em>Objective-C 使用示例:</em>
/// \code
/// [[WaylProxySDK shared] initial:^(Result *result) {
///    NSLog(@"初始化结果: %@", result.message);
/// }];
///
/// \endcode<em>注意:</em> 该方法仅供 Objective-C 调用,不适用于 Swift。
- (void)initial:(void (^ _Nonnull)(Result * _Nonnull))completion;
/// 异步启动代理服务的包装方法,仅供 Objective-C 使用
/// [Objective-C] 异步启动代理服务的包装方法
/// 为 Objective-C 提供启动代理服务的接口,
/// 通过回调返回一个 <code>Result</code> 对象表示启动结果。
/// <em>Objective-C 使用示例:</em>
/// \code
/// [[WaylProxySDK shared] start:10808 completion:^(Result *result) {
///    NSLog(@"代理启动状态: %@", result.message);
/// }];
///
/// \endcode<em>注意:</em> 该方法仅供 Objective-C 调用,不适用于 Swift。
- (void)startWithPort:(NSInteger)port completion:(void (^ _Nonnull)(Result * _Nonnull))completion;
/// 异步启动代理服务(自动选择端口)的包装方法,仅供 Objective-C 使用
/// 该方法为 Objective-C 提供自动选择端口启动代理服务的接口,
/// 通过回调返回启动结果。
/// <em>Objective-C 使用示例:</em>
/// \code
/// [[WaylProxySDK shared] startNoPort:^(Result *result) {
///    NSLog(@"代理启动状态: %@", result.message);
/// }];
///
/// \endcode<em>注意:</em> 该方法仅供 Objective-C 调用,不适用于 Swift。
/// \param completion 完成回调,返回 <code>Result</code> 对象表示启动结果。
///
- (void)startNoPort:(void (^ _Nonnull)(Result * _Nonnull))completion;
/// 停止代理服务
/// 停止当前的代理服务,并重置代理状态。
/// <em>Swift 调用示例:</em>
/// \code
/// WaylProxySDK.shared.stop()
///
/// \endcode<em>Objective-C 调用示例:</em>
/// \code
/// [[WaylProxySDK shared] stop];
///
/// \endcode
- (void)stop;
/// 获取可用端口
/// 获取当前可用的端口信息。
/// <em>Swift 调用示例:</em>
/// \code
/// let result = WaylProxySDK.shared.getFreePorts()
/// print(result.message ?? "未知错误")
///
/// \endcode<em>Objective-C 调用示例:</em>
/// \code
/// Result *result = [[WaylProxySDK shared] getFreePorts];
/// NSLog(@"可用端口信息: %@", result.message);
///
/// \endcode
/// returns:
/// <code>Result</code> 对象,包含可用端口的信息。
- (Result * _Nonnull)getFreePorts SWIFT_WARN_UNUSED_RESULT;
/// 清理代理状态
/// 清理代理的所有状态信息,包括重置标志位和清除钥匙串中的数据。
/// <em>Swift 调用示例:</em>
/// \code
/// WaylProxySDK.shared.clear()
///
/// \endcode<em>Objective-C 调用示例:</em>
/// \code
/// [[WaylProxySDK shared] clear];
///
/// \endcode
- (void)clear;
/// 获取 SDK 版本号
///
/// returns:
/// 当前 SDK 的版本号字符串
- (NSString * _Nonnull)getVersion SWIFT_WARN_UNUSED_RESULT;
@end

5.例子

使用了监听器展示一个相对完整的例子,也可以不使用,看项目实际情况

object-c

#import "ViewController.h"
#import <waylproxy/waylproxy-Swift.h>

@interface ViewController () <WaylProxyDelegate>

@property (nonatomic, strong) UIButton *refreshButton;
@property (nonatomic, strong) UILabel *logLabel;
@property (nonatomic, strong) UILabel *initializedLabel;
@property (nonatomic, strong) UILabel *validateLabel;
@property (nonatomic, strong) UILabel *startLabel;

@property (nonatomic, assign) BOOL isInitiated;
@property (nonatomic, assign) BOOL isValidated;
@property (nonatomic, assign) BOOL isRunning;
@property (nonatomic, assign) BOOL isLoadingIP;

@property (nonatomic, strong) NSString *currentIP;
@property (nonatomic, strong) NSDate *launchTime;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.launchTime = [NSDate date];
    self.isInitiated = NO;
    self.isValidated = NO;
    self.isRunning = NO;
    self.isLoadingIP = NO;
    self.currentIP = @"";
    
    self.view.backgroundColor = UIColor.systemBackgroundColor;
    
    self.refreshButton = [self createButtonWithTitle:@"刷新 IP 地址" action:@selector(refreshIP)];
    self.refreshButton.frame = CGRectMake(50, 160, self.view.frame.size.width - 100, 44);
    self.refreshButton.backgroundColor = [UIColor blueColor];
    self.refreshButton.hidden = YES;
    [self.view addSubview:self.refreshButton];
    
    // 标签
    self.initializedLabel = [self createLabelWithFrame:CGRectMake(20, 220, self.view.frame.size.width - 40, 40)];
    self.validateLabel = [self createLabelWithFrame:CGRectMake(20, 270, self.view.frame.size.width - 40, 40)];
    self.startLabel = [self createLabelWithFrame:CGRectMake(20, 320, self.view.frame.size.width - 40, 40)];
    self.logLabel = [self createLabelWithFrame:CGRectMake(20, 370, self.view.frame.size.width - 40, 60)];
    self.logLabel.numberOfLines = 0;
    
    [self.view addSubview:self.initializedLabel];
    [self.view addSubview:self.validateLabel];
    [self.view addSubview:self.startLabel];
    [self.view addSubview:self.logLabel];

    [WaylProxySDK shared].delegate = self;
    
    dispatch_async(dispatch_get_main_queue(), ^{
        self.isInitiated = YES;
        self.initializedLabel.text = @"初始化完成";
        [self checkAndStartProxy];
    });
}

- (UIButton *)createButtonWithTitle:(NSString *)title action:(SEL)selector {
    UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
    [btn setTitle:title forState:UIControlStateNormal];
    [btn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
    btn.layer.cornerRadius = 10;
    btn.clipsToBounds = YES;
    [btn addTarget:self action:selector forControlEvents:UIControlEventTouchUpInside];
    return btn;
}

- (UILabel *)createLabelWithFrame:(CGRect)frame {
    UILabel *label = [[UILabel alloc] initWithFrame:frame];
    label.textAlignment = NSTextAlignmentCenter;
    label.textColor = UIColor.grayColor;
    label.font = [UIFont systemFontOfSize:16 weight:UIFontWeightMedium];
    label.backgroundColor = UIColor.secondarySystemBackgroundColor;
    label.layer.cornerRadius = 10;
    label.clipsToBounds = YES;
    return label;
}

- (void)checkAndStartProxy {
    if (self.isInitiated && !self.isRunning) {
        NSLog(@"检测到初始化完成,准备启动...");
        [[WaylProxySDK shared] startWithPort:10808 completion:^(Result *result) {
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"启动返回:%@", result);
                if (result.success) {
                    self.isRunning = YES;
                    self.startLabel.text = @"代理开启";
                    self.logLabel.text = [NSString stringWithFormat:@"启动成功,端口: %@", result.data.port];
                    self.refreshButton.hidden = NO;
                    [self refreshIP];
                } else {
                    self.logLabel.text = result.message ?: @"启动失败";
                }
            });
        }];
    }
}

- (void)onInitStatusChanged:(BOOL)newStatus {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.isInitiated = newStatus;
        self.initializedLabel.text = newStatus ? @"初始化成功" : @"初始化失败";
    });
}

- (void)onValidationStatusChanged:(BOOL)newStatus {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.isValidated = newStatus;
        self.validateLabel.text = newStatus ? @"验证成功" : @"验证失败";
    });
}

- (void)onStartStatusChanged:(BOOL)newStatus {
    dispatch_async(dispatch_get_main_queue(), ^{
        self.isRunning = newStatus;
        self.startLabel.text = newStatus ? @"代理开启" : @"代理关闭";
    });
}

- (void)refreshIP {
    self.isLoadingIP = YES;
    self.logLabel.text = @"正在获取 IP 地址...";
    
    NSDictionary *proxyDict = @{
        @"HTTPEnable": @YES,
        @"HTTPProxy": @"127.0.0.1",
        @"HTTPPort": @10808,
        @"HTTPSEnable": @YES,
        @"HTTPSProxy": @"127.0.0.1",
        @"HTTPSPort": @10808
    };
    
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.connectionProxyDictionary = proxyDict;
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
    NSURL *url = [NSURL URLWithString:@"https://ipinfo.io/ip"];
    
    NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.isLoadingIP = NO;
            if (error) {
                self.logLabel.text = [NSString stringWithFormat:@"获取 IP 失败:%@", error.localizedDescription];
            } else {
                NSString *ip = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                self.currentIP = [ip stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
                self.logLabel.text = [NSString stringWithFormat:@"当前 IP: %@", self.currentIP];
            }
        });
    }];
    [task resume];
}

@end