feat: sync httpdns sdk/platform updates without large binaries
This commit is contained in:
17
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/AppDelegate.h
Normal file
17
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/AppDelegate.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// AppDelegate.h
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// Created by junmo on 2018/8/3.
|
||||
// Copyright © 2018<31><38>?trustapp.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (strong, nonatomic) UIWindow *window;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
56
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/AppDelegate.m
Normal file
56
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/AppDelegate.m
Normal file
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// AppDelegate.m
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// Created by junmo on 2018/8/3.
|
||||
// Copyright © 2018<EFBFBD><EFBFBD>?trustapp.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import "AppDelegate.h"
|
||||
#import "DemoViewController.h"
|
||||
|
||||
@interface AppDelegate ()
|
||||
|
||||
@end
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
DemoViewController *vc = [DemoViewController new];
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
|
||||
self.window.rootViewController = nav;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationWillResignActive:(UIApplication *)application {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationDidEnterBackground:(UIApplication *)application {
|
||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
||||
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
}
|
||||
|
||||
|
||||
- (void)applicationWillTerminate:(UIApplication *)application {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "76x76",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14109" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="vfq-jO-Ao8"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="cLZ-g3-3sL"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
12
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfig.plist
Normal file
12
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfig.plist
Normal file
@@ -0,0 +1,12 @@
|
||||
<?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>accountID</key>
|
||||
<integer>139450</integer>
|
||||
<key>secretKey</key>
|
||||
<string>807a19762f8eaefa8563489baf198535</string>
|
||||
<key>aesSecretKey</key>
|
||||
<string>82c0af0d0cb2d69c4f87bb25c2e23929</string>
|
||||
</dict>
|
||||
</plist>
|
||||
21
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.h
Normal file
21
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.h
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// DemoConfigLoader.h
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface DemoConfigLoader : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) NSInteger accountID;
|
||||
@property (nonatomic, copy, readonly) NSString *secretKey;
|
||||
@property (nonatomic, copy, readonly) NSString *aesSecretKey;
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL hasValidAccount;
|
||||
|
||||
+ (instancetype)shared;
|
||||
|
||||
@end
|
||||
|
||||
85
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.m
Normal file
85
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoConfigLoader.m
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// DemoConfigLoader.m
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoConfigLoader.h"
|
||||
|
||||
@implementation DemoConfigLoader {
|
||||
NSInteger _accountID;
|
||||
NSString *_secretKey;
|
||||
NSString *_aesSecretKey;
|
||||
BOOL _hasValidAccount;
|
||||
}
|
||||
|
||||
+ (instancetype)shared {
|
||||
static DemoConfigLoader *loader;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
loader = [[DemoConfigLoader alloc] init];
|
||||
});
|
||||
return loader;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
[self loadConfig];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// 复杂逻辑:配置加载顺序为 Bundle > 环境变量;并<EFBFBD><EFBFBD>?accountID 进行有效性校<EFBFBD><EFBFBD>?
|
||||
- (void)loadConfig {
|
||||
_accountID = 0;
|
||||
_secretKey = @"";
|
||||
_aesSecretKey = @"";
|
||||
_hasValidAccount = NO;
|
||||
|
||||
NSDictionary *bundleDict = nil;
|
||||
NSString *plistPath = [[NSBundle mainBundle] pathForResource:@"DemoConfig" ofType:@"plist"];
|
||||
if (plistPath.length > 0) {
|
||||
bundleDict = [NSDictionary dictionaryWithContentsOfFile:plistPath];
|
||||
}
|
||||
|
||||
NSDictionary *env = [[NSProcessInfo processInfo] environment];
|
||||
|
||||
NSNumber *acc = bundleDict[@"accountID"]; // NSNumber preferred in plist
|
||||
NSString *secret = bundleDict[@"secretKey"] ?: @"";
|
||||
NSString *aes = bundleDict[@"aesSecretKey"] ?: @"";
|
||||
|
||||
NSString *envAcc = env[@"HTTPDNS_ACCOUNT_ID"];
|
||||
NSString *envSecret = env[@"HTTPDNS_SECRET_KEY"];
|
||||
NSString *envAes = env[@"HTTPDNS_AES_SECRET_KEY"];
|
||||
|
||||
if (envAcc.length > 0) {
|
||||
acc = @([envAcc integerValue]);
|
||||
}
|
||||
if (envSecret.length > 0) {
|
||||
secret = envSecret;
|
||||
}
|
||||
if (envAes.length > 0) {
|
||||
aes = envAes;
|
||||
}
|
||||
|
||||
if (acc != nil && [acc integerValue] > 0 && secret.length > 0) {
|
||||
_accountID = [acc integerValue];
|
||||
_secretKey = secret;
|
||||
_aesSecretKey = aes ?: @"";
|
||||
_hasValidAccount = YES;
|
||||
} else {
|
||||
_accountID = 0;
|
||||
_secretKey = @"";
|
||||
_aesSecretKey = @"";
|
||||
_hasValidAccount = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSInteger)accountID { return _accountID; }
|
||||
- (NSString *)secretKey { return _secretKey; }
|
||||
- (NSString *)aesSecretKey { return _aesSecretKey; }
|
||||
- (BOOL)hasValidAccount { return _hasValidAccount; }
|
||||
|
||||
@end
|
||||
|
||||
49
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.h
Normal file
49
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.h
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// DemoHttpdnsScenario.h
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-23
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DemoResolveModel.h"
|
||||
#import "HttpdnsService.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class DemoHttpdnsScenario;
|
||||
|
||||
@interface DemoHttpdnsScenarioConfig : NSObject <NSCopying>
|
||||
|
||||
@property (nonatomic, copy) NSString *host;
|
||||
@property (nonatomic, assign) HttpdnsQueryIPType ipType;
|
||||
@property (nonatomic, assign) BOOL httpsEnabled;
|
||||
@property (nonatomic, assign) BOOL persistentCacheEnabled;
|
||||
@property (nonatomic, assign) BOOL reuseExpiredIPEnabled;
|
||||
|
||||
- (instancetype)init;
|
||||
|
||||
@end
|
||||
|
||||
@protocol DemoHttpdnsScenarioDelegate <NSObject>
|
||||
|
||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model;
|
||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didAppendLogLine:(NSString *)line;
|
||||
|
||||
@end
|
||||
|
||||
@interface DemoHttpdnsScenario : NSObject
|
||||
|
||||
@property (nonatomic, weak, nullable) id<DemoHttpdnsScenarioDelegate> delegate;
|
||||
@property (nonatomic, strong, readonly) DemoResolveModel *model;
|
||||
|
||||
- (instancetype)initWithDelegate:(id<DemoHttpdnsScenarioDelegate>)delegate;
|
||||
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config;
|
||||
- (void)resolveSyncNonBlocking;
|
||||
- (void)resolveSync;
|
||||
- (NSString *)logSnapshot;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
153
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.m
Normal file
153
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoHttpdnsScenario.m
Normal file
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// DemoHttpdnsScenario.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-23
|
||||
//
|
||||
|
||||
#import "DemoHttpdnsScenario.h"
|
||||
#import "DemoConfigLoader.h"
|
||||
|
||||
@interface DemoHttpdnsScenarioConfig ()
|
||||
@end
|
||||
|
||||
@implementation DemoHttpdnsScenarioConfig
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_host = @"www.new.com";
|
||||
_ipType = HttpdnsQueryIPTypeBoth;
|
||||
_httpsEnabled = YES;
|
||||
_persistentCacheEnabled = YES;
|
||||
_reuseExpiredIPEnabled = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone {
|
||||
DemoHttpdnsScenarioConfig *cfg = [[[self class] allocWithZone:zone] init];
|
||||
cfg.host = self.host;
|
||||
cfg.ipType = self.ipType;
|
||||
cfg.httpsEnabled = self.httpsEnabled;
|
||||
cfg.persistentCacheEnabled = self.persistentCacheEnabled;
|
||||
cfg.reuseExpiredIPEnabled = self.reuseExpiredIPEnabled;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface DemoHttpdnsScenario () <HttpdnsLoggerProtocol, HttpdnsTTLDelegate>
|
||||
|
||||
@property (nonatomic, strong) HttpDnsService *service;
|
||||
@property (nonatomic, strong) DemoHttpdnsScenarioConfig *config;
|
||||
@property (nonatomic, strong) NSMutableString *logBuffer;
|
||||
@property (nonatomic, strong) dispatch_queue_t logQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DemoHttpdnsScenario
|
||||
|
||||
- (instancetype)initWithDelegate:(id<DemoHttpdnsScenarioDelegate>)delegate {
|
||||
if (self = [super init]) {
|
||||
_delegate = delegate;
|
||||
_model = [[DemoResolveModel alloc] init];
|
||||
_config = [[DemoHttpdnsScenarioConfig alloc] init];
|
||||
_logBuffer = [NSMutableString string];
|
||||
_logQueue = dispatch_queue_create("com.new.httpdns.demo.log", DISPATCH_QUEUE_SERIAL);
|
||||
[self buildService];
|
||||
[self applyConfig:_config];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)buildService {
|
||||
DemoConfigLoader *cfg = [DemoConfigLoader shared];
|
||||
if (cfg.hasValidAccount) {
|
||||
if (cfg.aesSecretKey.length > 0) {
|
||||
self.service = [[HttpDnsService alloc] initWithAccountID:cfg.accountID secretKey:cfg.secretKey aesSecretKey:cfg.aesSecretKey];
|
||||
} else {
|
||||
self.service = [[HttpDnsService alloc] initWithAccountID:cfg.accountID secretKey:cfg.secretKey];
|
||||
}
|
||||
} else {
|
||||
self.service = [HttpDnsService sharedInstance];
|
||||
}
|
||||
[self.service setLogEnabled:YES];
|
||||
[self.service setNetworkingTimeoutInterval:8];
|
||||
[self.service setDegradeToLocalDNSEnabled:YES];
|
||||
self.service.ttlDelegate = self;
|
||||
[self.service setLogHandler:self];
|
||||
}
|
||||
|
||||
- (void)applyConfig:(DemoHttpdnsScenarioConfig *)config {
|
||||
self.config = [config copy];
|
||||
self.model.host = self.config.host;
|
||||
self.model.ipType = self.config.ipType;
|
||||
[self.service setHTTPSRequestEnabled:self.config.httpsEnabled];
|
||||
[self.service setPersistentCacheIPEnabled:self.config.persistentCacheEnabled];
|
||||
[self.service setReuseExpiredIPEnabled:self.config.reuseExpiredIPEnabled];
|
||||
}
|
||||
|
||||
- (void)resolveSyncNonBlocking {
|
||||
NSString *queryHost = [self currentHost];
|
||||
HttpdnsQueryIPType ipType = self.config.ipType;
|
||||
NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
||||
HttpdnsResult *result = [self.service resolveHostSyncNonBlocking:queryHost byIpType:ipType];
|
||||
[self handleResult:result host:queryHost ipType:ipType start:startMs];
|
||||
}
|
||||
|
||||
- (void)resolveSync {
|
||||
NSString *queryHost = [self currentHost];
|
||||
HttpdnsQueryIPType ipType = self.config.ipType;
|
||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
||||
NSTimeInterval startMs = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
||||
HttpdnsResult *result = [self.service resolveHostSync:queryHost byIpType:ipType];
|
||||
[self handleResult:result host:queryHost ipType:ipType start:startMs];
|
||||
});
|
||||
}
|
||||
|
||||
- (NSString *)logSnapshot {
|
||||
__block NSString *snapshot = @"";
|
||||
dispatch_sync(self.logQueue, ^{
|
||||
snapshot = [self.logBuffer copy];
|
||||
});
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
- (NSString *)currentHost {
|
||||
return self.config.host.length > 0 ? self.config.host : @"www.new.com";
|
||||
}
|
||||
|
||||
- (void)handleResult:(HttpdnsResult *)result host:(NSString *)host ipType:(HttpdnsQueryIPType)ipType start:(NSTimeInterval)startMs {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.model.host = host;
|
||||
self.model.ipType = ipType;
|
||||
[self.model updateWithResult:result startTimeMs:startMs];
|
||||
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
||||
if (delegate != nil) {
|
||||
[delegate scenario:self didUpdateModel:self.model];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (void)log:(NSString *)logStr {
|
||||
if (logStr.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSString *line = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], logStr];
|
||||
// 使用串行队列保证日志追加与快照的一致性
|
||||
dispatch_async(self.logQueue, ^{
|
||||
[self.logBuffer appendString:line];
|
||||
id<DemoHttpdnsScenarioDelegate> delegate = self.delegate;
|
||||
if (delegate != nil) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[delegate scenario:self didAppendLogLine:line];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (int64_t)httpdnsHost:(NSString *)host ipType:(NewHttpDNS_IPType)ipType ttl:(int64_t)ttl {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// DemoLogViewController.h
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface DemoLogViewController : UIViewController
|
||||
|
||||
- (void)setInitialText:(NSString *)text;
|
||||
- (void)appendLine:(NSString *)line;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// DemoLogViewController.m
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoLogViewController.h"
|
||||
|
||||
@interface DemoLogViewController ()
|
||||
|
||||
@property (nonatomic, strong) UITextView *textView;
|
||||
@property (nonatomic, copy) NSString *pendingInitialText;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DemoLogViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.title = @"日志";
|
||||
self.view.backgroundColor = [UIColor systemBackgroundColor];
|
||||
|
||||
self.textView = [[UITextView alloc] initWithFrame:CGRectZero];
|
||||
self.textView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.textView.editable = NO;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.textView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
} else {
|
||||
self.textView.font = [UIFont systemFontOfSize:12];
|
||||
}
|
||||
[self.view addSubview:self.textView];
|
||||
[NSLayoutConstraint activateConstraints:@[[self.textView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant:8], [self.textView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:12], [self.textView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-12], [self.textView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor constant:-8]]];
|
||||
|
||||
if (self.pendingInitialText.length > 0) {
|
||||
self.textView.text = self.pendingInitialText;
|
||||
self.pendingInitialText = nil;
|
||||
}
|
||||
|
||||
UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"关闭" style:UIBarButtonItemStylePlain target:self action:@selector(onClose)];
|
||||
self.navigationItem.leftBarButtonItem = close;
|
||||
}
|
||||
|
||||
- (void)onClose {
|
||||
[self dismissViewControllerAnimated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)setInitialText:(NSString *)text {
|
||||
if (self.isViewLoaded) {
|
||||
self.textView.text = text ?: @"";
|
||||
[self.textView scrollRangeToVisible:NSMakeRange(self.textView.text.length, 0)];
|
||||
} else {
|
||||
self.pendingInitialText = [text copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)appendLine:(NSString *)line {
|
||||
// 当日志较多时,直接追加可避免重排整块文本
|
||||
if (self.isViewLoaded) {
|
||||
NSString *append = line ?: @"";
|
||||
if (append.length > 0) {
|
||||
self.textView.text = [self.textView.text stringByAppendingString:append];
|
||||
[self.textView scrollRangeToVisible:NSMakeRange(self.textView.text.length, 0)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
27
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.h
Normal file
27
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.h
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// DemoResolveModel.h
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HttpdnsRequest.h"
|
||||
#import "HttpdnsResult.h"
|
||||
|
||||
@interface DemoResolveModel : NSObject
|
||||
|
||||
@property (nonatomic, copy) NSString *host;
|
||||
@property (nonatomic, assign) HttpdnsQueryIPType ipType;
|
||||
|
||||
@property (nonatomic, copy) NSArray<NSString *> *ipv4s;
|
||||
@property (nonatomic, copy) NSArray<NSString *> *ipv6s;
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval elapsedMs;
|
||||
@property (nonatomic, assign) NSTimeInterval ttlV4;
|
||||
@property (nonatomic, assign) NSTimeInterval ttlV6;
|
||||
|
||||
- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs;
|
||||
|
||||
@end
|
||||
|
||||
42
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.m
Normal file
42
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoResolveModel.m
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// DemoResolveModel.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoResolveModel.h"
|
||||
|
||||
@implementation DemoResolveModel
|
||||
|
||||
- (instancetype)init {
|
||||
if (self = [super init]) {
|
||||
_host = @"www.new.com";
|
||||
_ipType = HttpdnsQueryIPTypeBoth;
|
||||
_ipv4s = @[];
|
||||
_ipv6s = @[];
|
||||
_elapsedMs = 0;
|
||||
_ttlV4 = 0;
|
||||
_ttlV6 = 0;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateWithResult:(HttpdnsResult *)result startTimeMs:(NSTimeInterval)startMs {
|
||||
NSTimeInterval now = [[NSDate date] timeIntervalSince1970] * 1000.0;
|
||||
_elapsedMs = MAX(0, now - startMs);
|
||||
if (result != nil) {
|
||||
_ipv4s = result.ips ?: @[];
|
||||
_ipv6s = result.ipv6s ?: @[];
|
||||
_ttlV4 = result.ttl;
|
||||
_ttlV6 = result.v6ttl;
|
||||
} else {
|
||||
_ipv4s = @[];
|
||||
_ipv6s = @[];
|
||||
_ttlV4 = 0;
|
||||
_ttlV6 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
13
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.h
Normal file
13
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// DNSDemoViewController.h
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "DemoHttpdnsScenario.h"
|
||||
|
||||
@interface DemoViewController : UIViewController <DemoHttpdnsScenarioDelegate>
|
||||
|
||||
@end
|
||||
321
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.m
Normal file
321
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/DemoViewController.m
Normal file
@@ -0,0 +1,321 @@
|
||||
//
|
||||
// DNSDemoViewController.m
|
||||
// NewHttpDNSTestDemo
|
||||
//
|
||||
// @author Created by Claude Code on 2025-10-05
|
||||
//
|
||||
|
||||
#import "DemoViewController.h"
|
||||
#import "DemoResolveModel.h"
|
||||
#import "DemoLogViewController.h"
|
||||
#import "DemoHttpdnsScenario.h"
|
||||
|
||||
@interface DemoViewController () <DemoHttpdnsScenarioDelegate>
|
||||
|
||||
@property (nonatomic, strong) DemoHttpdnsScenario *scenario;
|
||||
@property (nonatomic, strong) DemoHttpdnsScenarioConfig *scenarioConfig;
|
||||
@property (nonatomic, strong) DemoResolveModel *model;
|
||||
|
||||
@property (nonatomic, strong) UIScrollView *scrollView;
|
||||
@property (nonatomic, strong) UIStackView *stack;
|
||||
|
||||
@property (nonatomic, strong) UITextField *hostField;
|
||||
@property (nonatomic, strong) UISegmentedControl *ipTypeSeg;
|
||||
|
||||
@property (nonatomic, strong) UISwitch *swHTTPS;
|
||||
@property (nonatomic, strong) UISwitch *swPersist;
|
||||
@property (nonatomic, strong) UISwitch *swReuse;
|
||||
|
||||
@property (nonatomic, strong) UILabel *elapsedLabel;
|
||||
@property (nonatomic, strong) UILabel *ttlLabel;
|
||||
|
||||
@property (nonatomic, strong) UITextView *resultTextView;
|
||||
|
||||
@property (nonatomic, weak) DemoLogViewController *presentedLogVC;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DemoViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.title = @"HTTPDNS Demo";
|
||||
self.view.backgroundColor = [UIColor systemBackgroundColor];
|
||||
self.scenario = [[DemoHttpdnsScenario alloc] initWithDelegate:self];
|
||||
self.model = self.scenario.model;
|
||||
self.scenarioConfig = [[DemoHttpdnsScenarioConfig alloc] init];
|
||||
[self buildUI];
|
||||
[self reloadUIFromModel:self.model];
|
||||
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"日志" style:UIBarButtonItemStylePlain target:self action:@selector(onShowLog)];
|
||||
}
|
||||
|
||||
- (void)buildUI {
|
||||
self.scrollView = [[UIScrollView alloc] init];
|
||||
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.view addSubview:self.scrollView];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.scrollView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
|
||||
[self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
|
||||
[self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
|
||||
[self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]
|
||||
]];
|
||||
|
||||
self.stack = [[UIStackView alloc] init];
|
||||
self.stack.axis = UILayoutConstraintAxisVertical;
|
||||
self.stack.spacing = 12.0;
|
||||
self.stack.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.scrollView addSubview:self.stack];
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[self.stack.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor constant:16],
|
||||
[self.stack.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor constant:16],
|
||||
[self.stack.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor constant:-16],
|
||||
[self.stack.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor constant:-16],
|
||||
[self.stack.widthAnchor constraintEqualToAnchor:self.view.widthAnchor constant:-32]
|
||||
]];
|
||||
|
||||
UIStackView *row1 = [self labeledRow:@"Host"];
|
||||
self.hostField = [[UITextField alloc] init];
|
||||
self.hostField.placeholder = @"www.new.com";
|
||||
self.hostField.text = self.scenarioConfig.host;
|
||||
self.hostField.borderStyle = UITextBorderStyleRoundedRect;
|
||||
[row1 addArrangedSubview:self.hostField];
|
||||
[self.stack addArrangedSubview:row1];
|
||||
|
||||
UIStackView *row2 = [self labeledRow:@"IP Type"];
|
||||
self.ipTypeSeg = [[UISegmentedControl alloc] initWithItems:@[@"IPv4", @"IPv6", @"Both"]];
|
||||
self.ipTypeSeg.selectedSegmentIndex = [self segmentIndexForIpType:self.scenarioConfig.ipType];
|
||||
[self.ipTypeSeg addTarget:self action:@selector(onIPTypeChanged:) forControlEvents:UIControlEventValueChanged];
|
||||
[row2 addArrangedSubview:self.ipTypeSeg];
|
||||
[self.stack addArrangedSubview:row2];
|
||||
|
||||
UIStackView *opts = [[UIStackView alloc] init];
|
||||
opts.axis = UILayoutConstraintAxisHorizontal;
|
||||
opts.alignment = UIStackViewAlignmentCenter;
|
||||
opts.distribution = UIStackViewDistributionFillEqually;
|
||||
opts.spacing = 8;
|
||||
[self.stack addArrangedSubview:opts];
|
||||
|
||||
[opts addArrangedSubview:[self switchItem:@"HTTPS" action:@selector(onToggleHTTPS:) out:&_swHTTPS]];
|
||||
[opts addArrangedSubview:[self switchItem:@"持久化" action:@selector(onTogglePersist:) out:&_swPersist]];
|
||||
[opts addArrangedSubview:[self switchItem:@"复用过期" action:@selector(onToggleReuse:) out:&_swReuse]];
|
||||
|
||||
self.swHTTPS.on = self.scenarioConfig.httpsEnabled;
|
||||
self.swPersist.on = self.scenarioConfig.persistentCacheEnabled;
|
||||
self.swReuse.on = self.scenarioConfig.reuseExpiredIPEnabled;
|
||||
[self applyOptionSwitches];
|
||||
|
||||
UIStackView *actions = [[UIStackView alloc] init];
|
||||
actions.axis = UILayoutConstraintAxisHorizontal;
|
||||
actions.spacing = 12;
|
||||
actions.distribution = UIStackViewDistributionFillEqually;
|
||||
[self.stack addArrangedSubview:actions];
|
||||
|
||||
UIButton *btnAsync = [self filledButton:@"Resolve (SyncNonBlocing)" action:@selector(onResolveAsync)];
|
||||
UIButton *btnSync = [self borderButton:@"Resolve (Sync)" action:@selector(onResolveSync)];
|
||||
[actions addArrangedSubview:btnAsync];
|
||||
[actions addArrangedSubview:btnSync];
|
||||
|
||||
UIStackView *info = [self labeledRow:@"Info"];
|
||||
self.elapsedLabel = [self monoLabel:@"elapsed: - ms"];
|
||||
self.ttlLabel = [self monoLabel:@"ttl v4/v6: -/- s"];
|
||||
[info addArrangedSubview:self.elapsedLabel];
|
||||
[info addArrangedSubview:self.ttlLabel];
|
||||
[self.stack addArrangedSubview:info];
|
||||
if (@available(iOS 11.0, *)) { [self.stack setCustomSpacing:24 afterView:info]; }
|
||||
|
||||
|
||||
UILabel *resultTitle = [[UILabel alloc] init];
|
||||
resultTitle.text = @"结果";
|
||||
resultTitle.font = [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline];
|
||||
[self.stack addArrangedSubview:resultTitle];
|
||||
|
||||
self.resultTextView = [[UITextView alloc] init];
|
||||
self.resultTextView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
self.resultTextView.editable = NO;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
self.resultTextView.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
self.resultTextView.textColor = [UIColor labelColor];
|
||||
} else {
|
||||
self.resultTextView.font = [UIFont systemFontOfSize:12];
|
||||
self.resultTextView.textColor = [UIColor blackColor];
|
||||
}
|
||||
self.resultTextView.backgroundColor = [UIColor clearColor];
|
||||
self.resultTextView.textContainerInset = UIEdgeInsetsMake(8, 12, 8, 12);
|
||||
[self.stack addArrangedSubview:self.resultTextView];
|
||||
[self.resultTextView.heightAnchor constraintEqualToConstant:320].active = YES;
|
||||
|
||||
|
||||
}
|
||||
|
||||
- (UIStackView *)labeledRow:(NSString *)title {
|
||||
UIStackView *row = [[UIStackView alloc] init];
|
||||
row.axis = UILayoutConstraintAxisHorizontal;
|
||||
row.alignment = UIStackViewAlignmentCenter;
|
||||
row.spacing = 8.0;
|
||||
UILabel *label = [[UILabel alloc] init];
|
||||
label.text = title;
|
||||
[label.widthAnchor constraintGreaterThanOrEqualToConstant:64].active = YES;
|
||||
[row addArrangedSubview:label];
|
||||
return row;
|
||||
}
|
||||
|
||||
- (UILabel *)monoLabel:(NSString *)text {
|
||||
UILabel *l = [[UILabel alloc] init];
|
||||
l.text = text;
|
||||
if (@available(iOS 13.0, *)) {
|
||||
l.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
|
||||
} else {
|
||||
l.font = [UIFont systemFontOfSize:12];
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
- (UIButton *)filledButton:(NSString *)title action:(SEL)sel {
|
||||
UIButton *b = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[b setTitle:title forState:UIControlStateNormal];
|
||||
b.backgroundColor = [UIColor systemBlueColor];
|
||||
[b setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
|
||||
b.layer.cornerRadius = 8;
|
||||
[b.heightAnchor constraintEqualToConstant:44].active = YES;
|
||||
[b addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
|
||||
return b;
|
||||
}
|
||||
|
||||
- (UIButton *)borderButton:(NSString *)title action:(SEL)sel {
|
||||
UIButton *b = [UIButton buttonWithType:UIButtonTypeSystem];
|
||||
[b setTitle:title forState:UIControlStateNormal];
|
||||
b.layer.borderWidth = 1;
|
||||
b.layer.borderColor = [UIColor systemBlueColor].CGColor;
|
||||
b.layer.cornerRadius = 8;
|
||||
[b.heightAnchor constraintEqualToConstant:44].active = YES;
|
||||
[b addTarget:self action:sel forControlEvents:UIControlEventTouchUpInside];
|
||||
return b;
|
||||
}
|
||||
|
||||
- (UIView *)switchItem:(NSString *)title action:(SEL)sel out:(UISwitch * __strong *)outSwitch {
|
||||
UIStackView *box = [[UIStackView alloc] init];
|
||||
box.axis = UILayoutConstraintAxisVertical;
|
||||
box.alignment = UIStackViewAlignmentCenter;
|
||||
UILabel *l = [[UILabel alloc] init];
|
||||
l.text = title;
|
||||
UISwitch *s = [[UISwitch alloc] init];
|
||||
[s addTarget:self action:sel forControlEvents:UIControlEventValueChanged];
|
||||
[box addArrangedSubview:s];
|
||||
[box addArrangedSubview:l];
|
||||
if (outSwitch != NULL) {
|
||||
*outSwitch = s;
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
- (NSInteger)segmentIndexForIpType:(HttpdnsQueryIPType)ipType {
|
||||
switch (ipType) {
|
||||
case HttpdnsQueryIPTypeIpv4: { return 0; }
|
||||
case HttpdnsQueryIPTypeIpv6: { return 1; }
|
||||
default: { return 2; }
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onIPTypeChanged:(UISegmentedControl *)seg {
|
||||
HttpdnsQueryIPType type = HttpdnsQueryIPTypeBoth;
|
||||
switch (seg.selectedSegmentIndex) {
|
||||
case 0: type = HttpdnsQueryIPTypeIpv4; break;
|
||||
case 1: type = HttpdnsQueryIPTypeIpv6; break;
|
||||
default: type = HttpdnsQueryIPTypeBoth; break;
|
||||
}
|
||||
self.model.ipType = type;
|
||||
self.scenarioConfig.ipType = type;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
}
|
||||
|
||||
- (void)applyOptionSwitches {
|
||||
self.scenarioConfig.httpsEnabled = self.swHTTPS.isOn;
|
||||
self.scenarioConfig.persistentCacheEnabled = self.swPersist.isOn;
|
||||
self.scenarioConfig.reuseExpiredIPEnabled = self.swReuse.isOn;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
}
|
||||
|
||||
- (void)onToggleHTTPS:(UISwitch *)s { [self applyOptionSwitches]; }
|
||||
- (void)onTogglePersist:(UISwitch *)s { [self applyOptionSwitches]; }
|
||||
- (void)onToggleReuse:(UISwitch *)s { [self applyOptionSwitches]; }
|
||||
|
||||
- (void)onResolveAsync {
|
||||
[self.view endEditing:YES];
|
||||
NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"www.new.com";
|
||||
self.model.host = host;
|
||||
self.scenarioConfig.host = host;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
[self.scenario resolveSyncNonBlocking];
|
||||
}
|
||||
|
||||
- (void)onResolveSync {
|
||||
[self.view endEditing:YES];
|
||||
NSString *host = self.hostField.text.length > 0 ? self.hostField.text : @"www.new.com";
|
||||
self.model.host = host;
|
||||
self.scenarioConfig.host = host;
|
||||
[self.scenario applyConfig:self.scenarioConfig];
|
||||
[self.scenario resolveSync];
|
||||
}
|
||||
|
||||
- (void)onShowLog {
|
||||
DemoLogViewController *logVC = [DemoLogViewController new];
|
||||
[logVC setInitialText:[self.scenario logSnapshot]];
|
||||
self.presentedLogVC = logVC;
|
||||
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:logVC];
|
||||
nav.modalPresentationStyle = UIModalPresentationAutomatic;
|
||||
[self presentViewController:nav animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)reloadUIFromModel:(DemoResolveModel *)model {
|
||||
self.model = model;
|
||||
if (![self.hostField isFirstResponder]) {
|
||||
self.hostField.text = model.host;
|
||||
}
|
||||
NSInteger segIndex = [self segmentIndexForIpType:model.ipType];
|
||||
if (self.ipTypeSeg.selectedSegmentIndex != segIndex) {
|
||||
self.ipTypeSeg.selectedSegmentIndex = segIndex;
|
||||
}
|
||||
self.elapsedLabel.text = [NSString stringWithFormat:@"elapsed: %.0f ms", model.elapsedMs];
|
||||
self.ttlLabel.text = [NSString stringWithFormat:@"ttl v4/v6: %.0f/%.0f s", model.ttlV4, model.ttlV6];
|
||||
self.resultTextView.text = [self buildJSONText:model];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *)buildJSONText:(DemoResolveModel *)model {
|
||||
NSString *ipTypeStr = @"both";
|
||||
switch (model.ipType) {
|
||||
case HttpdnsQueryIPTypeIpv4: { ipTypeStr = @"ipv4"; break; }
|
||||
case HttpdnsQueryIPTypeIpv6: { ipTypeStr = @"ipv6"; break; }
|
||||
default: { ipTypeStr = @"both"; break; }
|
||||
}
|
||||
NSDictionary *dict = @{
|
||||
@"host": model.host ?: @"",
|
||||
@"ipType": ipTypeStr,
|
||||
@"elapsedMs": @(model.elapsedMs),
|
||||
@"ttl": @{ @"v4": @(model.ttlV4), @"v6": @(model.ttlV6) },
|
||||
@"ipv4": model.ipv4s ?: @[],
|
||||
@"ipv6": model.ipv6s ?: @[]
|
||||
};
|
||||
NSError *err = nil;
|
||||
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:&err];
|
||||
if (data == nil || err != nil) {
|
||||
return [NSString stringWithFormat:@"{\n \"error\": \"%@\"\n}", err.localizedDescription ?: @"json serialize failed"];
|
||||
}
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
#pragma mark - DemoHttpdnsScenarioDelegate
|
||||
|
||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didUpdateModel:(DemoResolveModel *)model {
|
||||
[self reloadUIFromModel:model];
|
||||
}
|
||||
|
||||
- (void)scenario:(DemoHttpdnsScenario *)scenario didAppendLogLine:(NSString *)line {
|
||||
if (self.presentedLogVC != nil) {
|
||||
[self.presentedLogVC appendLine:line];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
48
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/Info.plist
Normal file
48
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/Info.plist
Normal file
@@ -0,0 +1,48 @@
|
||||
<?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>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
16
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/main.m
Normal file
16
HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo/main.m
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// main.m
|
||||
// TrustHttpDNSTestDemo
|
||||
//
|
||||
// Created by junmo on 2018/8/3.
|
||||
// Copyright © 2018<EFBFBD><EFBFBD>?trustapp.com. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user