一、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