更换腾讯cos上传

This commit is contained in:
启星
2025-10-24 10:52:40 +08:00
parent 22ba9e1070
commit 3a5cf56099
415 changed files with 47343 additions and 11864 deletions

View File

@@ -0,0 +1,18 @@
//
// QCloudThreadSafeMutableDictionary.h
// Pods
//
// Created by Dong Zhao on 2017/3/31.
//
//
#import <Foundation/Foundation.h>
@interface QCloudThreadSafeMutableDictionary : NSObject
- (id)objectForKey:(id)aKey;
- (void)removeObjectForKey:(id)aKey;
- (void)removeObject:(id)object;
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey;
- (NSArray *)allKeys;
- (NSArray *)allValues;
@end

View File

@@ -0,0 +1,78 @@
//
// QCloudThreadSafeMutableDictionary.m
// Pods
//
// Created by Dong Zhao on 2017/3/31.
//
//
#import "QCloudThreadSafeMutableDictionary.h"
@interface QCloudThreadSafeMutableDictionary ()
@property (nonatomic, strong) NSMutableDictionary *dictionary;
@property (nonatomic, strong) dispatch_queue_t dispatchQueue;
@end
@implementation QCloudThreadSafeMutableDictionary
- (instancetype)init {
if (self = [super init]) {
_dictionary = [NSMutableDictionary new];
_dispatchQueue = dispatch_queue_create("com.tencent.qcloud.safedicationary", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (id)objectForKey:(id)aKey {
__block id returnObject = nil;
dispatch_sync(self.dispatchQueue, ^{
returnObject = [self.dictionary objectForKey:aKey];
});
return returnObject;
}
- (void)removeObjectForKey:(id)aKey {
dispatch_sync(self.dispatchQueue, ^{
[self.dictionary removeObjectForKey:aKey];
});
}
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey {
dispatch_sync(self.dispatchQueue, ^{
[self.dictionary setObject:anObject forKey:aKey];
});
}
- (NSArray *)allKeys {
__block NSArray *allKeys = nil;
dispatch_sync(self.dispatchQueue, ^{
allKeys = [self.dictionary allKeys];
});
return allKeys;
}
- (NSArray *)allValues {
__block NSArray *allValues = nil;
dispatch_sync(self.dispatchQueue, ^{
allValues = [self.dictionary allValues];
});
return allValues;
}
- (void)removeObject:(id)object {
dispatch_sync(self.dispatchQueue, ^{
for (NSString *key in self.dictionary) {
if (object == self.dictionary[key]) {
[self.dictionary removeObjectForKey:key];
break;
}
}
});
}
@end

View File

@@ -0,0 +1,23 @@
//
// QCloudCustomLoader.h
// Pods
//
// Created by garenwang on 2024/12/30.
//
#import <Foundation/Foundation.h>
#import "QCloudCustomSession.h"
#import "QCloudCustomLoaderTask.h"
#import "QCloudHTTPRequest.h"
NS_ASSUME_NONNULL_BEGIN
@protocol QCloudCustomLoader <NSObject>
-(QCloudCustomSession *)session;
-(BOOL)enable:(QCloudHTTPRequest *)httpRequest;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,35 @@
//
// QCloudCustomLoaderTask.h
// Pods-QCloudCOSXMLDemo
//
// Created by garenwang on 2024/12/26.
//
#import <Foundation/Foundation.h>
@class QCloudCustomSession;
NS_ASSUME_NONNULL_BEGIN
@interface QCloudCustomLoaderTask : NSURLSessionDataTask
@property (nullable, readwrite, copy) NSHTTPURLResponse *response;
@property (nullable, readwrite, copy) NSURLRequest *currentRequest;
@property (nullable, readwrite, copy) NSURLRequest *originalRequest;
@property (atomic, assign) int64_t countOfBytesSent;
@property (atomic, assign) int64_t countOfBytesExpectedToSend;
/// 子类实现用于构建自定义task。
/// - Parameters:
/// - httpRequest: SDK 构建好的 NSMutableURLRequest示例对象。
/// - fromFile: 上传文件的本地路径,只有上传文件格式为文件路径时才有值。
/// - session: 自定义session QCloudCustomSession的子类实例。
- (instancetype)initWithHTTPRequest:(NSMutableURLRequest *)httpRequest
fromFile:(NSURL *)fromFile
session:(QCloudCustomSession *)session;
/// 子类实现,用于开启任务。
-(void)resume;
/// 子类实现,用于取消当前任务。
- (void)cancel;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,37 @@
//
// QCloudCustomLoaderTask.m
// Pods-QCloudCOSXMLDemo
//
// Created by garenwang on 2024/12/26.
//
#import "QCloudCustomLoaderTask.h"
#import "QCloudCustomSession.h"
#import "QCloudError.h"
@interface QCloudCustomLoaderTask ()
@property (nonatomic,strong)NSMutableURLRequest * httpRequest;
@property (nonatomic,strong)QCloudCustomSession * session;
@end
@implementation QCloudCustomLoaderTask
@synthesize response = _response;
@synthesize originalRequest = _originalRequest;
@synthesize currentRequest = _currentRequest;
@synthesize countOfBytesSent = _countOfBytesSent;
@synthesize countOfBytesExpectedToSend = _countOfBytesExpectedToSend;
- (instancetype)initWithHTTPRequest:(NSMutableURLRequest *)httpRequest
fromFile:(NSURL *)fromFile
session:(QCloudCustomSession *)session{
@throw [NSException exceptionWithName:QCloudErrorDomain reason:@"请在子类中实现" userInfo:nil];
}
-(void)resume{
@throw [NSException exceptionWithName:QCloudErrorDomain reason:@"请在子类中实现" userInfo:nil];
}
- (void)cancel{
@throw [NSException exceptionWithName:QCloudErrorDomain reason:@"请在子类中实现" userInfo:nil];
}
@end

View File

@@ -0,0 +1,91 @@
//
// QCloudCustomSession.h
// Pods-QCloudCOSXMLDemo
//
// Created by garenwang on 2024/12/26.
//
#import <Foundation/Foundation.h>
@class QCloudCustomLoaderTask;
NS_ASSUME_NONNULL_BEGIN
@interface QCloudCustomSession : NSURLSession
@property (nonatomic,
weak)id<NSURLSessionTaskDelegate, NSURLSessionDataDelegate>customDelegate;
/// 需要子类实现由COS SDK 进行回调。
/// - Parameters:
/// - request: SDK 传出来的请求实例。
/// - fromFile: 以文件路径进行上传时的本地文件路径。
-(QCloudCustomLoaderTask *)taskWithRequset:(NSMutableURLRequest *)request
fromFile:(NSURL *)fromFile;
/// 以下方法无需子类实现。供业务层调用用于将自定义网络相应数据传给COS SDK。
/******************************************************************************/
/// 处理数据任务接收到响应时的情况
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - response:请求响应数据
/// - completionHandler: 完成回调
- (void)customTask:(QCloudCustomLoaderTask *)task didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler;
/// 监控上传任务的进度
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - bytesSent: 当前发送数据
/// - totalBytesSent: 总共发送数据
/// - totalBytesExpectedToSend: 总共待发送数据
- (void)customTask:(QCloudCustomLoaderTask *)task didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
/// 接受到数据
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - data: 接受到的数据
- (void)customTask:(QCloudCustomLoaderTask *)task didReceiveData:(NSData *)data;
/// 任务完成
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - error: 错误信息, SDK内会根据error 中的错误信息判断是否需要重试。
- (void)customTask:(QCloudCustomLoaderTask *)task didCompleteWithError:(NSError *)error;
/// 处理身份验证
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - challenge: NSURLAuthenticationChallenge
/// - completionHandler: 完成回调
- (void)customTask:(QCloudCustomLoaderTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *_Nonnull)challenge
completionHandler:(void (^_Nonnull)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *_Nullable credential))completionHandler;
/// 收集性能参数
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - metrics: NSURLSessionTaskMetrics 请求性能参数
- (void)customTask:(QCloudCustomLoaderTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(ios(10.0));
/// 处理重定向
/// - Parameters:
/// - task: 自定义Task QCloudCustomLoaderTask子类
/// - response: 请求响应
/// - request: 重定向的请求
/// - completionHandler: 完成回调
- (void)customTask:(QCloudCustomLoaderTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,67 @@
//
// QCloudCustomSession.m
// Pods-QCloudCOSXMLDemo
//
// Created by garenwang on 2024/12/26.
//
#import "QCloudCustomSession.h"
#import "QCloudCustomLoaderTask.h"
#import "QCloudError.h"
@implementation QCloudCustomSession
-(QCloudCustomLoaderTask *)taskWithRequset:(NSMutableURLRequest *)request
fromFile:(NSURL *)fromFile{
@throw [NSException exceptionWithName:QCloudErrorDomain reason:@"请在子类中实现" userInfo:nil];
}
- (void)customTask:(QCloudCustomLoaderTask *)task didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
if ([self.customDelegate respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) {
task.response = (NSHTTPURLResponse *)response;
[self.customDelegate URLSession:self dataTask:task didReceiveResponse:response completionHandler:completionHandler];
}
}
- (void)customTask:(QCloudCustomLoaderTask *)task didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{
if ([self.customDelegate respondsToSelector:@selector(URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)]) {
[self.customDelegate URLSession:self task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
}
}
- (void)customTask:(QCloudCustomLoaderTask *)task didReceiveData:(NSData *)data{
if ([self.customDelegate respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) {
[self.customDelegate URLSession:self dataTask:task didReceiveData:data];
}
}
- (void)customTask:(QCloudCustomLoaderTask *)task didCompleteWithError:(NSError *)error{
if ([self.customDelegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) {
[self.customDelegate URLSession:self task:task didCompleteWithError:error];
}
}
- (void)customTask:(QCloudCustomLoaderTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *_Nonnull)challenge
completionHandler:(void (^_Nonnull)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *_Nullable credential))completionHandler{
if ([self.customDelegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) {
[self.customDelegate URLSession:self task:task didReceiveChallenge:challenge completionHandler:completionHandler];
}
}
- (void)customTask:(QCloudCustomLoaderTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(ios(10.0)){
if ([self.customDelegate respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
[self.customDelegate URLSession:self task:task didFinishCollectingMetrics:metrics];
}
}
- (void)customTask:(QCloudCustomLoaderTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler{
if ([self.customDelegate respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) {
task.currentRequest = request;
[self.customDelegate URLSession:self task:task willPerformHTTPRedirection:response newRequest:request completionHandler:completionHandler];
}
}
@end

View File

@@ -0,0 +1,27 @@
//
// QCloudLoaderManager.h
// Pods-QCloudCOSXMLDemo
//
// Created by garenwang on 2024/12/27.
//
#import <Foundation/Foundation.h>
#import "QCloudCustomSession.h"
#import "QCloudCustomLoader.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudLoaderManager :NSObject
@property (nonatomic,assign)BOOL enable;
@property (atomic,strong,readonly)NSMutableArray <id <QCloudCustomLoader>> * loaders;
- (void)addLoader:(id <QCloudCustomLoader>)loader;
+ (QCloudLoaderManager *)manager;
-(id <QCloudCustomLoader>)getAvailableLoader:(QCloudHTTPRequest *)httpRequest;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,52 @@
//
// QCloudLoaderManager.m
// Pods-QCloudCOSXMLDemo
//
// Created by garenwang on 2024/12/27.
//
#import "QCloudLoaderManager.h"
#import "QCloudHTTPRequest.h"
#import "QCloudHTTPSessionManager.h"
@interface QCloudLoaderManager()
@property (atomic,strong)NSMutableArray <id <QCloudCustomLoader>> * loaders;
@end
@implementation QCloudLoaderManager
+ (QCloudLoaderManager *)manager {
static QCloudLoaderManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[QCloudLoaderManager alloc] init];
});
return manager;
}
- (instancetype)init
{
self = [super init];
if (self) {
self.loaders = [NSMutableArray new];
}
return self;
}
-(id <QCloudCustomLoader>)getAvailableLoader:(QCloudHTTPRequest *)httpRequest{
for (int i = 0; i < self.loaders.count; i ++) {
if ([self.loaders[i] enable:httpRequest]) {
return self.loaders[i];
}
}
return nil;
}
-(void)addLoader:(id <QCloudCustomLoader>)loader{
@synchronized (self) {
loader.session.customDelegate = [QCloudHTTPSessionManager shareClient];
[self.loaders addObject:loader];
}
}
@end

View File

@@ -0,0 +1,29 @@
//
// NSDate+QCloudComapre.h
// Pods
//
// Created by Dong Zhao on 2017/4/5.
//
//
#import <Foundation/Foundation.h>
@interface NSDate (QCloudComapre)
- (BOOL)qcloud_isEarlierThan:(NSDate *)date;
/**
* Returns a YES if receiver is later than provided comparison date, otherwise returns NO
*
* @param date NSDate - Provided date for comparison
*
* @return BOOL representing comparison result
*/
- (BOOL)qcloud_isLaterThan:(NSDate *)date;
/**
* Returns a YES if receiver is earlier than or equal to the provided comparison date, otherwise returns NO
*
* @param date NSDate - Provided date for comparison
*
* @return BOOL representing comparison result
*/
- (BOOL)qcloud_isEarlierThanOrEqualTo:(NSDate *)date;
@end

View File

@@ -0,0 +1,55 @@
//
// NSDate+QCloudComapre.m
// Pods
//
// Created by Dong Zhao on 2017/4/5.
//
//
#import "NSDate+QCloudComapre.h"
@implementation NSDate (QCloudComapre)
#pragma mark Comparators
/**
* Returns a YES if receiver is earlier than provided comparison date, otherwise returns NO
*
* @param date NSDate - Provided date for comparison
*
* @return BOOL representing comparison result
*/
- (BOOL)qcloud_isEarlierThan:(NSDate *)date {
if (self.timeIntervalSince1970 < date.timeIntervalSince1970) {
return YES;
}
return NO;
}
/**
* Returns a YES if receiver is later than provided comparison date, otherwise returns NO
*
* @param date NSDate - Provided date for comparison
*
* @return BOOL representing comparison result
*/
- (BOOL)qcloud_isLaterThan:(NSDate *)date {
if (self.timeIntervalSince1970 > date.timeIntervalSince1970) {
return YES;
}
return NO;
}
/**
* Returns a YES if receiver is earlier than or equal to the provided comparison date, otherwise returns NO
*
* @param date NSDate - Provided date for comparison
*
* @return BOOL representing comparison result
*/
- (BOOL)qcloud_isEarlierThanOrEqualTo:(NSDate *)date {
if (self.timeIntervalSince1970 <= date.timeIntervalSince1970) {
return YES;
}
return NO;
}
@end

View File

@@ -0,0 +1,57 @@
//
// QCloudFCUUID.h
//
// Created by Fabio Caccamo on 26/06/14.
// Copyright © 2016 Fabio Caccamo. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString *const QCloudFCUUIDsOfUserDevicesDidChangeNotification;
@interface QCloudFCUUID : NSObject {
NSMutableDictionary *_uuidForKey;
NSString *_uuidForSession;
NSString *_uuidForInstallation;
NSString *_uuidForVendor;
NSString *_uuidForDevice;
NSString *_uuidsOfUserDevices;
BOOL _uuidsOfUserDevices_iCloudAvailable;
}
/**
每次运行应用都会变
*/
+ (NSString *)uuid;
/**
changes each time (no persistent), but allows to keep in memory more temporary uuids
*/
+ (NSString *)uuidForKey:(id<NSCopying>)key;
/**
每次运行应用都会变
*/
+ (NSString *)uuidForSession;
/**
重新安装的时候会变
*/
+ (NSString *)uuidForInstallation;
/**
卸载后重装会变
*/
+ (NSString *)uuidForVendor;
/**
抹掉iPhone的时候才会变适合做唯一标识
*/
+ (NSString *)uuidForDevice;
+ (NSString *)uuidForDeviceMigratingValue:(NSString *)value commitMigration:(BOOL)commitMigration;
+ (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key commitMigration:(BOOL)commitMigration;
+ (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service commitMigration:(BOOL)commitMigration;
+ (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key
service:(NSString *)service
accessGroup:(NSString *)accessGroup
commitMigration:(BOOL)commitMigration;
+ (NSArray *)uuidsOfUserDevices;
+ (NSArray *)uuidsOfUserDevicesExcludingCurrentDevice;
+ (BOOL)uuidValueIsValid:(NSString *)uuidValue;
@end

View File

@@ -0,0 +1,427 @@
//
// QCloudFCUUID.m
//
// Created by Fabio Caccamo on 26/06/14.
// Copyright © 2016 Fabio Caccamo. All rights reserved.
//
#import "QCloudFCUUID.h"
#import "QCloudUICKeyChainStore.h"
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
@implementation QCloudFCUUID
NSString *const QCloudFCUUIDsOfUserDevicesDidChangeNotification = @"QCloudFCUUIDsOfUserDevicesDidChangeNotification";
static NSString *const _uuidForInstallationKey = @"fc_uuidForInstallation";
static NSString *const _uuidForDeviceKey = @"fc_uuidForDevice";
static NSString *const _uuidsOfUserDevicesKey = @"fc_uuidsOfUserDevices";
static NSString *const _uuidsOfUserDevicesToggleKey = @"fc_uuidsOfUserDevicesToggle";
+ (QCloudFCUUID *)sharedInstance {
static QCloudFCUUID *instance = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
self = [super init];
if (self) {
[self uuidsOfUserDevices_iCloudInit];
}
return self;
}
- (NSString *)_getOrCreateValueForKey:(NSString *)key
defaultValue:(NSString *)defaultValue
userDefaults:(BOOL)userDefaults
keychain:(BOOL)keychain
service:(NSString *)service
accessGroup:(NSString *)accessGroup
synchronizable:(BOOL)synchronizable {
NSString *value = [self _getValueForKey:key userDefaults:userDefaults keychain:keychain service:service accessGroup:accessGroup];
if (!value) {
value = defaultValue;
}
if (!value) {
value = [self uuid];
}
[self _setValue:value
forKey:key
userDefaults:userDefaults
keychain:keychain
service:service
accessGroup:accessGroup
synchronizable:synchronizable];
return value;
}
- (NSString *)_getValueForKey:(NSString *)key
userDefaults:(BOOL)userDefaults
keychain:(BOOL)keychain
service:(NSString *)service
accessGroup:(NSString *)accessGroup {
NSString *value = nil;
if (!value && keychain) {
value = [QCloudUICKeyChainStore stringForKey:key service:service accessGroup:accessGroup];
}
if (!value && userDefaults) {
value = [[NSUserDefaults standardUserDefaults] stringForKey:key];
}
return value;
}
- (void)_setValue:(NSString *)value
forKey:(NSString *)key
userDefaults:(BOOL)userDefaults
keychain:(BOOL)keychain
service:(NSString *)service
accessGroup:(NSString *)accessGroup
synchronizable:(BOOL)synchronizable {
if (value && userDefaults) {
[[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
if (value && keychain) {
QCloudUICKeyChainStore *keychain = [QCloudUICKeyChainStore keyChainStoreWithService:service accessGroup:accessGroup];
[keychain setSynchronizable:synchronizable];
[keychain setString:value forKey:key];
}
}
- (NSString *)uuid {
// also known as qcloud_uuid/universallyUniqueIdentifier
CFUUIDRef uuidRef = CFUUIDCreate(NULL);
CFStringRef uuidStringRef = CFUUIDCreateString(NULL, uuidRef);
CFRelease(uuidRef);
NSString *uuidValue = (__bridge_transfer NSString *)uuidStringRef;
uuidValue = [uuidValue lowercaseString];
uuidValue = [uuidValue stringByReplacingOccurrencesOfString:@"-" withString:@""];
return uuidValue;
}
- (NSString *)uuidForKey:(id<NSCopying>)key {
if (_uuidForKey == nil) {
_uuidForKey = [[NSMutableDictionary alloc] init];
}
NSString *uuidValue = [_uuidForKey objectForKey:key];
if (uuidValue == nil) {
uuidValue = [self uuid];
[_uuidForKey setObject:uuidValue forKey:key];
}
return uuidValue;
}
- (NSString *)uuidForSession {
if (_uuidForSession == nil) {
_uuidForSession = [self uuid];
}
return _uuidForSession;
}
- (NSString *)uuidForInstallation {
if (_uuidForInstallation == nil) {
_uuidForInstallation = [self _getOrCreateValueForKey:_uuidForInstallationKey
defaultValue:nil
userDefaults:YES
keychain:NO
service:nil
accessGroup:nil
synchronizable:NO];
}
return _uuidForInstallation;
}
- (NSString *)uuidForVendor {
if (_uuidForVendor == nil) {
#if TARGET_OS_IOS
_uuidForVendor = [[[[[UIDevice currentDevice] identifierForVendor] UUIDString] lowercaseString] stringByReplacingOccurrencesOfString:@"-"
withString:@""];
#elif TARGET_OS_MAC
_uuidForVendor = @"0000";
#endif
}
return _uuidForVendor;
}
- (void)uuidForDevice_updateWithValue:(NSString *)value {
_uuidForDevice = [NSString stringWithString:value];
[self _setValue:_uuidForDevice forKey:_uuidForDeviceKey userDefaults:YES keychain:YES service:nil accessGroup:nil synchronizable:NO];
}
- (NSString *)uuidForDevice {
// also known as udid/uniqueDeviceIdentifier but this doesn't persists to system reset
if (_uuidForDevice == nil) {
_uuidForDevice = [self _getOrCreateValueForKey:_uuidForDeviceKey
defaultValue:nil
userDefaults:YES
keychain:YES
service:nil
accessGroup:nil
synchronizable:NO];
}
return _uuidForDevice;
}
- (NSString *)uuidForDeviceMigratingValue:(NSString *)value commitMigration:(BOOL)commitMigration {
if ([self uuidValueIsValid:value]) {
NSString *oldValue = [self uuidForDevice];
NSString *newValue = [NSString stringWithString:value];
if ([oldValue isEqualToString:newValue]) {
return oldValue;
}
if (commitMigration) {
[self uuidForDevice_updateWithValue:newValue];
NSMutableOrderedSet *uuidsOfUserDevicesSet = [[NSMutableOrderedSet alloc] initWithArray:[self uuidsOfUserDevices]];
[uuidsOfUserDevicesSet addObject:newValue];
[uuidsOfUserDevicesSet removeObject:oldValue];
[self uuidsOfUserDevices_updateWithValue:[uuidsOfUserDevicesSet array]];
[self uuidsOfUserDevices_iCloudSync];
return [self uuidForDevice];
} else {
return oldValue;
}
} else {
[NSException raise:@"Invalid qcloud_uuid to migrate" format:@"qcloud_uuid value should be a string of 32 or 36 characters."];
return nil;
}
}
- (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key commitMigration:(BOOL)commitMigration {
return [self uuidForDeviceMigratingValueForKey:key service:nil accessGroup:nil commitMigration:commitMigration];
}
- (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service commitMigration:(BOOL)commitMigration {
return [self uuidForDeviceMigratingValueForKey:key service:service accessGroup:nil commitMigration:commitMigration];
}
- (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key
service:(NSString *)service
accessGroup:(NSString *)accessGroup
commitMigration:(BOOL)commitMigration {
NSString *uuidToMigrate = [self _getValueForKey:key userDefaults:YES keychain:YES service:service accessGroup:accessGroup];
return [self uuidForDeviceMigratingValue:uuidToMigrate commitMigration:commitMigration];
}
- (void)uuidsOfUserDevices_iCloudInit {
_uuidsOfUserDevices_iCloudAvailable = NO;
if (NSClassFromString(@"NSUbiquitousKeyValueStore")) {
NSUbiquitousKeyValueStore *iCloud = [NSUbiquitousKeyValueStore defaultStore];
if (iCloud) {
_uuidsOfUserDevices_iCloudAvailable = YES;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(uuidsOfUserDevices_iCloudChange:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:nil];
[self uuidsOfUserDevices_iCloudSync];
} else {
// NSLog(@"iCloud not available");
}
} else {
// NSLog(@"iOS < 5");
}
}
- (void)uuidsOfUserDevices_iCloudSync {
if (_uuidsOfUserDevices_iCloudAvailable) {
NSUbiquitousKeyValueStore *iCloud = [NSUbiquitousKeyValueStore defaultStore];
// if keychain contains more device identifiers than icloud, maybe that icloud has been empty, so re-write these identifiers to iCloud
for (NSString *uuidOfUserDevice in [self uuidsOfUserDevices]) {
NSString *uuidOfUserDeviceAsKey = [NSString stringWithFormat:@"%@_%@", _uuidForDeviceKey, uuidOfUserDevice];
if (![[iCloud stringForKey:uuidOfUserDeviceAsKey] isEqualToString:uuidOfUserDevice]) {
[iCloud setString:uuidOfUserDevice forKey:uuidOfUserDeviceAsKey];
}
}
// toggle a boolean value to force notification on other devices, useful for debug
[iCloud setBool:![iCloud boolForKey:_uuidsOfUserDevicesToggleKey] forKey:_uuidsOfUserDevicesToggleKey];
[iCloud synchronize];
}
}
- (void)uuidsOfUserDevices_iCloudChange:(NSNotification *)notification {
if (_uuidsOfUserDevices_iCloudAvailable) {
NSMutableOrderedSet *uuidsSet = [[NSMutableOrderedSet alloc] initWithArray:[self uuidsOfUserDevices]];
NSInteger uuidsCount = [uuidsSet count];
NSUbiquitousKeyValueStore *iCloud = [NSUbiquitousKeyValueStore defaultStore];
NSDictionary *iCloudDict = [iCloud dictionaryRepresentation];
// NSLog(@"uuidsOfUserDevicesSync: %@", iCloudDict);
[iCloudDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSString *uuidKey = (NSString *)key;
if ([uuidKey rangeOfString:_uuidForDeviceKey].location == 0) {
if ([obj isKindOfClass:[NSString class]]) {
NSString *uuidValue = (NSString *)obj;
if ([uuidKey rangeOfString:uuidValue].location != NSNotFound && [self uuidValueIsValid:uuidValue]) {
// NSLog(@"qcloud_uuid: %@", uuidValue);
[uuidsSet addObject:uuidValue];
} else {
// NSLog(@"invalid qcloud_uuid");
}
}
}
}];
if ([uuidsSet count] > uuidsCount) {
[self uuidsOfUserDevices_updateWithValue:[uuidsSet array]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[self uuidsOfUserDevices] forKey:@"uuidsOfUserDevices"];
[[NSNotificationCenter defaultCenter] postNotificationName:QCloudFCUUIDsOfUserDevicesDidChangeNotification object:self userInfo:userInfo];
}
}
}
- (void)uuidsOfUserDevices_updateWithValue:(NSArray *)value {
_uuidsOfUserDevices = [value componentsJoinedByString:@"|"];
[self _setValue:_uuidsOfUserDevices forKey:_uuidsOfUserDevicesKey userDefaults:YES keychain:YES service:nil accessGroup:nil synchronizable:YES];
}
- (NSArray *)uuidsOfUserDevices {
if (_uuidsOfUserDevices == nil) {
_uuidsOfUserDevices = [self _getOrCreateValueForKey:_uuidsOfUserDevicesKey
defaultValue:[self uuidForDevice]
userDefaults:YES
keychain:YES
service:nil
accessGroup:nil
synchronizable:YES];
}
return [_uuidsOfUserDevices componentsSeparatedByString:@"|"];
}
- (NSArray *)uuidsOfUserDevicesExcludingCurrentDevice {
NSMutableArray *uuids = [NSMutableArray arrayWithArray:[self uuidsOfUserDevices]];
[uuids removeObject:[self uuidForDevice]];
return [NSArray arrayWithArray:uuids];
}
- (BOOL)uuidValueIsValid:(NSString *)uuidValue {
if (uuidValue != nil) {
NSString *uuidPattern = @"^[0-9a-f]{32}|[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$";
NSRegularExpression *uuidRegExp = [NSRegularExpression regularExpressionWithPattern:uuidPattern
options:NSRegularExpressionCaseInsensitive
error:nil];
NSRange uuidValueRange = NSMakeRange(0, [uuidValue length]);
NSRange uuidMatchRange = [uuidRegExp rangeOfFirstMatchInString:uuidValue options:0 range:uuidValueRange];
NSString *uuidMatchValue;
if (!NSEqualRanges(uuidMatchRange, NSMakeRange(NSNotFound, 0))) {
uuidMatchValue = [uuidValue substringWithRange:uuidMatchRange];
if ([uuidMatchValue isEqualToString:uuidValue]) {
return YES;
} else {
return NO;
}
} else {
return NO;
}
} else {
return NO;
}
}
+ (NSString *)uuid {
return [[self sharedInstance] uuid];
}
+ (NSString *)uuidForKey:(id<NSCopying>)key {
return [[self sharedInstance] uuidForKey:key];
}
+ (NSString *)uuidForSession {
return [[self sharedInstance] uuidForSession];
}
+ (NSString *)uuidForInstallation {
return [[self sharedInstance] uuidForInstallation];
}
+ (NSString *)uuidForVendor {
return [[self sharedInstance] uuidForVendor];
}
+ (NSString *)uuidForDevice {
return [[self sharedInstance] uuidForDevice];
}
+ (NSString *)uuidForDeviceMigratingValue:(NSString *)value commitMigration:(BOOL)commitMigration {
return [[self sharedInstance] uuidForDeviceMigratingValue:value commitMigration:commitMigration];
}
+ (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key commitMigration:(BOOL)commitMigration {
return [[self sharedInstance] uuidForDeviceMigratingValueForKey:key service:nil accessGroup:nil commitMigration:commitMigration];
}
+ (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key service:(NSString *)service commitMigration:(BOOL)commitMigration {
return [[self sharedInstance] uuidForDeviceMigratingValueForKey:key service:service accessGroup:nil commitMigration:commitMigration];
}
+ (NSString *)uuidForDeviceMigratingValueForKey:(NSString *)key
service:(NSString *)service
accessGroup:(NSString *)accessGroup
commitMigration:(BOOL)commitMigration {
return [[self sharedInstance] uuidForDeviceMigratingValueForKey:key service:service accessGroup:accessGroup commitMigration:commitMigration];
}
+ (NSArray *)uuidsOfUserDevices {
return [[self sharedInstance] uuidsOfUserDevices];
}
+ (NSArray *)uuidsOfUserDevicesExcludingCurrentDevice {
return [[self sharedInstance] uuidsOfUserDevicesExcludingCurrentDevice];
}
+ (BOOL)uuidValueIsValid:(NSString *)uuidValue {
return [[self sharedInstance] uuidValueIsValid:uuidValue];
}
@end

View File

@@ -0,0 +1,17 @@
//
// UIDevice+QCloudFCUUID.h
//
// Created by Fabio Caccamo on 19/11/15.
// Copyright © 2015 Fabio Caccamo. All rights reserved.
//
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#import "QCloudFCUUID.h"
@interface UIDevice (QCloudFCUUID)
- (NSString *)qcloud_uuid;
@end
#endif

View File

@@ -0,0 +1,17 @@
//
// UIDevice+QCloudFCUUID.m
//
// Created by Fabio Caccamo on 19/11/15.
// Copyright © 2015 Fabio Caccamo. All rights reserved.
//
#if TARGET_OS_IOS
#import "UIDevice+QCloudFCUUID.h"
@implementation UIDevice (QCloudFCUUID)
- (NSString *)qcloud_uuid {
return [QCloudFCUUID uuidForDevice];
}
@end
#endif

View File

@@ -0,0 +1,19 @@
//
// QCloudEncryt.h
// Pods
//
// Created by Dong Zhao on 2017/6/6.
//
//
#import <Foundation/Foundation.h>
FOUNDATION_EXTERN NSString *QCloudEncrytNSDataMD5Base64(NSData *data);
FOUNDATION_EXPORT NSString *QCloudEncrytNSDataMD5(NSData *data);
FOUNDATION_EXTERN NSString *QCloudEncrytFileMD5Base64(NSString *filePath);
FOUNDATION_EXTERN NSString *QCloudEncrytFileMD5(NSString *filePath);
FOUNDATION_EXTERN NSString *QCloudEncrytFileOffsetMD5Base64(NSString *filePath, int64_t offset, int64_t siliceLength);
FOUNDATION_EXTERN NSString *QCloudEncrytFileOffsetMD5(NSString *filePath, int64_t offset, int64_t siliceLength);
FOUNDATION_EXTERN NSString *QCloudEncrytMD5String(NSString *originString);
FOUNDATION_EXTERN NSString *QCloudHmacSha1Encrypt(NSString *data, NSString *key);
FOUNDATION_EXTERN NSData *QCloudHmacEncrypt(NSString *data, NSString *key);

View File

@@ -0,0 +1,216 @@
//
// QCloudEncryt.m
// Pods
//
// Created by Dong Zhao on 2017/6/6.
//
//
#import "QCloudEncryt.h"
#import <CommonCrypto/CommonDigest.h>
#import <CommonCrypto/CommonCrypto.h>
#import "QCloudFileUtils.h"
#import <stdio.h>
#include <string.h>
#include <string>
@interface NSData(MD5Related)
- (NSString*)qcloud_MD5String;
@end
@implementation NSData(MD5Related)
- (NSString*) qcloud_MD5String {
if (!self) {
return nil;
}
const unsigned char* buf = (const unsigned char*)self.bytes;
NSMutableString* mutableString = [[NSMutableString alloc] init];
for (int i = 0; i < self.length; i++) {
[mutableString appendFormat:@"%02lX",(NSUInteger)buf[i]];
}
return [mutableString copy];
}
@end
NSString* QCloudEncrytNSDataMD5Base64(NSData* data)
{
if (!data) {
return nil;
}
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( data.bytes, (CC_LONG)data.length, result ); // This is the md5 call
NSData* md5data = [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
return [md5data base64EncodedStringWithOptions:0];
}
NSData* _internalEncrytFileMD5(NSString* filePath) {
if (!QCloudFileExist(filePath)) {
return nil;
}
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath];
if(!handle)
{
return nil;
}
CC_MD5_CTX md5;
CC_MD5_Init(&md5);
BOOL done = NO;
static NSUInteger MD5_CHUNK = 1024*16;
while (!done)
{
@autoreleasepool {
NSData *fileData = [handle readDataOfLength:MD5_CHUNK];
CC_MD5_Update(&md5, [fileData bytes], (CC_LONG)[fileData length]);
if([fileData length] == 0)
done = YES;
}
}
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(digest, &md5);
[handle closeFile];
NSData* md5data = [NSData dataWithBytes:digest length:CC_MD5_DIGEST_LENGTH];
return md5data;
}
NSString* QCloudEncrytFileMD5Base64(NSString* filePath) {
NSData* md5Data = _internalEncrytFileMD5(filePath);
if (!md5Data) {
return nil;
}
return [md5Data base64EncodedStringWithOptions:0];
}
NSData* _internalEncrytNSDataMD5(NSData* data) {
if (!data) {
return nil;
}
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( data.bytes, (CC_LONG)data.length, result ); // This is the md5 call
NSData* md5data = [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
return md5data;
}
NSString* QCloudEncrytNSDataMD5(NSData* data) {
NSData* md5data = _internalEncrytNSDataMD5(data);
if (!md5data) {
return nil;
}
return [md5data qcloud_MD5String];
}
NSString* QCloudEncrytFileMD5(NSString* filePath) {
NSData* md5data = _internalEncrytFileMD5(filePath);
return [md5data qcloud_MD5String];
}
NSData* _internalEncrytFileOffsetMD5(NSString* filePath, int64_t offset , int64_t siliceLength) {
if (!QCloudFileExist(filePath)) {
return nil;
}
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath];
if(!handle)
{
return nil;
}
if (QCloudFileSize(filePath) < offset+siliceLength) {
return nil;
}
[handle seekToFileOffset:offset];
CC_MD5_CTX md5;
CC_MD5_Init(&md5);
BOOL done = NO;
NSUInteger totalReadCount = 0;
static NSUInteger MD5_CHUNK = 1024*16;
while (!done)
{
if (totalReadCount >= siliceLength) {
break;
}
NSUInteger willReadLength = MIN(MD5_CHUNK, siliceLength-totalReadCount);
if (willReadLength == 0) {
break;
}
NSData *fileData = [handle readDataOfLength:willReadLength];
if (fileData.length == 0) {
break;
}
totalReadCount += fileData.length;
CC_MD5_Update(&md5, [fileData bytes], (CC_LONG)[fileData length]);
}
unsigned char digest[CC_MD5_DIGEST_LENGTH];
CC_MD5_Final(digest, &md5);
[handle closeFile];
NSData* md5data = [NSData dataWithBytes:digest length:CC_MD5_DIGEST_LENGTH];
return md5data;
}
NSString* QCloudEncrytFileOffsetMD5Base64(NSString* filePath, int64_t offset , int64_t siliceLength) {
NSData* md5Data = _internalEncrytFileOffsetMD5(filePath, offset, siliceLength);
if (!md5Data) {
return nil;
}
return [md5Data base64EncodedStringWithOptions:0];
}
NSString* QCloudEncrytFileOffsetMD5(NSString* filePath, int64_t offset , int64_t siliceLength) {
NSData* md5Data = _internalEncrytFileOffsetMD5(filePath, offset, siliceLength);
if (!md5Data) {
return nil;
}
return [md5Data qcloud_MD5String];
}
NSString* QCloudEncrytMD5String(NSString* originString) {
const char *cStr = [originString UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5( cStr, (CC_LONG)strlen(cStr), result );
return [NSString stringWithFormat:
@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
NSString* QCloudHmacSha1Encrypt(NSString *data , NSString* key)
{
const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMACData = [[NSData alloc] initWithBytes:cHMAC
length:sizeof(cHMAC)];
NSString *HMAC = [NSMutableString stringWithCapacity:HMACData.length * 2];
for (int i = 0; i < HMACData.length; ++i)
HMAC = [HMAC stringByAppendingFormat:@"%02lx", (unsigned long)cHMAC[i]];
return HMAC;
}
NSData* QCloudHmacEncrypt(NSString *data ,NSString *key){
const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
unsigned char cHMAC[CC_SHA1_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA1, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *hmac = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
return hmac;
}

View File

@@ -0,0 +1,22 @@
//
// QCloudCLSLoggerOutput.h
// QCloudCore
//
// Created by garenwang on 2025/4/7.
//
#import <QCloudCore/QCloudCore.h>
NS_ASSUME_NONNULL_BEGIN
@interface QCloudCLSLoggerOutput : QCloudLoggerOutput
@property (nonatomic,strong,readonly)id clsService;
- (instancetype)initWithTopicId:(NSString *)topicId endpoint:(NSString *)endPoint;
- (void)setupPermanentCredentialsSecretId:(NSString *)secretId secretKey:(NSString *)secretKey;
- (void)setupCredentialsRefreshBlock:(QCloudCredential * _Nonnull (^)(void))refreshBlock;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,141 @@
//
// QCloudCLSLoggerOutput.m
// QCloudCore
//
// Created by garenwang on 2025/4/7.
//
#import "QCloudCLSLoggerOutput.h"
#import "QCloudLogModel.h"
#import "NSDate+QCloud.h"
NSString * const QCloudTrackCosSdkLog = @"qcloud_track_cos_sdk_log";
@interface QCloudCLSLoggerOutput ()
@property (nonatomic,strong)id clsService;
@property (nonatomic, strong) dispatch_queue_t buildQueue;
@end
@implementation QCloudCLSLoggerOutput
- (instancetype)initWithTopicId:(NSString *)topicId endpoint:(NSString *)endPoint {
if (self = [super init]) {
Class trackServiceClass = NSClassFromString(@"QCloudCLSTrackService");
if (trackServiceClass) {
SEL initSelector = NSSelectorFromString(@"initWithTopicId:endpoint:");
if ([trackServiceClass instancesRespondToSelector:initSelector]) {
_clsService = [[trackServiceClass alloc] performSelector:initSelector withObject:topicId withObject:endPoint];
}
}
_buildQueue = dispatch_queue_create("com.tencent.qcloud.logger.cls.build", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)setupPermanentCredentialsSecretId:(NSString *)secretId secretKey:(NSString *)secretKey { // id
Class cla = NSClassFromString(@"QCloudClsSessionCredentials");
if (!cla) {
return;
}
id credentials = [[cla alloc]init];
//
SEL secretIdSelector = NSSelectorFromString(@"setSecretId:");
if ([credentials respondsToSelector:secretIdSelector]) {
[credentials setValue:secretId forKey:@"secretId"];
}
SEL secretKeySelector = NSSelectorFromString(@"setSecretKey:");
if ([credentials respondsToSelector:secretKeySelector]) {
[credentials setValue:secretKey forKey:@"secretKey"];
}
SEL selector = NSSelectorFromString(@"setupPermanentCredentials:");
if ([_clsService respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_clsService performSelector:selector withObject:credentials];
#pragma clang diagnostic pop
}
}
- (void)setupCredentialsRefreshBlock:(QCloudCredential * _Nonnull (^)(void))refreshBlock {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
if (refreshBlock) {
SEL selector = NSSelectorFromString(@"setupCredentialsRefreshBlock:");
if ([_clsService respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[_clsService performSelector:selector withObject:^id _Nonnull{
QCloudCredential *credential = refreshBlock();
if (!credential) {
return nil;
}
Class cla = NSClassFromString(@"QCloudClsSessionCredentials");
if (!cla) {
return nil;
}
id credentials = [[cla alloc]init];
//
SEL secretIdSelector = NSSelectorFromString(@"setSecretId:");
if ([credentials respondsToSelector:secretIdSelector]) {
[credentials setValue:credential.secretID forKey:@"secretId"];
}
SEL secretKeySelector = NSSelectorFromString(@"setSecretKey:");
if ([credentials respondsToSelector:secretKeySelector]) {
[credentials setValue:credential.secretKey forKey:@"secretKey"];
}
SEL tokenSelector = NSSelectorFromString(@"setToken:");
if ([credentials respondsToSelector:tokenSelector]) {
[credentials setValue:credential.token forKey:@"token"];
}
SEL expiredTimeSelector = NSSelectorFromString(@"setExpiredTime:");
if ([credentials respondsToSelector:expiredTimeSelector]) {
[credentials setValue:@([credential.expirationDate timeIntervalSince1970]) forKey:@"expiredTime"];
}
return credentials;
}];
#pragma clang diagnostic pop
}
}
});
}
- (void)appendLog:(QCloudLogModel * (^)(void))logCreate {
QCloudWeakSelf(self);
dispatch_async(_buildQueue, ^{
QCloudStrongSelf(self);
QCloudLogModel *log = logCreate();
if (log.level <= [QCloudLogger sharedLogger].logClsLevel) {
NSMutableDictionary *params = [NSMutableDictionary new];
params[@"level"] = [QCloudLogModel descriptionForLogLevel:log.level]?:@"";
params[@"category"] = [QCloudLogModel descriptionForLogCategory:log.category]?:@"";
params[@"timestamp"] = @([log.date timeIntervalSince1970]).stringValue?:@"";
params[@"threadName"] = log.threadName?:@"";
params[@"tag"] = log.tag?:@"";
params[@"message"] = log.message?:@"";
params[@"deviceID"] = QCloudLogger.sharedLogger.deviceID?:@"";
params[@"deviceModel"] = QCloudLogger.sharedLogger.deviceModel?:@"";
params[@"appVersion"] = QCloudLogger.sharedLogger.appVersion?:@"";
[QCloudLogger.sharedLogger.extendInfo enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if (obj && key) {
[params setObject:obj forKey:key];
}
}];
// reportWithEventCode:params:
SEL selector = NSSelectorFromString(@"reportWithEventCode:params:");
if ([strongself.clsService respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[strongself.clsService performSelector:selector withObject:QCloudTrackCosSdkLog withObject:params];
#pragma clang diagnostic pop
}
}
});
}
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudCustomLoggerOutput.h
// QCloudCore
//
// Created by garenwang on 2025/4/11.
//
#import <QCloudCore/QCloudCore.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^QCloudCustomLoggerOutputCallBack)(QCloudLogModel * model,NSDictionary *extendInfo);
@interface QCloudCustomLoggerOutput : QCloudLoggerOutput
@property (nonatomic,strong)QCloudCustomLoggerOutputCallBack callback;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,45 @@
//
// QCloudCustomLoggerOutput.m
// QCloudCore
//
// Created by garenwang on 2025/4/11.
//
#import "QCloudCustomLoggerOutput.h"
@interface QCloudCustomLoggerOutput ()
@property(strong,nonatomic)dispatch_queue_t buildQueue;
@end
@implementation QCloudCustomLoggerOutput
- (instancetype)init
{
self = [super init];
if (self) {
_buildQueue = dispatch_queue_create("com.tencent.qcloud.logger.cls.build", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)appendLog:(QCloudLogModel * (^)(void))logCreate {
QCloudWeakSelf(self);
dispatch_async(_buildQueue, ^{
QCloudStrongSelf(self);
QCloudLogModel *log = logCreate();
NSMutableDictionary *params = QCloudLogger.sharedLogger.extendInfo?QCloudLogger.sharedLogger.extendInfo.mutableCopy:[NSMutableDictionary new];
if (QCloudLogger.sharedLogger.deviceID) {
params[@"deviceID"] = QCloudLogger.sharedLogger.deviceID;
}
if (QCloudLogger.sharedLogger.deviceModel) {
params[@"deviceModel"] = QCloudLogger.sharedLogger.deviceModel;
}
if (QCloudLogger.sharedLogger.appVersion) {
params[@"appVersion"] = QCloudLogger.sharedLogger.appVersion;
}
if (self.callback) {
self.callback(log,params);
}
});
}
@end

View File

@@ -0,0 +1,24 @@
//
// QCloudFileLogger.h
// Pods
//
// Created by Dong Zhao on 2017/3/15.
//
//
#import <Foundation/Foundation.h>
#import "QCloudLoggerOutput.h"
@class QCloudFileLogger;
@protocol QCloudFileLoggerDelegate <NSObject>
- (void)fileLoggerDidFull:(QCloudFileLogger *)logger;
@end
@class QCloudLogModel;
@interface QCloudFileLogger : QCloudLoggerOutput
@property (nonatomic, weak) id<QCloudFileLoggerDelegate> delegate;
@property (nonatomic, strong, readonly) NSString *path;
@property (nonatomic, assign, readonly) uint64_t maxSize;
@property (nonatomic, assign, readonly) uint64_t currentSize;
@property (nonatomic, assign, readonly) BOOL isFull;
- (instancetype)initWithPath:(NSString *)path maxSize:(uint64_t)maxSize;
@end

View File

@@ -0,0 +1,188 @@
//
// QCloudFileLogger.m
// Pods
//
// Created by Dong Zhao on 2017/3/15.
//
//
#import "QCloudFileLogger.h"
#import "QCloudLogModel.h"
#import "QCloudFileUtils.h"
#import "QCloudLogger.h"
#import <zlib.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
#import "QCloudSDKModuleManager.h"
#import "NSObject+QCloudModel.h"
#import <CommonCrypto/CommonCrypto.h>
@interface QCloudFileLogger () {
dispatch_source_t _timer;
}
@property (nonatomic, strong) dispatch_queue_t buildQueue;
@property (nonatomic, strong) NSFileHandle *fileHandler;
@property (nonatomic, strong) NSMutableData *sliceData;
@property (nonatomic, assign) uint64_t sliceSize;
@end
@implementation QCloudFileLogger
@synthesize currentSize = _currentSize;
- (void)commonInit {
_buildQueue = dispatch_queue_create("com.tencent.qcloud.logger.build", DISPATCH_QUEUE_SERIAL);
//
_sliceSize = 200 * 1024;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _buildQueue);
dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
[self writeCliceDataToFile];
});
_sliceData = [NSMutableData dataWithCapacity:(NSUInteger)_sliceSize];
}
- (void)dealloc {
[self writeCliceDataToFile];
[_fileHandler closeFile];
dispatch_source_cancel(_timer);
}
- (instancetype)initWithPath:(NSString *)path maxSize:(uint64_t)maxSize {
self = [super init];
if (!self) {
return self;
}
[self commonInit];
_maxSize = maxSize;
_path = path;
_currentSize = QCloudFileSize(path);
if (!QCloudFileExist(path)) {
[[NSFileManager defaultManager] createFileAtPath:path contents:[NSData data] attributes:nil];
NSArray *allModules = [[QCloudSDKModuleManager shareInstance] allModules];
NSData *modulestring = [allModules qcloud_modelToJSONData];
[_sliceData appendData:modulestring];
}
dispatch_resume(_timer);
_fileHandler = [NSFileHandle fileHandleForWritingAtPath:path];
[_fileHandler seekToEndOfFile];
#if TARGET_OS_IOS
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(flushAllFiles)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(flushAllFiles)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(flushAllFiles) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(flushAllFiles)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
//
#elif TARGET_OS_MAC
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(flushAllFiles)
name:NSApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(flushAllFiles) name:NSApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(flushAllFiles) name:NSApplicationWillHideNotification object:nil];
#endif
return self;
}
- (void)flushAllFiles {
dispatch_async(_buildQueue, ^{
[self writeCliceDataToFile];
});
}
- (void)writeCliceDataToFile {
if (_sliceData.length) {
@try {
if ([QCloudLogger sharedLogger].aesKey.length>0 && [QCloudLogger sharedLogger].aesIv.length>0) {
[self appendEncryptedLogToFile:_sliceData key:[QCloudLogger sharedLogger].aesKey iv:[QCloudLogger sharedLogger].aesIv];
}else{
[_fileHandler writeData:_sliceData];
}
_sliceData = [NSMutableData dataWithCapacity:(NSUInteger)_sliceSize];
} @catch (NSException *exception) {
NSLog(@"no space left on device");
}
}
}
- (void)appendLog:(QCloudLogModel * (^)(void))logCreate {
dispatch_async(_buildQueue, ^{
QCloudLogModel *log = logCreate();
if (log.level <= [QCloudLogger sharedLogger].logFileLevel) {
NSString *message = [NSString stringWithFormat:@"%@\n", [log fileDescription]];
NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding];
self->_currentSize += data.length;
[self->_sliceData appendData:data];
//
if (self.currentSize >= self.maxSize) {
[self writeCliceDataToFile];
if ([self.delegate respondsToSelector:@selector(fileLoggerDidFull:)]) {
[self.delegate fileLoggerDidFull:self];
}
} else {
if (self->_sliceData.length >= self->_sliceSize) {
[self writeCliceDataToFile];
}
}
}
});
}
- (BOOL)isFull {
return self.currentSize >= self.maxSize;
}
//
- (BOOL)appendEncryptedLogToFile:(NSData *)messageData key:(NSData *)key iv:(NSData *)iv {
NSData *encryptedData = [self encryptData:messageData key:key iv:iv];
if (!encryptedData) return NO;
if (_fileHandler) {
// 4
uint32_t length = (uint32_t)encryptedData.length;
uint32_t lengthBE = htonl(length);
[_fileHandler writeData:[NSData dataWithBytes:&lengthBE length:sizeof(lengthBE)]];
//
[_fileHandler writeData:encryptedData];
return YES;
}
return NO;
}
#pragma mark -
- (NSData *)encryptData:(NSData *)data key:(NSData *)key iv:(NSData *)iv{
return [self cryptData:data operation:kCCEncrypt key:key iv:iv];
}
- (NSData *)cryptData:(NSData *)data operation:(CCOperation)op key:(NSData *)key iv:(NSData *)iv {
size_t bufferSize = data.length + kCCBlockSizeAES128;
void *buffer = malloc(bufferSize);
size_t numBytesProcessed = 0;
CCCryptorStatus status = CCCrypt(op,
kCCAlgorithmAES,
kCCOptionPKCS7Padding,
key.bytes,
kCCKeySizeAES256,
iv.bytes,
data.bytes,
data.length,
buffer,
bufferSize,
&numBytesProcessed);
if (status == kCCSuccess) {
return [NSData dataWithBytesNoCopy:buffer length:numBytesProcessed];
}else{
NSLog(@"警告:日志加密失败");
}
free(buffer);
return nil;
}
@end

View File

@@ -0,0 +1,14 @@
//
// QCloudFileZipper.h
// Pods
//
// Created by Dong Zhao on 2017/3/15.
//
//
#import <Foundation/Foundation.h>
@interface QCloudFileZipper : NSObject
- (instancetype)initWithInputFilePath:(NSString *)path;
- (BOOL)outputToPath:(NSString *)path;
@end

View File

@@ -0,0 +1,28 @@
//
// QCloudFileZipper.m
// Pods
//
// Created by Dong Zhao on 2017/3/15.
//
//
#import "QCloudFileZipper.h"
@interface QCloudFileZipper ()
@property (nonatomic, strong) NSString *inputPath;
@end
@implementation QCloudFileZipper
- (instancetype)initWithInputFilePath:(NSString *)path {
self = [super init];
if (!self) {
return self;
}
_inputPath = path;
return self;
}
- (BOOL)outputToPath:(NSString *)path {
return YES;
}
@end

View File

@@ -0,0 +1,103 @@
//
// QCloudLogModel.h
// Pods
//
// Created by Dong Zhao on 2017/3/15.
//
//
#import <Foundation/Foundation.h>
/**
`QCloudLogLevel` enum specifies different levels of logging that could be used to limit or display more messages in logs.
*/
typedef NS_ENUM(uint8_t, QCloudLogLevel) {
/**
Log level that disables all logging.
*/
QCloudLogLevelNone = 1,
/**
Log level that if set is going to output error messages to the log.
*/
QCloudLogLevelError = 2,
/**
Log level that if set is going to output the following messages to log:
- Errors
- Warnings
*/
QCloudLogLevelWarning = 3,
/**
Log level that if set is going to output the following messages to log:
- Errors
- Warnings
- Informational messages
*/
QCloudLogLevelInfo = 4,
/**
Log level that if set is going to output the following messages to log:
- Errors
- Warnings
- Informational messages
- Debug messages
*/
QCloudLogLevelDebug = 5,
/**
Log level that if set is going to output the following messages to log:
- Errors
- Warnings
- Informational messages
- Debug messages
- Verbose
*/
QCloudLogLevelVerbose = 6,
};
typedef NS_ENUM(uint8_t, QCloudLogCategory) {
QCloudLogCategoryNone,
/**
操作过程日志(如上传分片开始、网络请求发起)
*/
QCloudLogCategoryProcess,
/**
操作结果日志(如上传成功、下载失败)
*/
QCloudLogCategoryResult,
/**
网络层日志如请求、响应、http性能
*/
QCloudLogCategoryNetwork,
/**
网络探测日志(如网络连接导致失败时的探测)
*/
QCloudLogCategoryProbe,
/**
错误堆栈日志(如异常捕获)
*/
QCloudLogCategoryError,
};
@interface QCloudLogModel : NSObject
@property (nonatomic, assign) QCloudLogLevel level;
@property (nonatomic, strong) NSString *message;
@property (nonatomic, assign) QCloudLogCategory category;
@property (nonatomic, strong) NSString *tag;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) NSString *file;
@property (nonatomic, assign) int line;
@property (nonatomic, assign) BOOL simpleLog;
@property (nonatomic, strong) NSString *funciton;
@property (nonatomic, strong) NSString *threadName;
/**
生成用于写文件的Log信息
@return 写入文件的Log信息
*/
- (NSString *)fileDescription;
+ (NSString *)descriptionForLogCategory:(QCloudLogCategory)logCategory;
+ (NSString *)descriptionForLogLevel:(QCloudLogLevel)logLevel;
@end

View File

@@ -0,0 +1,168 @@
//
// QCloudLogModel.m
// Pods
//
// Created by Dong Zhao on 2017/3/15.
//
//
#import "QCloudLogModel.h"
#import "NSDate+QCLOUD.h"
#import "QCloudLogger.h"
#import "NSObject+QCloudModel.h"
@implementation QCloudLogModel
///--------------------------------------
#pragma mark - Logging Messages
///--------------------------------------
+ (NSString *)descriptionForLogLevel:(QCloudLogLevel)logLevel {
NSString *description = nil;
switch (logLevel) {
case QCloudLogLevelNone:
break;
case QCloudLogLevelDebug:
description = @"Debug";
break;
case QCloudLogLevelError:
description = @"Error";
break;
case QCloudLogLevelWarning:
description = @"Warning";
break;
case QCloudLogLevelInfo:
description = @"Info";
break;
case QCloudLogLevelVerbose:
description = @"Verbose";
break;
}
return description;
}
+ (NSString *)descriptionForLogCategory:(QCloudLogCategory)logCategory {
NSString *description = nil;
switch (logCategory) {
case QCloudLogCategoryNone:
description = @"";
break;
case QCloudLogCategoryProcess:
description = @"PROCESS";
break;
case QCloudLogCategoryResult:
description = @"RESULT";
break;
case QCloudLogCategoryNetwork:
description = @"NETWORK";
break;
case QCloudLogCategoryProbe:
description = @"PROBE";
break;
case QCloudLogCategoryError:
description = @"ERROR";
break;
}
return description;
}
- (NSString *)debugDescription {
static BOOL willOutputColor = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
char *xcodeColor = getenv("XcodeColors");
if (xcodeColor && (strcmp(xcodeColor, "YES") == 0)) {
willOutputColor = YES;
setenv("XcodeColors", "YES", 0);
}
});
NSString * extInfo = @"";
if ([QCloudLogger sharedLogger].extendInfo && !self.simpleLog) {
extInfo = [NSString stringWithFormat:@",extendInfo=%@",[[QCloudLogger sharedLogger].extendInfo qcloud_modelToJSONString]];
extInfo = [NSString stringWithFormat:@",appVersion=%@,deviceModel=%@,deviceID=%@",[QCloudLogger sharedLogger].appVersion,[QCloudLogger sharedLogger].deviceModel,[QCloudLogger sharedLogger].deviceID];
self.message = [self.message stringByAppendingString:extInfo];
}
NSString *description;
if (self.simpleLog) {
description = [NSMutableString stringWithFormat:@"%@ %@[%@][%@]%@", [NSDate qcloud_stringFromDate_24:self.date],self.tag,[QCloudLogModel descriptionForLogCategory:self.category],self.threadName,self.message];
}else if (willOutputColor) {
description = [NSMutableString stringWithFormat:@"%@%@/%@[%@][%@ %@]%@%@",[QCloudLogModel consoleLogColorWithLevel:self.level], [QCloudLogModel descriptionForLogLevel:self.level],[NSDate qcloud_stringFromDate_24:self.date],self.threadName,[QCloudLogModel descriptionForLogCategory:self.category],self.tag,[QCloudLogModel consoleLogColorWithLevel:self.level],self.message];
} else {
description = [NSMutableString stringWithFormat:@"%@/%@[%@][%@ %@]%@", [QCloudLogModel descriptionForLogLevel:self.level],[NSDate qcloud_stringFromDate_24:self.date],self.threadName,[QCloudLogModel descriptionForLogCategory:self.category],self.tag,self.message];
}
return description;
}
+ (NSString *)consoleLogColorWithLevel:(QCloudLogLevel)level {
switch (level) {
case QCloudLogLevelInfo:
return @"🔷";
case QCloudLogLevelNone:
return @"";
case QCloudLogLevelDebug:
return @"◾️ ";
case QCloudLogLevelError:
return @"🛑";
case QCloudLogLevelWarning:
return @"🔶";
default:
break;
}
return @"";
}
+ (NSString *)consoleRestLogColorWithLevel:(QCloudLogLevel)level {
switch (level) {
case QCloudLogLevelInfo:
return @"\033[fg0,0,0;\033[bg255,255,255;";
case QCloudLogLevelNone:
return @"\033[fg0,0,0;\033[bg255,255,255;";
case QCloudLogLevelDebug:
return @"\033[fg0,0,0;\033[bg255,255,255;";
case QCloudLogLevelError:
return @"\033[fg0,0,0;\033[bg200,0,0;";
case QCloudLogLevelWarning:
return @"\033[fg0,0,0;\033[bg100,100,100;";
default:
break;
}
return @"";
}
- (NSString *)fileLogColorWithLevel:(QCloudLogLevel)level {
switch (level) {
case QCloudLogLevelInfo:
return @"\e[38;5;38;82m";
case QCloudLogLevelNone:
return @"\e[0m";
case QCloudLogLevelDebug:
return @"\e[30;48;5;50m";
case QCloudLogLevelError:
return @"\e[41;41;41;256m";
case QCloudLogLevelWarning:
return @"\e[38;5;251;203m";
default:
break;
}
return @"";
}
- (NSString *)fileDescription {
NSString *color = [self fileLogColorWithLevel:self.level];
NSMutableString *log = [NSMutableString new];
[log appendString:color];
[log appendFormat:@"[%@]", self.date];
[log appendFormat:@"[%@]", [QCloudLogModel descriptionForLogLevel:self.level]];
[log appendString:@"\e[0m"];
if (self.file.length) {
[log appendFormat:@"[%@]", [self.file componentsSeparatedByString:@"/"].lastObject];
}
if (self.funciton.length) {
[log appendFormat:@"[%@]", self.funciton];
}
if (self.line > 0) {
[log appendFormat:@"[%d]", self.line];
}
[log appendString:self.message];
return log;
}
@end

View File

@@ -0,0 +1,154 @@
//
// QCloudLogger.h
// Pods
//
// Created by Dong Zhao on 2017/3/14.
//
//
#import <Foundation/Foundation.h>
#import "QCloudLogModel.h"
#import "QCloudLoggerOutput.h"
#define QCloudLog(level, c, t, frmt, ...) \
[[QCloudLogger sharedLogger] logMessageWithLevel:level category:c tag:t cmd:__PRETTY_FUNCTION__ line:__LINE__ file:__FILE__ format:(frmt), ##__VA_ARGS__]
#define QCloudLogError(frmt, ...) QCloudLog(QCloudLogLevelError, QCloudLogCategoryNone, @"", (frmt), ##__VA_ARGS__)
#define QCloudLogWarning(frmt, ...) QCloudLog(QCloudLogLevelWarning, QCloudLogCategoryNone, @"", (frmt), ##__VA_ARGS__)
#define QCloudLogInfo(frmt, ...) QCloudLog(QCloudLogLevelInfo, QCloudLogCategoryNone, @"", (frmt), ##__VA_ARGS__)
#define QCloudLogDebug(frmt, ...) QCloudLog(QCloudLogLevelDebug, QCloudLogCategoryNone, @"", (frmt), ##__VA_ARGS__)
#define QCloudLogVerbose(frmt, ...) QCloudLog(QCloudLogLevelVerbose, QCloudLogCategoryNone, @"", (frmt), ##__VA_ARGS__)
#define QCloudLogErrorP(tag,frmt, ...) QCloudLog(QCloudLogLevelError,QCloudLogCategoryProcess,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogWarningP(tag,frmt, ...) QCloudLog(QCloudLogLevelWarning,QCloudLogCategoryProcess,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogInfoP(tag,frmt, ...) QCloudLog(QCloudLogLevelInfo,QCloudLogCategoryProcess,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogDebugP(tag,frmt, ...) QCloudLog(QCloudLogLevelDebug,QCloudLogCategoryProcess,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogVerboseP(tag,frmt, ...) QCloudLog(QCloudLogLevelVerbose,QCloudLogCategoryProcess,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogErrorR(tag,frmt, ...) QCloudLog(QCloudLogLevelError,QCloudLogCategoryResult,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogWarningR(tag,frmt, ...) QCloudLog(QCloudLogLevelWarning,QCloudLogCategoryResult,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogInfoR(tag,frmt, ...) QCloudLog(QCloudLogLevelInfo,QCloudLogCategoryResult,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogDebugR(tag,frmt, ...) QCloudLog(QCloudLogLevelDebug,QCloudLogCategoryResult,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogVerboseR(tag,frmt, ...) QCloudLog(QCloudLogLevelVerbose,QCloudLogCategoryResult,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogErrorN(tag,frmt, ...) QCloudLog(QCloudLogLevelError,QCloudLogCategoryNetwork,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogWarningN(tag,frmt, ...) QCloudLog(QCloudLogLevelWarning,QCloudLogCategoryNetwork,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogInfoN(tag,frmt, ...) QCloudLog(QCloudLogLevelInfo,QCloudLogCategoryNetwork,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogDebugN(tag,frmt, ...) QCloudLog(QCloudLogLevelDebug,QCloudLogCategoryNetwork,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogVerboseN(tag,frmt, ...) QCloudLog(QCloudLogLevelVerbose,QCloudLogCategoryNetwork,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogErrorPB(tag,frmt, ...) QCloudLog(QCloudLogLevelError,QCloudLogCategoryProbe,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogWarningPB(tag,frmt, ...) QCloudLog(QCloudLogLevelWarning,QCloudLogCategoryProbe,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogInfoPB(tag,frmt, ...) QCloudLog(QCloudLogLevelInfo,QCloudLogCategoryProbe,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogDebugPB(tag,frmt, ...) QCloudLog(QCloudLogLevelDebug,QCloudLogCategoryProbe,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogVerbosePB(tag,frmt, ...) QCloudLog(QCloudLogLevelVerbose,QCloudLogCategoryProbe,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogErrorE(tag,frmt, ...) QCloudLog(QCloudLogLevelError,QCloudLogCategoryError,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogWarningE(tag,frmt, ...) QCloudLog(QCloudLogLevelWarning,QCloudLogCategoryError,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogInfoE(tag,frmt, ...) QCloudLog(QCloudLogLevelInfo,QCloudLogCategoryError,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogDebugE(tag,frmt, ...) QCloudLog(QCloudLogLevelDebug,QCloudLogCategoryError,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogVerboseE(tag,frmt, ...) QCloudLog(QCloudLogLevelVerbose,QCloudLogCategoryError,tag, (frmt), ##__VA_ARGS__)
#define QCloudLogException(exception) \
QCloudLogError(@"",@"Caught \"%@\" with reason \"%@\"%@", exception.name, exception, \
[exception callStackSymbols] ? [NSString stringWithFormat:@":\n%@.", [exception callStackSymbols]] : @"")
#define QCloudLogTrance() QCloudLog(QCloudLogLevelDebug,QCloudLogCategoryNone,@"", @"%@", [NSThread callStackSymbols])
@interface QCloudLogger : NSObject
/// 日志加密Key不指定则不加密日志。
@property (nonatomic, strong) NSData *aesKey;
/// 日志加密IV不指定则不加密日志。
@property (nonatomic, strong) NSData *aesIv;
/// 扩展信息,用于日志上报
@property (nonatomic, strong) NSDictionary *extendInfo;
/// 设备ID
@property (nonatomic, strong) NSString *deviceID;
/// 机型
@property (nonatomic, strong) NSString *deviceModel;
/// APP版本
@property (nonatomic, strong) NSString *appVersion;
/// 控制台输出的日志级别
@property (nonatomic, assign) QCloudLogLevel logLevel;
@property (nonatomic, assign) QCloudLogLevel logFileLevel;
@property (nonatomic, assign) QCloudLogLevel logClsLevel;
/// 本地日志路径
@property (nonatomic, strong, readonly) NSString *logDirctoryPath;
@property (nonatomic, assign) uint64_t maxStoarageSize;
/// 日志保存天数
@property (nonatomic, assign) float keepDays;
///--------------------------------------
#pragma mark - Shared Logger
///--------------------------------------
/**
A shared instance of `QCloudLogger` that should be used for all logging.
@return An shared singleton instance of `QCloudLogger`.
*/
+ (instancetype)sharedLogger;
///--------------------------------------
#pragma mark - Logging Messages
///--------------------------------------
- (void)logMessageWithLevel:(QCloudLogLevel)level category:(QCloudLogCategory)category tag:(NSString *)tag cmd:(const char *)commandInfo line:(int)line file:(const char *)file format:(NSString *)format, ...;
/**
增加一个输出源
@param output 输出源
*/
- (void)addLogger:(QCloudLoggerOutput *)output;
/**
删除一个输出源
@param output 删除一个输出源
*/
- (void)removeLogger:(QCloudLoggerOutput *)output;
@end

View File

@@ -0,0 +1,274 @@
//
// QCloudLogger.m
// Pods
//
// Created by Dong Zhao on 2017/3/14.
//
//
#import "QCloudLogger.h"
#import "QCloudLogModel.h"
#import "QCloudFileUtils.h"
#import <time.h>
#import <xlocale.h>
#import "QCloudFileLogger.h"
#import "NSDate+QCLOUD.h"
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
#define QCloudEachLogFileSize 10 * 1024 * 1024
@interface NSDate (QCloudEasy)
@end
@implementation NSDate (QCloudEasy)
- (NSString *)qcloud_string {
time_t pubdate = [self timeIntervalSince1970];
struct tm *cTime = localtime(&pubdate);
return [NSString stringWithFormat:@"%d-%02d-%02d-%02d-%02d-%02d", 1900 + cTime->tm_year, 1 + cTime->tm_mon, cTime->tm_mday, cTime->tm_hour,
cTime->tm_min, cTime->tm_sec];
}
+ (NSString *)qcloud_todayString {
time_t pubdate = [[NSDate date] timeIntervalSince1970];
struct tm *cTime = localtime(&pubdate);
return [NSString stringWithFormat:@"%d-%02d-%02d", 1900 + cTime->tm_year, 1 + cTime->tm_mon, cTime->tm_mday];
}
+ (NSDate *)qcloud_dateWithString:(NSString *)str {
struct tm sometime;
const char *formatString = "%Y-%m-%d";
(void)strptime_l([str UTF8String], formatString, &sometime, NULL);
return [NSDate dateWithTimeIntervalSince1970:mktime(&sometime)];
}
@end
NSString *const kQCloudLogExtension = @"log";
@interface QCloudLogger () <QCloudFileLoggerDelegate>
@property (nonatomic, strong) QCloudFileLogger *currentFileLogger;
@end
@implementation QCloudLogger {
NSMutableArray *_loggerOutputs;
}
+ (instancetype)sharedLogger {
static QCloudLogger *logger = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
logger = [QCloudLogger new];
char *level = getenv("QCloudLogLevel");
if (NULL != level && strlen(level) > 0) {
int logLevel = atoi(level);
if (logLevel >= QCloudLogLevelNone && logLevel <= QCloudLogLevelVerbose) {
logger.logLevel = logLevel;
} else {
logger.logLevel = QCloudLogLevelVerbose;
}
} else {
logger.logLevel = QCloudLogLevelVerbose;
}
});
logger.logFileLevel = QCloudLogLevelVerbose;
logger.logClsLevel = QCloudLogLevelVerbose;
return logger;
}
- (NSDictionary *)extendInfo{
if (![_extendInfo objectForKey:@"qcloud_platform"]) {
NSMutableDictionary *mextendInfo = _extendInfo?[_extendInfo mutableCopy]:[NSMutableDictionary new];
[mextendInfo setObject:@"iOS" forKey:@"qcloud_platform"];
_extendInfo = mextendInfo.copy;
}
return _extendInfo;
}
-(NSMutableArray *)loggerOutputs{
if (!_loggerOutputs) {
_loggerOutputs = [NSMutableArray new];
_currentFileLogger = [[QCloudFileLogger alloc] initWithPath:[self avilableLogFilePath] maxSize:QCloudEachLogFileSize];
_currentFileLogger.delegate = self;
[_loggerOutputs addObject:_currentFileLogger];
}
return _loggerOutputs;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_maxStoarageSize = 70 * 1024 * 1024;
_keepDays = 3;
//
#if TARGET_OS_IOS
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tryCleanLogs)
name:UIApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tryCleanLogs) name:UIApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tryCleanLogs)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
#elif TARGET_OS_MAC
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(tryCleanLogs)
name:NSApplicationWillResignActiveNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tryCleanLogs) name:NSApplicationWillTerminateNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tryCleanLogs) name:NSApplicationWillHideNotification object:nil];
#endif
return self;
}
- (NSArray<NSString *> *)allLogFiles {
NSString *logDir = self.logDirctoryPath;
NSDirectoryEnumerator<NSString *> *enumertor = [[NSFileManager defaultManager] enumeratorAtPath:logDir];
NSString *file = nil;
NSMutableArray *files = [NSMutableArray new];
while (file = [enumertor nextObject]) {
if ([file.pathExtension isEqualToString:kQCloudLogExtension]) {
[files addObject:file];
}
}
return files;
}
- (void)fileLoggerDidFull:(QCloudFileLogger *)logger {
if (logger != _currentFileLogger) {
return;
}
NSString *nextLogPath = [self avilableLogFilePath];
if (_currentFileLogger.isFull) {
QCloudFileLogger *fileLogger = [[QCloudFileLogger alloc] initWithPath:nextLogPath maxSize:QCloudEachLogFileSize];
fileLogger.delegate = self;
_currentFileLogger = fileLogger;
}
}
- (NSString *)avilableLogFilePath {
NSArray *allLogFiles = [self allLogFiles];
allLogFiles = [allLogFiles sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
NSString *lastLog = allLogFiles.lastObject;
NSString *todayLogPrefix = [NSDate qcloud_todayString];
NSString *readyLogName = [[NSDate date] qcloud_string];
NSString *logName = nil;
NSString *lastLogPath = nil;
if ([QCloudLogger sharedLogger].aesKey.length &&[QCloudLogger sharedLogger].aesKey.length) {
readyLogName = [readyLogName stringByAppendingString:@"_encrypt"];
if (![lastLog containsString:@"encrypt"]) {
lastLog = nil;
}
}else{
if ([lastLog containsString:@"encrypt"]) {
lastLog = nil;
}
}
if (lastLog) {
lastLogPath = QCloudPathJoin(self.logDirctoryPath, lastLog);
}
if (!lastLog) {
logName = [readyLogName stringByAppendingPathExtension:kQCloudLogExtension];
} else {
if ([lastLog hasPrefix:todayLogPrefix]) {
if (QCloudFileSize(lastLogPath) >= QCloudEachLogFileSize) {
logName = [readyLogName stringByAppendingPathExtension:kQCloudLogExtension];
} else {
logName = lastLog;
}
} else {
logName = [readyLogName stringByAppendingPathExtension:kQCloudLogExtension];
}
}
return QCloudPathJoin(self.logDirctoryPath, logName);
}
- (void)tryCleanLogs {
NSArray *allLogFiles = [self allLogFiles];
allLogFiles = [allLogFiles sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj2 compare:obj1];
}];
NSString *logDir = self.logDirctoryPath;
uint64_t totalSize = 0;
NSString *agoDateString = [[NSDate dateWithTimeIntervalSinceNow:-self.keepDays * 24 * 60 * 60] qcloud_string];
for (NSString *logName in allLogFiles) {
NSString *path = QCloudPathJoin(logDir, logName);
totalSize += QCloudFileSize(path);
NSString *dateString = [[logName stringByDeletingPathExtension] componentsSeparatedByString:@"_"].firstObject;
if (totalSize > self.maxStoarageSize || [dateString compare:agoDateString] == NSOrderedAscending) {
QCloudRemoveFileByPath(path);
}
}
}
- (NSString *)logDirctoryPath {
NSString *path = QCloudPathJoin(QCloudPathJoin(QCloudPathJoin(QCloudApplicationLibaryPath(), @"Caches"), @"qcloud"), @"logs");
QCloudEnsurePathExist(path);
return path;
}
- (void)logMessageWithLevel:(QCloudLogLevel)level
category:(QCloudLogCategory)category
tag:(NSString *)tag
cmd:(const char *)cmd
line:(int)line
file:(const char *)file
format:(NSString *)format, ... NS_FORMAT_FUNCTION(7, 8) {
if (level == QCloudLogLevelNone || !format) {
return;
}
va_list args;
va_start(args, format);
NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
QCloudLogModel * (^CreateLog)(void) = ^(void) {
QCloudLogModel *log = [QCloudLogModel new];
log.message = message;
log.date = [NSDate localDate];
log.level = level;
log.funciton = [NSString stringWithCString:cmd encoding:NSUTF8StringEncoding];
log.file = [NSString stringWithCString:file encoding:NSUTF8StringEncoding];
log.line = line;
log.category = category;
log.tag = tag;
if ([NSThread currentThread].name.length>0) {
log.threadName = [NSThread currentThread].name;
}else {
log.threadName = [NSThread isMainThread]?@"main thread":@"child thread";
}
return log;
};
if (level <= self.logLevel) {
QCloudLogModel *model = CreateLog();
model.simpleLog = YES;
NSLog(@"%@", [model debugDescription]);
}
for (QCloudLoggerOutput *output in self.loggerOutputs) {
[output appendLog:CreateLog];
}
va_end(args);
}
- (void)addLogger:(QCloudLoggerOutput *)output {
if (!output) {
return;
}
[self.loggerOutputs addObject:output];
}
- (void)removeLogger:(QCloudLoggerOutput *)output {
[self.loggerOutputs removeObject:output];
}
@end

View File

@@ -0,0 +1,13 @@
//
// QCloudLoggerOutput.h
// QCloudCore
//
// Created by Dong Zhao on 2018/5/29.
//
#import <Foundation/Foundation.h>
#import "QCloudLogModel.h"
@class QCloudLogModel;
@interface QCloudLoggerOutput : NSObject
- (void)appendLog:(QCloudLogModel * (^)(void))logCreate;
@end

View File

@@ -0,0 +1,15 @@
//
// QCloudLoggerOutput.m
// QCloudCore
//
// Created by Dong Zhao on 2018/5/29.
//
#import "QCloudLoggerOutput.h"
#import "QCloudLogger.h"
@implementation QCloudLoggerOutput
- (void)appendLog:(QCloudLogModel * (^)(void))logCreate {
[NSException exceptionWithName:@"com.qcloud.logger" reason:@"You must implementation this method in subclass of QCloudLoggerOutput" userInfo:nil];
}
@end

View File

@@ -0,0 +1,28 @@
//
// QCloudMultiDelegateProxy.h
// TACAuthorization
//
// Created by Dong Zhao on 2017/12/11.
//
#import <Foundation/Foundation.h>
/**
解决delegate多次转发的问题通常情况下绝大部分SDK的delegate为一个属性也就是说只能接受一个delegate但是当SDK容纳了非常多的场景逻辑的时候这个时候实现delegate协议的地方就非常臃肿难以拆分。所以设计了这个基础机制可以将delegate向多个多想转发消息。这是一个1对多的转发代理。
*/
@interface QCloudMultiDelegateProxy<Type> : NSObject
/**
添加一个接受转发的委托
@note 在内部对该对象为弱应用,不用担心会产生内存问题。同时在编程的时候,也不要认为我们会持有该对象。
@param delegate 接受转发的对象
*/
- (void)addDelegate:(Type)delegate;
/**
删除一个接受转发的委托
@param delegate 将要被删除的对象
*/
- (void)removeDelegate:(Type)delegate;
@end

View File

@@ -0,0 +1,106 @@
//
// QCloudMultiDelegateProxy.m
// TACAuthorization
//
// Created by Dong Zhao on 2017/12/11.
//
#import "QCloudMultiDelegateProxy.h"
@interface QCloudMultiDelegateProxy ()
@property (nonatomic, strong) NSRecursiveLock *lock;
@property (nonatomic, strong) NSPointerArray *delegates;
@end
@implementation QCloudMultiDelegateProxy
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_delegates = [NSPointerArray pointerArrayWithOptions:NSPointerFunctionsWeakMemory];
return self;
}
- (void)addDelegate:(id)delegate {
[_lock lock];
NSUInteger index = NSNotFound;
for (NSUInteger i = 0; i < _delegates.count; i++) {
id d = [_delegates pointerAtIndex:i];
if (d == delegate) {
index = i;
}
}
if (index == NSNotFound) {
[_delegates addPointer:(void *)delegate];
}
[_delegates compact];
[_lock unlock];
}
- (void)removeDelegate:(id)delegate {
[_lock lock];
NSUInteger index = NSNotFound;
for (NSUInteger i = 0; i < _delegates.count; i++) {
id d = [_delegates pointerAtIndex:i];
if (d == delegate) {
index = i;
}
}
if (index != NSNotFound) {
[_delegates removePointerAtIndex:index];
}
[_delegates compact];
[_lock unlock];
}
- (id)forwardingTargetForSelector:(SEL)sel {
return self;
}
- (NSInvocation *)_copyInvocation:(NSInvocation *)invocation {
NSInvocation *copy = [NSInvocation invocationWithMethodSignature:[invocation methodSignature]];
NSUInteger argCount = [[invocation methodSignature] numberOfArguments];
for (int i = 0; i < argCount; i++) {
char buffer[sizeof(intmax_t)];
[invocation getArgument:(void *)&buffer atIndex:i];
[copy setArgument:(void *)&buffer atIndex:i];
}
return copy;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[_delegates compact];
for (NSUInteger index = 0; index < _delegates.count; index++) {
id object = [_delegates pointerAtIndex:index];
if ([object respondsToSelector:invocation.selector]) {
NSInvocation *invocationCopy = [self _copyInvocation:invocation];
[invocationCopy invokeWithTarget:object];
}
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
[_delegates compact];
for (NSUInteger index = 0; index < _delegates.count; index++) {
id object = [_delegates pointerAtIndex:index];
if (object) {
id result = [object methodSignatureForSelector:sel];
return result;
}
}
return nil;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
[_delegates compact];
for (NSUInteger index = 0; index < _delegates.count; index++) {
id object = [_delegates pointerAtIndex:index];
if ([object respondsToSelector:aSelector]) {
return YES;
}
}
return NO;
}
@end

View File

@@ -0,0 +1,35 @@
//
// QCloudWeakProxy.h
// Pods
//
// Created by QCloudTernimalLab on 2016/11/16.
//
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface QCloudWeakProxy : NSProxy
/**
The proxy target.
*/
@property (nullable, nonatomic, weak, readonly) id target;
/**
Creates a new weak proxy for target.
@param target Target object.
@return A new proxy object.
*/
- (instancetype)initWithTarget:(id)target;
/**
Creates a new weak proxy for target.
@param target Target object.
@return A new proxy object.
*/
+ (instancetype)proxyWithTarget:(id)target;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,88 @@
//
// QCloudWeakProxy.m
// Pods
//
// Created by QCloudTernimalLab on 2016/11/16.
//
//
#import "QCloudWeakProxy.h"
@implementation QCloudWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
if ([target isKindOfClass:[QCloudWeakProxy class]]) {
return target;
}
return [[QCloudWeakProxy alloc] initWithTarget:target];
}
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
/**
crashtargetweaktargetnilcrash
*/
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
if (aClass == [QCloudWeakProxy class]) {
return YES;
}
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudBundlePath.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import "QCloudUniversalAdjustablePath.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudBundlePath : QCloudUniversalAdjustablePath
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,12 @@
//
// QCloudBundlePath.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudBundlePath.h"
@implementation QCloudBundlePath
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudMediaPath.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import "QCloudUniversalFixedPath.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudMediaPath : QCloudUniversalFixedPath
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,12 @@
//
// QCloudMediaPath.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudMediaPath.h"
@implementation QCloudMediaPath
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudSandboxPath.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import "QCloudUniversalAdjustablePath.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudSandboxPath : QCloudUniversalAdjustablePath
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,15 @@
//
// QCloudSandboxPath.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudSandboxPath.h"
#import "QCloudFileUtils.h"
@implementation QCloudSandboxPath
- (NSURL *)fileURL {
NSString *restoredPath = QCloudGenerateLocalPath(self.originURL);
return [NSURL fileURLWithPath:restoredPath];
}
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudUniversalAdjustablePath.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import <QCloudCore/QCloudCore.h>
#import "QCloudUniversalPath.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudUniversalAdjustablePath : QCloudUniversalPath
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,12 @@
//
// QCloudUniversalAdjustablePath.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudUniversalAdjustablePath.h"
@implementation QCloudUniversalAdjustablePath
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudUniversalFixedPath.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import <QCloudCore/QCloudCore.h>
#import "QCloudUniversalPath.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudUniversalFixedPath : QCloudUniversalPath
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,18 @@
//
// QCloudUniversalFixedPath.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudUniversalFixedPath.h"
@implementation QCloudUniversalFixedPath
- (NSURL *)fileURL {
if ([self.originURL hasPrefix:@"file:///"]) {
return [NSURL URLWithString:self.originURL];
}else{
return [NSURL fileURLWithPath:self.originURL];
}
}
@end

View File

@@ -0,0 +1,21 @@
//
// QCloudUniversalPath.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import <Foundation/Foundation.h>
#import "QCloudUniversalPathConstants.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudUniversalPath : NSObject
@property (nonatomic, strong) NSString *originURL;
@property (nonatomic, assign) QCloudUniversalPathType type;
- (NSURL *)fileURL;
- (instancetype)initWithStrippedURL:(NSString *)strippedURL;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,22 @@
//
// QCloudUniversalPath.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudUniversalPath.h"
#import <QCloudCore/QCloudCore.h>
@implementation QCloudUniversalPath
- (instancetype)initWithStrippedURL:(NSString *)strippedURL {
self = [super init];
_originURL = strippedURL;
return self;
}
- (NSURL *)fileURL {
@throw [NSException exceptionWithName:QCloudErrorDomain
reason:[NSString stringWithFormat:@"不支持该路径下的文件续传:%@", _originURL]
userInfo:nil];
}
@end

View File

@@ -0,0 +1,18 @@
//
// QCloudUniversalPathConstants.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/21.
//
#ifndef QCloudUniversalPathConstants_h
#define QCloudUniversalPathConstants_h
typedef NS_ENUM(NSInteger, QCloudUniversalPathType) {
QCLOUD_UNIVERSAL_PATH_TYPE_FIXED,
QCLOUD_UNIVERSAL_PATH_TYPE_ADJUSTABLE,
QCLOUD_UNIVERSAL_PATH_TYPE_BUNDLE,
QCLOUD_UNIVERSAL_PATH_TYPE_MEDIA,
QCLOUD_UNIVERSAL_PATH_TYPE_SANDBOX
};
#endif /* QCloudUniversalPathConstants_h */

View File

@@ -0,0 +1,18 @@
//
// QCloudUniversalPathFactory.h
// QCloudCore
//
// Created by erichmzhang(张恒铭) on 2018/7/20.
//
#import <Foundation/Foundation.h>
#import "QCloudUniversalPath.h"
NS_ASSUME_NONNULL_BEGIN
@interface QCloudUniversalPathFactory : NSObject
+ (QCloudUniversalPath *)universalPathWithURL:(NSURL *)url;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,69 @@
//
// QCloudUniversalPathFactory.m
// QCloudCore
//
// Created by erichmzhang() on 2018/7/20.
//
#import "QCloudUniversalPathFactory.h"
#import "QCloudMediaPath.h"
#import "QCloudSandboxPath.h"
#import "QCloudBundlePath.h"
#import "QCloudFileUtils.h"
NSString *const kMediaURLPrefix = @"/var/mobile/Media/DCIM";
#define kBundlePath [NSBundle mainBundle].bundlePath
@interface NSString (UniversalPathExtension)
@end
@implementation NSString (UniversalPathExtension)
- (BOOL)isBundlePath {
return [self containsString:kBundlePath];
}
- (BOOL)isSandboxPath {
return [self containsString:QCloudApplicationDirectory()];
}
- (BOOL)isMediaPath {
return [self containsString:kMediaURLPrefix];
}
@end
@implementation QCloudUniversalPathFactory
+ (QCloudUniversalPath *)universalPathWithURL:(NSURL *)url {
QCloudUniversalPath *result;
NSString *strippedURL;
NSString *absoluteString = url.absoluteString;
if (!url && ![url isKindOfClass:NSURL.class]) {
QCloudLogDebugE(@"Utils",@"Nil paramater url!");
return nil;
}
if ([absoluteString isMediaPath]) {
strippedURL = absoluteString;
result = [[QCloudMediaPath alloc] initWithStrippedURL:strippedURL];
result.type = QCLOUD_UNIVERSAL_PATH_TYPE_MEDIA;
} else if ([absoluteString isBundlePath]) {
NSRange range = [absoluteString rangeOfString:kBundlePath];
strippedURL = [absoluteString substringFromIndex:range.location + range.length];
result = [[QCloudBundlePath alloc] initWithStrippedURL:strippedURL];
result.type = QCLOUD_UNIVERSAL_PATH_TYPE_BUNDLE;
} else if ([absoluteString isSandboxPath]) {
// sandbox
NSRange range = [absoluteString rangeOfString:QCloudApplicationDirectory()];
strippedURL = [absoluteString substringFromIndex:range.location + range.length];
result = [[QCloudSandboxPath alloc] initWithStrippedURL:strippedURL];
result.type = QCLOUD_UNIVERSAL_PATH_TYPE_SANDBOX;
} else {
// Unknown, not stripped
strippedURL = absoluteString;
result = [[QCloudUniversalFixedPath alloc] initWithStrippedURL:strippedURL];
result.type = QCLOUD_UNIVERSAL_PATH_TYPE_FIXED;
}
QCloudLogDebugP(@"Utils",@"Origin URL is %@ , stripped URL is %@", absoluteString, strippedURL);
return result;
}
@end

View File

@@ -0,0 +1,48 @@
//
// QCloudFileUtils.h
// Pods
//
// Created by stonedong on 16/3/6.
//
//
#import <Foundation/Foundation.h>
#import "QCloudSHAPart.h"
#ifndef __QCloudFileUtils
#define __QCloudFileUtils
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#define NSShareFileManager [NSFileManager defaultManager]
FOUNDATION_EXTERN void QCloudEnsurePathExist(NSString *path);
FOUNDATION_EXTERN NSString *QCloudDocumentsPath();
FOUNDATION_EXTERN NSString *QCloudDocumentsSubPath(NSString *name);
FOUNDATION_EXTERN NSString *QCloudSettingsFilePath();
FOUNDATION_EXTERN NSString *QCloudAppendPath();
FOUNDATION_EXTERN NSString *QCloudMKTempDirectory();
FOUNDATION_EXTERN NSString *QCloudPathJoin(NSString *a, NSString *b);
FOUNDATION_EXTERN NSString *QCloudTempDir();
FOUNDATION_EXTERN NSString *QCloudTempFilePathWithExtension(NSString *extension);
FOUNDATION_EXTERN NSString *QCloudCacheDir();
FOUNDATION_EXTERN void QCloudRemoveFileByPath(NSString *path);
FOUNDATION_EXTERN NSString *QCloudFileInSubPath(NSString *subPath, NSString *fileName);
FOUNDATION_EXTERN BOOL QCloudFileExist(NSString *path);
FOUNDATION_EXTERN BOOL QCloudMoveFile(NSString *originPath, NSString *aimPath, NSError *__autoreleasing *error);
FOUNDATION_EXTERN NSString *QCloudDocumentsTempFilePathWithExcentsion(NSString *extension);
FOUNDATION_EXTERN NSString *QCloudApplicationDocumentsPath();
FOUNDATION_EXTERN NSString *QCloudApplicationLibaryPath();
FOUNDATION_EXTERN NSString *QCloudApplicationTempPath();
FOUNDATION_EXTERN NSString *QCloudApplicationDirectory();
FOUNDATION_EXTERN NSString *QCloudFilteLocalPath(NSString *originPath);
FOUNDATION_EXTERN NSString *QCloudGenerateLocalPath(NSString *pathCompents);
FOUNDATION_EXTERN NSURL *QCloudMediaURL(NSString *path);
FOUNDATION_EXTERN NSString *QCloudDocumentsTempPath();
FOUNDATION_EXTERN NSString *QCloudDocumentsTempFile(NSString *fileName, NSString *extension);
FOUNDATION_EXTERN uint64_t QCloudFileSize(NSString *path);
FOUNDATION_EXTERN uint64_t QCloudDirectorySize(NSString *path, NSFileManager *fileManager);
FOUNDATION_EXTERN NSArray<QCloudSHAPart *> *QCloudIncreaseFileSHAData(NSString *path, uint64_t sliceSize);
FOUNDATION_EXTERN NSString *detemineFileMemeType(NSURL *filePathURL, NSString *fileName);
#pragma clang diagnostic pop
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
//
// QCloudModel.h
// Pods
//
// Created by Dong Zhao on 2017/3/8.
//
//
#import <Foundation/Foundation.h>
@interface QCloudModel : NSObject
@end

View File

@@ -0,0 +1,13 @@
//
// QCloudModel.m
// Pods
//
// Created by Dong Zhao on 2017/3/8.
//
//
#import "QCloudModel.h"
@implementation QCloudModel
@end

View File

@@ -0,0 +1,26 @@
//
// QCloudSHAPart.h
// Pods
//
// Created by Dong Zhao on 2017/3/8.
//
//
#import "QCloudModel.h"
@interface QCloudSHAPart : QCloudModel
/**
sha值
*/
@property (nonatomic, strong) NSString *datasha;
/**
offset
*/
@property (nonatomic, assign) uint64_t offset;
/**
长度
*/
@property (nonatomic, assign) uint64_t datalen;
@end

View File

@@ -0,0 +1,13 @@
//
// QCloudSHAPart.m
// Pods
//
// Created by Dong Zhao on 2017/3/8.
//
//
#import "QCloudSHAPart.h"
@implementation QCloudSHAPart
@end

View File

@@ -0,0 +1,320 @@
//
// NSObject+QCloudModel.h
// QCloudModel <https://github.com/ibireme/QCloudModel>
//
// Created by ibireme on 15/5/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
Provide some data-model method:
* Convert json to any object, or convert any object to json.
* Set object properties with a key-value dictionary (like KVC).
* Implementations of `NSCoding`, `NSCopying`, `-hash` and `-isEqual:`.
See `QCloudModel` protocol for custom methods.
*/
@interface NSObject (QCloudModel)
/**
Creates and returns a new instance of the receiver from a json.
This method is thread-safe.
@param json A json object in `NSDictionary`, `NSString` or `NSData`.
@return A new instance created from the json, or nil if an error occurs.
*/
+ (nullable instancetype)qcloud_modelWithJSON:(id)json;
/**
Creates and returns a new instance of the receiver from a key-value dictionary.
This method is thread-safe.
@param dictionary A key-value dictionary mapped to the instance's properties.
Any invalid key-value pair in dictionary will be ignored.
@return A new instance created from the dictionary, or nil if an error occurs.
@discussion The key in `dictionary` will mapped to the reciever's property name,
and the value will set to the property. If the value's type does not match the
property, this method will try to convert the value based on these rules:
`NSString` or `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger...
`NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd".
`NSString` -> NSURL.
`NSValue` -> struct or union, such as CGRect, CGSize, ...
`NSString` -> SEL, Class.
*/
+ (nullable instancetype)qcloud_modelWithDictionary:(NSDictionary *)dictionary;
/**
Set the receiver's properties with a json object.
@discussion Any invalid data in json will be ignored.
@param json A json object of `NSDictionary`, `NSString` or `NSData`, mapped to the
receiver's properties.
@return Whether succeed.
*/
- (BOOL)qcloud_modelSetWithJSON:(id)json;
/**
Set the receiver's properties with a key-value dictionary.
@param dic A key-value dictionary mapped to the receiver's properties.
Any invalid key-value pair in dictionary will be ignored.
@discussion The key in `dictionary` will mapped to the reciever's property name,
and the value will set to the property. If the value's type doesn't match the
property, this method will try to convert the value based on these rules:
`NSString`, `NSNumber` -> c number, such as BOOL, int, long, float, NSUInteger...
`NSString` -> NSDate, parsed with format "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd HH:mm:ss" or "yyyy-MM-dd".
`NSString` -> NSURL.
`NSValue` -> struct or union, such as CGRect, CGSize, ...
`NSString` -> SEL, Class.
@return Whether succeed.
*/
- (BOOL)qcloud_modelSetWithDictionary:(NSDictionary *)dic;
/**
Generate a json object from the receiver's properties.
@return A json object in `NSDictionary` or `NSArray`, or nil if an error occurs.
See [NSJSONSerialization isValidJSONObject] for more information.
@discussion Any of the invalid property is ignored.
If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it just convert
the inner object to json object.
*/
- (nullable id)qcloud_modelToJSONObject;
/**
Generate a json string's data from the receiver's properties.
@return A json string's data, or nil if an error occurs.
@discussion Any of the invalid property is ignored.
If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the
inner object to json string.
*/
- (nullable NSData *)qcloud_modelToJSONData;
/**
Generate a json string from the receiver's properties.
@return A json string, or nil if an error occurs.
@discussion Any of the invalid property is ignored.
If the reciver is `NSArray`, `NSDictionary` or `NSSet`, it will also convert the
inner object to json string.
*/
- (nullable NSString *)qcloud_modelToJSONString;
/**
Copy a instance with the receiver's properties.
@return A copied instance, or nil if an error occurs.
*/
- (nullable id)qcloud_modelCopy;
/**
Encode the receiver's properties to a coder.
@param aCoder An archiver object.
*/
- (void)qcloud_modelEncodeWithCoder:(NSCoder *)aCoder;
/**
Decode the receiver's properties from a decoder.
@param aDecoder An archiver object.
@return self
*/
- (id)qcloud_modelInitWithCoder:(NSCoder *)aDecoder;
/**
Get a hash code with the receiver's properties.
@return Hash code.
*/
- (NSUInteger)qcloud_modelHash;
/**
Compares the receiver with another object for equality, based on properties.
@param model Another object.
@return `YES` if the reciever is equal to the object, otherwise `NO`.
*/
- (BOOL)qcloud_modelIsEqual:(id)model;
/**
Description method for debugging purposes based on properties.
@return A string that describes the contents of the receiver.
*/
- (NSString *)qcloud_modelDescription;
@end
/**
Provide some data-model method for NSArray.
*/
@interface NSArray (QCloudModel)
/**
Creates and returns an array from a json-array.
This method is thread-safe.
@param cls The instance's class in array.
@param json A json array of `NSArray`, `NSString` or `NSData`.
Example: [{"name","Mary"},{name:"Joe"}]
@return A array, or nil if an error occurs.
*/
+ (nullable NSArray *)qcloud_modelArrayWithClass:(Class)cls json:(id)json;
@end
/**
Provide some data-model method for NSDictionary.
*/
@interface NSDictionary (QCloudModel)
/**
Creates and returns a dictionary from a json.
This method is thread-safe.
@param cls The value instance's class in dictionary.
@param json A json dictionary of `NSDictionary`, `NSString` or `NSData`.
Example: {"user1":{"name","Mary"}, "user2": {name:"Joe"}}
@return A dictionary, or nil if an error occurs.
*/
+ (nullable NSDictionary *)qcloud_modelDictionaryWithClass:(Class)cls json:(id)json;
@end
/**
If the default model transform does not fit to your model class, implement one or
more method in this protocol to change the default key-value transform process.
There's no need to add '<QCloudModel>' to your class header.
*/
@protocol QCloudModel <NSObject>
@optional
/**
Custom property mapper.
@discussion If the key in JSON/Dictionary does not match to the model's property name,
implements this method and returns the additional mapper.
@return A custom mapper for properties.
*/
+ (nullable NSDictionary<NSString *, id> *)modelCustomPropertyMapper;
/**
The generic class mapper for container properties.
@discussion If the property is a container object, such as NSArray/NSSet/NSDictionary,
implements this method and returns a property->class mapper, tells which kind of
object will be add to the array/set/dictionary.
@return A class mapper.
*/
+ (nullable NSDictionary<NSString *, id> *)modelContainerPropertyGenericClass;
/**
If you need to create instances of different classes during json->object transform,
use the method to choose custom class based on dictionary data.
@discussion If the model implements this method, it will be called to determine resulting class
during `+modelWithJSON:`, `+modelWithDictionary:`, conveting object of properties of parent objects
(both singular and containers via `+modelContainerPropertyGenericClass`).
@param dictionary The json/kv dictionary.
@return Class to create from this dictionary, `nil` to use current class.
*/
+ (nullable Class)modelCustomClassForDictionary:(NSDictionary *)dictionary;
/**
All the properties in blacklist will be ignored in model transform process.
Returns nil to ignore this feature.
@return An array of property's name.
*/
+ (nullable NSArray<NSString *> *)modelPropertyBlacklist;
/**
If a property is not in the whitelist, it will be ignored in model transform process.
Returns nil to ignore this feature.
@return An array of property's name.
*/
+ (nullable NSArray<NSString *> *)modelPropertyWhitelist;
/**
This method's behavior is similar to `- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;`,
but be called before the model transform.
@discussion If the model implements this method, it will be called before
`+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`.
If this method returns nil, the transform process will ignore this model.
@param dic The json/kv dictionary.
@return Returns the modified dictionary, or nil to ignore this model.
*/
- (NSDictionary *)modelCustomWillTransformFromDictionary:(NSDictionary *)dic;
/**
If the default json-to-model transform does not fit to your model object, implement
this method to do additional process. You can also use this method to validate the
model's properties.
@discussion If the model implements this method, it will be called at the end of
`+modelWithJSON:`, `+modelWithDictionary:`, `-modelSetWithJSON:` and `-modelSetWithDictionary:`.
If this method returns NO, the transform process will ignore this model.
@param dic The json/kv dictionary.
@return Returns YES if the model is valid, or NO to ignore this model.
*/
- (BOOL)modelCustomTransformFromDictionary:(NSDictionary *)dic;
/**
If the default model-to-json transform does not fit to your model class, implement
this method to do additional process. You can also use this method to validate the
json dictionary.
@discussion If the model implements this method, it will be called at the end of
`-modelToJSONObject` and `-modelToJSONString`.
If this method returns NO, the transform process will ignore this json dictionary.
@param dic The json dictionary.
@return Returns YES if the model is valid, or NO to ignore this model.
*/
- (BOOL)modelCustomTransformToDictionary:(NSMutableDictionary *)dic;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
//
// NSObject+QCloudModelTool.h
// QCloudCore
//
// Created by karisli(李雪) on 2021/8/2.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (QCloudModelTool)
+ (NSArray *)jsonsToModelsWithJsons:(NSArray *)jsons;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,21 @@
//
// NSObject+QCloudModelTool.m
// QCloudCore
//
// Created by karisli() on 2021/8/2.
//
#import "NSObject+QCloudModelTool.h"
#import "NSObject+QCloudModel.h"
@implementation NSObject (QCloudModelTool)
+ (NSArray *)jsonsToModelsWithJsons:(NSArray *)jsons {
NSMutableArray *models = [NSMutableArray array];
for (NSDictionary *json in jsons) {
id model = [[self class] qcloud_modelWithDictionary:json];
if (model) {
[models addObject:model];
}
}
return models;
}
@end

View File

@@ -0,0 +1,196 @@
//
// QCloudClassInfo.h
// QCloudModel <https://github.com/ibireme/QCloudModel>
//
// Created by ibireme on 15/5/9.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
NS_ASSUME_NONNULL_BEGIN
/**
Type encoding's type.
*/
typedef NS_OPTIONS(NSUInteger, QCloudEncodingType) {
QCloudEncodingTypeMask = 0xFF, ///< mask of type value
QCloudEncodingTypeUnknown = 0, ///< unknown
QCloudEncodingTypeVoid = 1, ///< void
QCloudEncodingTypeBool = 2, ///< bool
QCloudEncodingTypeInt8 = 3, ///< char / BOOL
QCloudEncodingTypeUInt8 = 4, ///< unsigned char
QCloudEncodingTypeInt16 = 5, ///< short
QCloudEncodingTypeUInt16 = 6, ///< unsigned short
QCloudEncodingTypeInt32 = 7, ///< int
QCloudEncodingTypeUInt32 = 8, ///< unsigned int
QCloudEncodingTypeInt64 = 9, ///< long long
QCloudEncodingTypeUInt64 = 10, ///< unsigned long long
QCloudEncodingTypeFloat = 11, ///< float
QCloudEncodingTypeDouble = 12, ///< double
QCloudEncodingTypeLongDouble = 13, ///< long double
QCloudEncodingTypeObject = 14, ///< id
QCloudEncodingTypeClass = 15, ///< Class
QCloudEncodingTypeSEL = 16, ///< SEL
QCloudEncodingTypeBlock = 17, ///< block
QCloudEncodingTypePointer = 18, ///< void*
QCloudEncodingTypeStruct = 19, ///< struct
QCloudEncodingTypeUnion = 20, ///< union
QCloudEncodingTypeCString = 21, ///< char*
QCloudEncodingTypeCArray = 22, ///< char[10] (for example)
QCloudEncodingTypeQualifierMask = 0xFF00, ///< mask of qualifier
QCloudEncodingTypeQualifierConst = 1 << 8, ///< const
QCloudEncodingTypeQualifierIn = 1 << 9, ///< in
QCloudEncodingTypeQualifierInout = 1 << 10, ///< inout
QCloudEncodingTypeQualifierOut = 1 << 11, ///< out
QCloudEncodingTypeQualifierBycopy = 1 << 12, ///< bycopy
QCloudEncodingTypeQualifierByref = 1 << 13, ///< byref
QCloudEncodingTypeQualifierOneway = 1 << 14, ///< oneway
QCloudEncodingTypePropertyMask = 0xFF0000, ///< mask of property
QCloudEncodingTypePropertyReadonly = 1 << 16, ///< readonly
QCloudEncodingTypePropertyCopy = 1 << 17, ///< copy
QCloudEncodingTypePropertyRetain = 1 << 18, ///< retain
QCloudEncodingTypePropertyNonatomic = 1 << 19, ///< nonatomic
QCloudEncodingTypePropertyWeak = 1 << 20, ///< weak
QCloudEncodingTypePropertyCustomGetter = 1 << 21, ///< getter=
QCloudEncodingTypePropertyCustomSetter = 1 << 22, ///< setter=
QCloudEncodingTypePropertyDynamic = 1 << 23, ///< @dynamic
};
/**
Get the type from a Type-Encoding string.
@discussion See also:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
@param typeEncoding A Type-Encoding string.
@return The encoding type.
*/
QCloudEncodingType QCloudEncodingGetType(const char *typeEncoding);
/**
Instance variable information.
*/
@interface QCloudClassIvarInfo : NSObject
@property (nonatomic, assign, readonly) Ivar ivar; ///< ivar opaque struct
@property (nonatomic, strong, readonly) NSString *name; ///< Ivar's name
@property (nonatomic, assign, readonly) ptrdiff_t offset; ///< Ivar's offset
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< Ivar's type encoding
@property (nonatomic, assign, readonly) QCloudEncodingType type; ///< Ivar's type
/**
Creates and returns an ivar info object.
@param ivar ivar opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithIvar:(Ivar)ivar;
@end
/**
Method information.
*/
@interface QCloudClassMethodInfo : NSObject
@property (nonatomic, assign, readonly) Method method; ///< method opaque struct
@property (nonatomic, strong, readonly) NSString *name; ///< method name
@property (nonatomic, assign, readonly) SEL sel; ///< method's selector
@property (nonatomic, assign, readonly) IMP imp; ///< method's implementation
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< method's parameter and return types
@property (nonatomic, strong, readonly) NSString *returnTypeEncoding; ///< return value's type
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *argumentTypeEncodings; ///< array of arguments' type
/**
Creates and returns a method info object.
@param method method opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithMethod:(Method)method;
@end
/**
Property information.
*/
@interface QCloudClassPropertyInfo : NSObject
@property (nonatomic, assign, readonly) objc_property_t property ; ///< property's opaque struct
@property (nonatomic, strong, readonly) NSString *name; ///< property's name
@property (nonatomic, assign, readonly) QCloudEncodingType type; ///< property's type
@property (nonatomic, strong, readonly) NSString *typeEncoding; ///< property's encoding value
@property (nonatomic, strong, readonly) NSString *ivarName; ///< property's ivar name
@property (nullable, nonatomic, assign, readonly) Class cls; ///< may be nil
@property (nullable, nonatomic, strong, readonly) NSArray<NSString *> *protocols; ///< may nil
@property (nonatomic, assign, readonly) SEL getter; ///< getter (nonnull)
@property (nonatomic, assign, readonly) SEL setter; ///< setter (nonnull)
/**
Creates and returns a property info object.
@param property property opaque struct
@return A new object, or nil if an error occurs.
*/
- (instancetype)initWithProperty:(objc_property_t)property;
@end
/**
Class information for a class.
*/
@interface QCloudClassInfo : NSObject
@property (nonatomic, assign, readonly) Class cls; ///< class object
@property (nullable, nonatomic, assign, readonly) Class superCls; ///< super class object
@property (nullable, nonatomic, assign, readonly) Class metaCls; ///< class's meta class object
@property (nonatomic, readonly) BOOL isMeta; ///< whether this class is meta class
@property (nonatomic, strong, readonly) NSString *name; ///< class name
@property (nullable, nonatomic, strong, readonly) QCloudClassInfo *superClassInfo; ///< super class's class info
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, QCloudClassIvarInfo *> *ivarInfos; ///< ivars
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, QCloudClassMethodInfo *> *methodInfos; ///< methods
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, QCloudClassPropertyInfo *> *propertyInfos; ///< properties
/**
If the class is changed (for example: you add a method to this class with
'class_addMethod()'), you should call this method to refresh the class info cache.
After called this method, `needUpdate` will returns `YES`, and you should call
'classInfoWithClass' or 'classInfoWithClassName' to get the updated class info.
*/
- (void)setNeedUpdate;
/**
If this method returns `YES`, you should stop using this instance and call
`classInfoWithClass` or `classInfoWithClassName` to get the updated class info.
@return Whether this class info need update.
*/
- (BOOL)needUpdate;
/**
Get the class info of a specified Class.
@discussion This method will cache the class info and super-class info
at the first access to the Class. This method is thread-safe.
@param cls A class.
@return A class info, or nil if an error occurs.
*/
+ (nullable instancetype)classInfoWithClass:(Class)cls;
/**
Get the class info of a specified Class.
@discussion This method will cache the class info and super-class info
at the first access to the Class. This method is thread-safe.
@param className A class name.
@return A class info, or nil if an error occurs.
*/
+ (nullable instancetype)classInfoWithClassName:(NSString *)className;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,407 @@
//
// QCloudClassInfo.m
// QCloudModel <https://github.com/ibireme/QCloudModel>
//
// Created by ibireme on 15/5/9.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "QCloudClassInfo.h"
#import <objc/runtime.h>
QCloudEncodingType QCloudEncodingGetType(const char *typeEncoding) {
char *type = (char *)typeEncoding;
if (!type)
return QCloudEncodingTypeUnknown;
size_t len = strlen(type);
if (len == 0)
return QCloudEncodingTypeUnknown;
QCloudEncodingType qualifier = 0;
bool prefix = true;
while (prefix) {
switch (*type) {
case 'r': {
qualifier |= QCloudEncodingTypeQualifierConst;
type++;
} break;
case 'n': {
qualifier |= QCloudEncodingTypeQualifierIn;
type++;
} break;
case 'N': {
qualifier |= QCloudEncodingTypeQualifierInout;
type++;
} break;
case 'o': {
qualifier |= QCloudEncodingTypeQualifierOut;
type++;
} break;
case 'O': {
qualifier |= QCloudEncodingTypeQualifierBycopy;
type++;
} break;
case 'R': {
qualifier |= QCloudEncodingTypeQualifierByref;
type++;
} break;
case 'V': {
qualifier |= QCloudEncodingTypeQualifierOneway;
type++;
} break;
default: {
prefix = false;
} break;
}
}
len = strlen(type);
if (len == 0)
return QCloudEncodingTypeUnknown | qualifier;
switch (*type) {
case 'v':
return QCloudEncodingTypeVoid | qualifier;
case 'B':
return QCloudEncodingTypeBool | qualifier;
case 'c':
return QCloudEncodingTypeInt8 | qualifier;
case 'C':
return QCloudEncodingTypeUInt8 | qualifier;
case 's':
return QCloudEncodingTypeInt16 | qualifier;
case 'S':
return QCloudEncodingTypeUInt16 | qualifier;
case 'i':
return QCloudEncodingTypeInt32 | qualifier;
case 'I':
return QCloudEncodingTypeUInt32 | qualifier;
case 'l':
return QCloudEncodingTypeInt32 | qualifier;
case 'L':
return QCloudEncodingTypeUInt32 | qualifier;
case 'q':
return QCloudEncodingTypeInt64 | qualifier;
case 'Q':
return QCloudEncodingTypeUInt64 | qualifier;
case 'f':
return QCloudEncodingTypeFloat | qualifier;
case 'd':
return QCloudEncodingTypeDouble | qualifier;
case 'D':
return QCloudEncodingTypeLongDouble | qualifier;
case '#':
return QCloudEncodingTypeClass | qualifier;
case ':':
return QCloudEncodingTypeSEL | qualifier;
case '*':
return QCloudEncodingTypeCString | qualifier;
case '^':
return QCloudEncodingTypePointer | qualifier;
case '[':
return QCloudEncodingTypeCArray | qualifier;
case '(':
return QCloudEncodingTypeUnion | qualifier;
case '{':
return QCloudEncodingTypeStruct | qualifier;
case '@': {
if (len == 2 && *(type + 1) == '?')
return QCloudEncodingTypeBlock | qualifier;
else
return QCloudEncodingTypeObject | qualifier;
}
default:
return QCloudEncodingTypeUnknown | qualifier;
}
}
@implementation QCloudClassIvarInfo
- (instancetype)initWithIvar:(Ivar)ivar {
if (!ivar)
return nil;
self = [super init];
_ivar = ivar;
const char *name = ivar_getName(ivar);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
_offset = ivar_getOffset(ivar);
const char *typeEncoding = ivar_getTypeEncoding(ivar);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
_type = QCloudEncodingGetType(typeEncoding);
}
return self;
}
@end
@implementation QCloudClassMethodInfo
- (instancetype)initWithMethod:(Method)method {
if (!method)
return nil;
self = [super init];
_method = method;
_sel = method_getName(method);
_imp = method_getImplementation(method);
const char *name = sel_getName(_sel);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
const char *typeEncoding = method_getTypeEncoding(method);
if (typeEncoding) {
_typeEncoding = [NSString stringWithUTF8String:typeEncoding];
}
char *returnType = method_copyReturnType(method);
if (returnType) {
_returnTypeEncoding = [NSString stringWithUTF8String:returnType];
free(returnType);
}
unsigned int argumentCount = method_getNumberOfArguments(method);
if (argumentCount > 0) {
NSMutableArray *argumentTypes = [NSMutableArray new];
for (unsigned int i = 0; i < argumentCount; i++) {
char *argumentType = method_copyArgumentType(method, i);
NSString *type = argumentType ? [NSString stringWithUTF8String:argumentType] : nil;
[argumentTypes addObject:type ? type : @""];
if (argumentType)
free(argumentType);
}
_argumentTypeEncodings = argumentTypes;
}
return self;
}
@end
@implementation QCloudClassPropertyInfo
- (instancetype)initWithProperty:(objc_property_t)property {
if (!property)
return nil;
self = [super init];
_property = property;
const char *name = property_getName(property);
if (name) {
_name = [NSString stringWithUTF8String:name];
}
QCloudEncodingType type = 0;
unsigned int attrCount;
objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int i = 0; i < attrCount; i++) {
switch (attrs[i].name[0]) {
case 'T': { // Type encoding
if (attrs[i].value) {
_typeEncoding = [NSString stringWithUTF8String:attrs[i].value];
type = QCloudEncodingGetType(attrs[i].value);
if ((type & QCloudEncodingTypeMask) == QCloudEncodingTypeObject && _typeEncoding.length) {
NSScanner *scanner = [NSScanner scannerWithString:_typeEncoding];
if (![scanner scanString:@"@\"" intoString:NULL])
continue;
NSString *clsName = nil;
if ([scanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"<"] intoString:&clsName]) {
if (clsName.length)
_cls = objc_getClass(clsName.UTF8String);
}
NSMutableArray *protocols = nil;
while ([scanner scanString:@"<" intoString:NULL]) {
NSString *protocol = nil;
if ([scanner scanUpToString:@">" intoString:&protocol]) {
if (protocol.length) {
if (!protocols)
protocols = [NSMutableArray new];
[protocols addObject:protocol];
}
}
[scanner scanString:@">" intoString:NULL];
}
_protocols = protocols;
}
}
} break;
case 'V': { // Instance variable
if (attrs[i].value) {
_ivarName = [NSString stringWithUTF8String:attrs[i].value];
}
} break;
case 'R': {
type |= QCloudEncodingTypePropertyReadonly;
} break;
case 'C': {
type |= QCloudEncodingTypePropertyCopy;
} break;
case '&': {
type |= QCloudEncodingTypePropertyRetain;
} break;
case 'N': {
type |= QCloudEncodingTypePropertyNonatomic;
} break;
case 'D': {
type |= QCloudEncodingTypePropertyDynamic;
} break;
case 'W': {
type |= QCloudEncodingTypePropertyWeak;
} break;
case 'G': {
type |= QCloudEncodingTypePropertyCustomGetter;
if (attrs[i].value) {
_getter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} break;
case 'S': {
type |= QCloudEncodingTypePropertyCustomSetter;
if (attrs[i].value) {
_setter = NSSelectorFromString([NSString stringWithUTF8String:attrs[i].value]);
}
} // break; commented for code coverage in next line
default:
break;
}
}
if (attrs) {
free(attrs);
attrs = NULL;
}
_type = type;
if (_name.length) {
if (!_getter) {
_getter = NSSelectorFromString(_name);
}
if (!_setter && _name.length) {
_setter = NSSelectorFromString(
[NSString stringWithFormat:@"set%@%@:", [_name substringToIndex:1].uppercaseString, [_name substringFromIndex:1]]);
}
}
return self;
}
@end
@implementation QCloudClassInfo {
BOOL _needUpdate;
}
- (instancetype)initWithClass:(Class)cls {
if (!cls)
return nil;
self = [super init];
_cls = cls;
_superCls = class_getSuperclass(cls);
_isMeta = class_isMetaClass(cls);
if (!_isMeta) {
_metaCls = objc_getMetaClass(class_getName(cls));
}
_name = NSStringFromClass(cls);
[self _update];
_superClassInfo = [self.class classInfoWithClass:_superCls];
return self;
}
- (void)_update {
_ivarInfos = nil;
_methodInfos = nil;
_propertyInfos = nil;
Class cls = self.cls;
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(cls, &methodCount);
if (methods) {
NSMutableDictionary *methodInfos = [NSMutableDictionary new];
_methodInfos = methodInfos;
for (unsigned int i = 0; i < methodCount; i++) {
QCloudClassMethodInfo *info = [[QCloudClassMethodInfo alloc] initWithMethod:methods[i]];
if (info.name)
methodInfos[info.name] = info;
}
free(methods);
}
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
if (properties) {
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
_propertyInfos = propertyInfos;
for (unsigned int i = 0; i < propertyCount; i++) {
QCloudClassPropertyInfo *info = [[QCloudClassPropertyInfo alloc] initWithProperty:properties[i]];
if (info.name)
propertyInfos[info.name] = info;
}
free(properties);
}
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(cls, &ivarCount);
if (ivars) {
NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
_ivarInfos = ivarInfos;
for (unsigned int i = 0; i < ivarCount; i++) {
QCloudClassIvarInfo *info = [[QCloudClassIvarInfo alloc] initWithIvar:ivars[i]];
if (info.name)
ivarInfos[info.name] = info;
}
free(ivars);
}
if (!_ivarInfos)
_ivarInfos = @{};
if (!_methodInfos)
_methodInfos = @{};
if (!_propertyInfos)
_propertyInfos = @{};
_needUpdate = NO;
}
- (void)setNeedUpdate {
_needUpdate = YES;
}
- (BOOL)needUpdate {
return _needUpdate;
}
+ (instancetype)classInfoWithClass:(Class)cls {
if (!cls)
return nil;
static CFMutableDictionaryRef classCache;
static CFMutableDictionaryRef metaCache;
static dispatch_once_t onceToken;
static dispatch_semaphore_t lock;
dispatch_once(&onceToken, ^{
classCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
metaCache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
lock = dispatch_semaphore_create(1);
});
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
QCloudClassInfo *info = CFDictionaryGetValue(class_isMetaClass(cls) ? metaCache : classCache, (__bridge const void *)(cls));
if (info && info->_needUpdate) {
[info _update];
}
dispatch_semaphore_signal(lock);
if (!info) {
info = [[QCloudClassInfo alloc] initWithClass:cls];
if (info) {
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
CFDictionarySetValue(info.isMeta ? metaCache : classCache, (__bridge const void *)(cls), (__bridge const void *)(info));
dispatch_semaphore_signal(lock);
}
}
return info;
}
+ (instancetype)classInfoWithClassName:(NSString *)className {
Class cls = NSClassFromString(className);
return [self classInfoWithClass:cls];
}
@end

View File

@@ -0,0 +1,22 @@
//
// QCloudModel.h
// QCloudModel <https://github.com/ibireme/QCloudModel>
//
// Created by ibireme on 15/5/10.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
#if __has_include(<QCloudCore/QCloudCore.h>)
FOUNDATION_EXPORT double QCloudModelVersionNumber;
FOUNDATION_EXPORT const unsigned char QCloudModelVersionString[];
#import <QCloudCore/NSObject+QCloudModel.h>
#import <QCloudCore/QCloudClassInfo.h>
#else
#import "NSObject+QCloudModel.h"
#import "QCloudClassInfo.h"
#endif

View File

@@ -0,0 +1,98 @@
//
// DZProgrameDefines.h
// TimeUI
//
// Created by Stone Dong on 14-1-21.
// Copyright (c) 2014年 Stone Dong. All rights reserved.
//
#import <Foundation/Foundation.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wstrict-prototypes"
#define DEFINE_PROPERTY_KEY(key) static void const kPK##key = &kPK##key
/**
* 定义字符串
*/
#define DEFINE_NSString(str) static NSString *const kDZ##str = @"" #str;
#define DEFINE_NSStringValue(str, value) static NSString *const kDZ##str = @"" #value;
#define DEFINE_NOTIFICATION_MESSAGE(str) static NSString *const kDZNotification_##str = @"" #str;
#define DEFINE_PROPERTY_READONLY(mnmKind, type, name) @property (nonatomic, mnmKind, readonly) type name
#define DEFINE_PROPERTY(mnmKind, type, name) @property (nonatomic, mnmKind) type name
#define DEFINE_PROPERTY_ASSIGN(type, name) DEFINE_PROPERTY(assign, type, name)
#define DEFINE_PROPERTY_ASSIGN_Float(name) DEFINE_PROPERTY_ASSIGN(float, name)
#define DEFINE_PROPERTY_ASSIGN_INT64(name) DEFINE_PROPERTY_ASSIGN(int64_t, name)
#define DEFINE_PROPERTY_ASSIGN_INT32(name) DEFINE_PROPERTY_ASSIGN(int32_t, name)
#define DEFINE_PROPERTY_ASSIGN_INT16(name) DEFINE_PROPERTY_ASSIGN(int16_t, name)
#define DEFINE_PROPERTY_ASSIGN_INT8(name) DEFINE_PROPERTY_ASSIGN(int8_t, name)
#define DEFINE_PROPERTY_ASSIGN_Double(name) DEFINE_PROPERTY_ASSIGN(double, name)
#define DEFINE_PROPERTY_ASSIGN_BOOL(name) DEFINE_PROPERTY_ASSIGN(BOOL, name)
#define DEFINE_PROPERTY_STRONG_READONLY(type, name) DEFINE_PROPERTY_READONLY(strong, type, name)
#define DEFINE_PROPERTY_STRONG(type, name) DEFINE_PROPERTY(strong, type, name)
#define DEFINE_PROPERTY_STRONG_UILabel(name) DEFINE_PROPERTY_STRONG(UILabel *, name)
#define DEFINE_PROPERTY_STRONG_NSString(name) DEFINE_PROPERTY_STRONG(NSString *, name)
#define DEFINE_PROPERTY_STRONG_NSDate(name) DEFINE_PROPERTY_STRONG(NSDate *, name)
#define DEFINE_PROPERTY_STRONG_NSArray(name) DEFINE_PROPERTY_STRONG(NSArray *, name)
#define DEFINE_PROPERTY_STRONG_UIImageView(name) DEFINE_PROPERTY_STRONG(UIImageView *, name)
#define DEFINE_PROPERTY_STRONG_UIButton(name) DEFINE_PROPERTY_STRONG(UIButton *, name)
#define DEFINE_PROPERTY_WEAK(type, name) DEFINE_PROPERTY(weak, type, name)
#define DEFINE_DZ_EXTERN_STRING(key) extern NSString *const key;
#define INIT_DZ_EXTERN_STRING(key, value) NSString *const key = @"" #value;
#define DZ_CheckObjcetClass(object, cla) [object isKindOfClass:[cla class]]
/**
数据类型的转化
*/
#define DZ_STR_2_URL(str) (([str hasPrefix:@"http"] || !str) ? [NSURL URLWithString:str] : [NSURL fileURLWithPath:str])
#define DZ_NUM_2_STR(num) [@(num) stringValue]
// Notification defaults
#define DZExternObserverMessage(msg) \
void DZAddObserverFor##msg(NSObject *ob, SEL selector); \
void DZRemoveObserverFor##msg(NSObject *ob); \
void DZPost##msg(NSDictionary *dic);
#define DZObserverMessage(message) \
void DZAddObserverFor##message(NSObject *ob, SEL selector) { \
[[NSNotificationCenter defaultCenter] addObserver:ob selector:selector name:@"" #message object:nil]; \
} \
\
void DZRemoveObserverFor##message(NSObject *ob) { \
[[NSNotificationCenter defaultCenter] removeObserver:ob name:@"" #message object:nil]; \
} \
\
void DZPost##message(NSDictionary *dic) { \
[[NSNotificationCenter defaultCenter] postNotificationName:@"" #message object:nil userInfo:dic]; \
}
FOUNDATION_EXTERN void DZEnsureMainThread(void (^mainSafeBlock)());
#define DZEnsureMainThreadBegin DZEnsureMainThread(^{
#define DZEnsureMainThreadEnd \
});
//定义block中使用的变量
#define QCloudWeakSelf(type) __weak typeof(type) weak##type = type
#define QCloudStrongSelf(type) __strong typeof(weak##type) strong##type = weak##type
#pragma mark----
#define bQCloudSystemVersion(min, max) \
([UIDevice currentDevice].systemVersion.doubleValue >= min) && ([UIDevice currentDevice].systemVersion.doubleValue <= max)
#define bQCloudSystemVersion8 bQCloudSystemVersion(8.0, 8.999)
#pragma clang diagnostic pop

View File

@@ -0,0 +1,21 @@
//
// DZProgramDefines.m
// TimeUI
//
// Created by Stone Dong on 14-1-21.
// Copyright (c) 2014 Stone Dong. All rights reserved.
//
#import "QCloudProgrameDefines.h"
#import <objc/runtime.h>
void DZEnsureMainThread(void (^mainSafeBlock)(void)) {
if (mainSafeBlock == NULL) {
return;
}
if ([NSThread isMainThread]) {
mainSafeBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), mainSafeBlock);
}
}

View File

@@ -0,0 +1,32 @@
//
// QCloudNetworkingAPI.h
// QCloudTernimalLab_CommonLogic
//
// Created by tencent on 5/12/16.
// Copyright © 2016 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QCloudHTTPRequestDelegate.h"
extern NSString *const kQCloudRestNetURLUsageNotification;
@interface NSDictionary (QCloudRestNetUsage)
- (NSURL *)bdwt_RestNetCoreUsagedURL;
@end
@class QCloudHTTPRequest;
@class QCloudRequestOperation;
@protocol QCloudNetworkingAPI <NSObject>
/**
最大并发的网络线程数量
*/
@property (atomic, assign) int32_t maxConcurrencyTask;
+ (NSObject<QCloudNetworkingAPI> *)shareClient;
- (int)performRequest:(QCloudHTTPRequest *)httpRequst;
- (int)performRequest:(QCloudHTTPRequest *)httpRequst withFinishBlock:(QCloudRequestFinishBlock)block;
- (void)cancelRequestWithID:(int)requestID;
- (void)cancelAllRequest;
- (void)cancelRequestsWithID:(NSArray<NSNumber *> *)requestIDs;
- (void)executeRestHTTPReqeust:(QCloudHTTPRequest *)httpRequest;
@end

View File

@@ -0,0 +1,14 @@
//
// NSHTTPCookie+QCloudNetworking.h
// QCloudNetworking
//
// Created by tencent on 15/9/29.
// Copyright © 2015年 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
FOUNDATION_EXTERN NSArray *QCloudFuseAndUpdateCookiesArray(NSArray *source, NSArray *aim);
@interface NSHTTPCookie (QCloudNetworking)
- (BOOL)isEqualToQCloudCookie:(NSHTTPCookie *)c;
@end

View File

@@ -0,0 +1,43 @@
//
// NSHTTPCookie+QCloudNetworking.m
// QCloudNetworking
//
// Created by tencent on 15/9/29.
// Copyright © 2015 QCloudTernimalLab. All rights reserved.
//
#import "NSHTTPCookie+QCloudNetworking.h"
NSArray *QCloudFuseAndUpdateCookiesArray(NSArray *source, NSArray *aim) {
NSMutableArray *aimArray = [NSMutableArray new];
if (source.count) {
[aimArray addObjectsFromArray:source];
}
for (NSHTTPCookie *s in source) {
if (![aimArray containsObject:s]) {
[aimArray addObject:s];
}
}
return aimArray;
}
@implementation NSHTTPCookie (QCloudNetworking)
- (BOOL)isEqualToQCloudCookie:(NSHTTPCookie *)c {
if (![c.name isEqualToString:self.name]) {
return NO;
}
if (![c.value isEqualToString:self.value]) {
return NO;
}
if (![c.path isEqualToString:self.path]) {
return NO;
}
NSString *maxDomain = c.domain.length > self.domain.length ? c.domain : self.domain;
NSString *minDomain = c.domain.length < self.domain.length ? c.domain : self.domain;
if ([maxDomain hasSuffix:minDomain]) {
return YES;
}
return NO;
}
@end

View File

@@ -0,0 +1,134 @@
//
// QCloudAbstractRequest.h
// Pods
//
// Created by Dong Zhao on 2017/3/10.
//
//
#import <Foundation/Foundation.h>
#import "QCloudHTTPRequestDelegate.h"
#import "QCloudHttpMetrics.h"
#import "QCloudCredential.h"
#import "QCloudEndPoint.h"
#import "QCloudSignature.h"
typedef double QCloudAbstractRequestPriority;
#define QCloudAbstractRequestPriorityHigh 3.0
#define QCloudAbstractRequestPriorityNormal 2.0
#define QCloudAbstractRequestPriorityLow 1.0
#define QCloudAbstractRequestPriorityBackground 0.0
typedef void (^QCloudRequestSendProcessBlock)(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend);
typedef void (^QCloudRequestDownProcessBlock)(int64_t bytesDownload, int64_t totalBytesDownload, int64_t totalBytesExpectedToDownload);
typedef void (^QCloudRequestDownProcessWithDataBlock)(int64_t bytesDownload, int64_t totalBytesDownload, int64_t totalBytesExpectedToDownload,
NSData * _Nullable receivedData);
typedef NS_ENUM(NSUInteger, QCloudRequestNetworkType) {
QCloudRequestNetworkNone = 0,
QCloudRequestNetworkHttp,
QCloudRequestNetworkQuic,
};
/**
请求的抽象基类该类封装了用于进行request-response模式数据请求的通用属性和接口。包括发起请求相应结果优先级处理性能监控能常见特性。
*/
@interface QCloudAbstractRequest : NSObject {
@protected
int64_t _requestID;
}
/**
签名信息的回调接口,该委托必须实现。签名是腾讯云进行服务时进行用户身份校验的关键手段,同时也保障了用户访问的安全性。该委托中通过函数回调来提供签名信息。
*/
/**
指定接口级请求域名
*/
@property (nonatomic, strong, nullable) QCloudEndPoint * endpoint;
/// 用于外部指定网络请求使用何种协议。默认QCloudRequestNetworkHttp。优先级高于config.enableQuic。
@property (nonatomic, assign)QCloudRequestNetworkType networkType;
/// sdk 内部使用,外部设置无效。
@property (nonatomic, assign) BOOL enableQuic;
@property (atomic, assign) BOOL forbidCancelled;
@property (atomic, assign) BOOL requestRetry;
@property (atomic, assign, readonly) BOOL canceled;
@property (nonatomic, assign, readonly) int64_t requestID;
@property (nonatomic, assign) QCloudAbstractRequestPriority priority;
@property (nonatomic, strong, readonly) QCloudHttpMetrics *_Nullable benchMarkMan;
@property (atomic, assign, readonly) BOOL finished;
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
/**
用于业务中携带与request关联的参数不参与SDK内部逻辑与网络请求
*/
@property (nonatomic, strong ,nullable) NSDictionary * payload;
/**
设置接口级参与签头部和参数。
默认下方所有字段参与签名无需设置若指定某个字段不参与签名则将相应的字段删除然后将数组赋值给shouldSignedList即可。
@[@"Cache-Control", @"Content-Disposition", @"Content-Encoding", @"Content-Length", @"Content-MD5", @"Content-Type", @"Expect", @"Expires", @"If-Match" , @"If-Modified-Since" , @"If-None-Match" , @"If-Unmodified-Since" , @"Origin" , @"Range" , @"transfer-encoding" ,@"Host",@"Pic-Operations",@"ci-process"]
示例:
1、指定Host不参与签名(删除数组中的@"Host")。
shouldSignedList = @[@"Cache-Control", @"Content-Disposition", @"Content-Encoding", @"Content-Length", @"Content-MD5", @"Content-Type", @"Expect", @"Expires", @"If-Match" , @"If-Modified-Since" , @"If-None-Match" , @"If-Unmodified-Since" , @"Origin" , @"Range" , @"transfer-encoding" ,@"Pic-Operations",@"ci-process"];
2、指定所有字段都不参与签名。
shouldSignedList = @[];
*/
@property (nonatomic, strong, nullable) NSArray *shouldSignedList;
@property (nonatomic, strong, nullable) QCloudCredential * credential;
@property (nonatomic, strong, nullable) QCloudSignature * signature;
/**
协议执行结果向外通知的委托delegate主要包括成功和失败两种情况。与Block方式并存当两者都设置的时候都会通知。
*/
@property (nonatomic, weak) id<QCloudHTTPRequestDelegate> _Nullable delegate;
/**
协议执行结果向外通知的Block与delegate方式并存当两者都设置的时候都会通知。
*/
@property (nonatomic, strong) QCloudRequestFinishBlock _Nullable finishBlock;
@property (nonatomic, strong) QCloudRequestSendProcessBlock _Nullable sendProcessBlock;
@property (nonatomic, strong) QCloudRequestDownProcessBlock _Nullable downProcessBlock;
@property (nonatomic, strong) QCloudRequestDownProcessWithDataBlock _Nullable downProcessWithDataBlock;
- (void)setFinishBlock:(void (^_Nullable)(id _Nullable outputObject, NSError *_Nullable error))QCloudRequestFinishBlock;
- (void)setDownProcessBlock:(void (^_Nullable)(int64_t bytesDownload, int64_t totalBytesDownload,
int64_t totalBytesExpectedToDownload))downloadProcessBlock;
- (void)setDownProcessWithDataBlock:(void (^_Nullable)(int64_t bytesDownload, int64_t totalBytesDownload,
int64_t totalBytesExpectedToDownload, NSData * _Nullable receiveData))downloadProcessWithDataBlock;
- (void)setSendProcessBlock:(void (^_Nullable)(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))sendProcessBlock;
/**
请求过程出错进行处理。默认只处理HTTP协议层错误信息。并进行delegate的通知。
@param error 请求过程出错信息默认只处理HTTP层错误信息
*/
- (void)onError:(NSError *_Nonnull)error;
/**
请求过程成功并获取到了数据进行处理。并进行delegate的通知。
@param object 获取到的数据经过responseserilizer处理的后的数据。
*/
- (void)onSuccess:(id _Nonnull)object;
- (void)notifySuccess:(id _Nonnull)object;
- (void)notifyError:(NSError *_Nonnull)error;
- (void)notifyDownloadProgressBytesDownload:(int64_t)bytesDownload
totalBytesDownload:(int64_t)totalBytesDownload
totalBytesExpectedToDownload:(int64_t)totalBytesExpectedToDownload;
- (void)notifyDownloadProgressBytesDownload:(int64_t)bytesDownload
totalBytesDownload:(int64_t)totalBytesDownload
totalBytesExpectedToDownload:(int64_t)totalBytesExpectedToDownload
receivedData:(NSData *_Nullable)data;
- (void)notifySendProgressBytesSend:(int64_t)bytesSend
totalBytesSend:(int64_t)totalBytesSend
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (void)cancel;
- (void)waitForComplete;
- (void)configTaskResume;
@end

View File

@@ -0,0 +1,240 @@
//
// QCloudAbstractRequest.m
// Pods
//
// Created by Dong Zhao on 2017/3/10.
//
//
#import "QCloudAbstractRequest.h"
#import "QCloudLogger.h"
#import "QCloudNetEnv.h"
#import "NSError+QCloudNetworking.h"
__attribute__((noinline)) void cosWarnBlockingOperationOnMainThread(void) {
NSLog(@"Warning: A long-running operation is being executed on the main thread. \n"
" Break on warnBlockingOperationOnMainThread() to debug.");
}
@interface QCloudAbstractRequest ()
@property (nonatomic, strong) NSObject *lock;
@property (nonatomic, strong) NSCondition *condition;
@end
@implementation QCloudAbstractRequest
@synthesize requestID = _requestID;
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_lock = [[NSObject alloc] init];
_condition = [[NSCondition alloc] init];
_benchMarkMan = [QCloudHttpMetrics new];
_priority = QCloudAbstractRequestPriorityHigh;
static int64_t requestID = 3333;
_requestID = (++ requestID) * 1000 + arc4random_uniform(1000);
_finished = NO;
return self;
}
- (void)__notifyError:(NSError *)error {
[self.condition broadcast];
// [self.benchMarkMan benginWithKey:kRNBenchmarkLogicOnly];
if ([self.delegate respondsToSelector:@selector(QCloudHTTPRequestDidFinished:failed:)]) {
[self.delegate QCloudHTTPRequestDidFinished:self failed:error];
}
if (self.finishBlock) {
self.finishBlock(nil, error);
}
self.finishBlock = nil;
// [self.benchMarkMan markFinishWithKey:kRNBenchmarkLogicOnly];
}
- (void)notifyError:(NSError *)error {
if (![NSThread isMainThread]) {
[self __notifyError:error];
} else {
[self performSelectorInBackground:@selector(__notifyError:) withObject:error];
}
}
- (void)onError:(NSError *)error {
@synchronized(self) {
if (_finished) {
return;
}
_finished = YES;
}
[self.benchMarkMan markFinishWithKey:kTaskTookTime];
[self notifyError:error];
if (self.requestRetry) {
_finished = NO;
}else{
_finished = YES;
}
QCloudLogErrorE(@"",@"[%@][%lld]当前网络环境为%d 请求失败%@", [self class], self.requestID, QCloudNetworkShareEnv.currentNetStatus, error);
QCloudLogInfoN(@"",@"[%@][%lld]性能监控 %@", [self class], self.requestID, [self.benchMarkMan readablityDescription]);
}
- (void)__notifySuccess:(id)object {
[self.condition broadcast];
if ([self.delegate respondsToSelector:@selector(QCloudHTTPRequestDidFinished:succeedWithObject:)]) {
[self.delegate QCloudHTTPRequestDidFinished:self succeedWithObject:object];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (self.finishBlock) {
self.finishBlock(object, nil);
}
self.finishBlock = nil;
});
}
- (void)notifySuccess:(id)object {
if (![NSThread isMainThread]) {
[self __notifySuccess:object];
} else {
[self performSelectorInBackground:@selector(__notifySuccess:) withObject:object];
}
}
- (void)onSuccess:(id)object {
@synchronized(self) {
if (_finished) {
return;
}
_finished = YES;
}
[self.benchMarkMan markFinishWithKey:kTaskTookTime];
[self notifySuccess:object];
if (self.requestRetry) {
_finished = NO;
}else{
_finished = YES;
}
QCloudLogInfoN(@"",@"[%@][%lld]性能监控\n%@", [self class], self.requestID, [self.benchMarkMan readablityDescription]);
}
- (void)cancel {
@synchronized(self) {
_canceled = YES;
}
NSError *cancelError = [NSError qcloud_errorWithCode:QCloudNetworkErrorCodeCanceled message:@"UserCancelled:The request is canceled"];
[self onError:cancelError];
}
- (void)notifyDownloadProgressBytesDownload:(int64_t)bytesDownload
totalBytesDownload:(int64_t)totalBytesDownload
totalBytesExpectedToDownload:(int64_t)totalBytesExpectedToDownload {
if (self.canceled) {
return;
}
if (self.downProcessBlock) {
self.downProcessBlock(bytesDownload, totalBytesDownload, totalBytesExpectedToDownload);
}
if ([self.delegate respondsToSelector:@selector(QCloudHTTPRequest:bytesDownload:totalBytesDownload:totalBytesExpectedToDownload:)]) {
[self.delegate QCloudHTTPRequest:self
bytesDownload:bytesDownload
totalBytesDownload:totalBytesDownload
totalBytesExpectedToDownload:totalBytesExpectedToDownload];
}
}
- (void)notifyDownloadProgressBytesDownload:(int64_t)bytesDownload
totalBytesDownload:(int64_t)totalBytesDownload
totalBytesExpectedToDownload:(int64_t)totalBytesExpectedToDownload
receivedData:(NSData *)data {
[self notifyDownloadProgressBytesDownload:bytesDownload
totalBytesDownload:totalBytesDownload
totalBytesExpectedToDownload:totalBytesExpectedToDownload];
if (self.downProcessWithDataBlock) {
self.downProcessWithDataBlock(bytesDownload, totalBytesDownload, totalBytesExpectedToDownload, data);
}
if ([self.delegate respondsToSelector:@selector(QCloudHTTPRequest:bytesDownload:totalBytesDownload:totalBytesExpectedToDownload:receiveData:)]) {
[self.delegate QCloudHTTPRequest:self
bytesDownload:bytesDownload
totalBytesDownload:totalBytesDownload
totalBytesExpectedToDownload:totalBytesExpectedToDownload
receiveData:data];
}
}
- (void)notifySendProgressBytesSend:(int64_t)bytesSend
totalBytesSend:(int64_t)totalBytesSend
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
if (self.canceled) {
return;
}
if (self.sendProcessBlock) {
self.sendProcessBlock(bytesSend, totalBytesSend, totalBytesExpectedToSend);
}
if ([self.delegate respondsToSelector:@selector(QCloudHTTPRequest:sendBytes:totalBytesSent:totalBytesExpectedToSend:)]) {
[self.delegate QCloudHTTPRequest:self sendBytes:bytesSend totalBytesSent:totalBytesSend totalBytesExpectedToSend:totalBytesExpectedToSend];
}
}
- (void)waitForComplete {
if ([NSThread isMainThread]) {
cosWarnBlockingOperationOnMainThread();
}
@synchronized(self.lock) {
if (self.finished) {
return;
}
[self.condition lock];
}
while (!self.finished) {
[self.condition wait];
}
[self.condition unlock];
}
- (void)setCredential:(QCloudCredential *)credential{
NSMutableDictionary * _payload = self.payload.mutableCopy;
if (!_payload) {
_payload = [NSMutableDictionary new];
}
if (credential) {
[_payload setObject:credential forKey:@"QCloudCredential"];
}
self.payload = _payload.copy;
}
- (QCloudCredential *)credential{
return [self.payload objectForKey:@"QCloudCredential"];
}
- (void)setShouldSignedList:(NSArray *)shouldSignedList{
NSMutableDictionary * _payload = self.payload.mutableCopy;
if (!_payload) {
_payload = [NSMutableDictionary new];
}
if (shouldSignedList) {
[_payload setObject:shouldSignedList forKey:@"shouldSignedList"];
}
self.payload = _payload.copy;
}
- (NSArray *)shouldSignedList{
return [self.payload objectForKey:@"shouldSignedList"];
}
- (void)setSignature:(QCloudSignature *)signature{
NSMutableDictionary * _payload = self.payload.mutableCopy;
if (!_payload) {
_payload = [NSMutableDictionary new];
}
if (signature) {
[_payload setObject:signature forKey:@"signature"];
}
self.payload = _payload.copy;
}
- (QCloudSignature *)signature{
return [self.payload objectForKey:@"signature"];
}
- (void)configTaskResume {
}
@end

View File

@@ -0,0 +1,148 @@
//
// QCloudHTTPRequest.h
// QCloudNetworking
//
// Created by tencent on 15/9/25.
// Copyright © 2015年 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QCloudRequestData.h"
#import "QCloudRequestSerializer.h"
#import "QCloudResponseSerializer.h"
#import "QCloudHTTPRequestDelegate.h"
#import "QCloudAbstractRequest.h"
@class QCloudHTTPRetryHanlder;
@class QCloudHTTPSessionManager;
@class QCloudService;
typedef void (^QCloudHTTPRequestConfigure)(QCloudRequestSerializer *_Nonnull requestSerializer,
QCloudResponseSerializer *_Nonnull responseSerializer);
/**
network base request
*/
@interface QCloudHTTPRequest : QCloudAbstractRequest {
@protected
QCloudRequestData *_requestData;
QCloudRequestSerializer *_requestSerializer;
QCloudResponseSerializer *_responseSerializer;
QCloudHTTPRetryHanlder *_retryHandler;
NSString *_serverDomain;
}
@property (nonatomic, strong, readonly) QCloudRequestSerializer *_Nonnull requestSerializer;
@property (nonatomic, strong, readonly) QCloudRequestData *_Nonnull requestData;
@property (nonatomic, strong, readonly) QCloudResponseSerializer *_Nonnull responseSerializer;
@property (nonatomic, strong, readonly) NSURLRequest *_Nullable urlRequest;
/// sdk内部管理业务测无需设置。
@property (nonatomic, assign, readonly) BOOL isRetry;
@property (nonatomic, assign) NSInteger retryCount;
/**
该任务所处的服务
*/
@property (nonatomic, weak) QCloudService *_Nullable runOnService;
/**
如果存在改参数,则数据会下载到改路径指名的地址下面,而不会写入内存中。
*/
@property (nonatomic, strong) NSURL *_Nonnull downloadingURL;
@property (nonatomic, strong , readonly) NSURL *_Nonnull downloadingTempURL;
/**
本地已经下载的数据偏移量如果使用则会从改位置开始下载如果不使用则从头开始下载如果您使用了Range参数则需要注意改参数。
*/
@property (nonatomic, assign) int64_t localCacheDownloadOffset;
/**
在特殊网络错误下进行重试的策略默认是不进行重试。可通过集成QCloudHTTPRetryHandler来自定义重试的出发条件和重试策略。
*/
@property (nonatomic, strong, readonly) QCloudHTTPRetryHanlder *_Nullable retryPolicy;
/**
服务器返回数据,当服务器有返回数据的时候,该字段有值,其他时候该字段无意义
*/
@property (nonatomic, strong, readonly) NSData *_Nullable responseData;
/**
服务器响应结构,当服务器有返回数据的时候,该字段有值,其他时候该字段无意义
*/
@property (nonatomic, strong, readonly) NSHTTPURLResponse *_Nullable httpURLResponse;
/**
当系统调用结束,并且出错的情况下,使用该字段表示错误信息,注意:只有在错误的情况下,该字段才会有数据
*/
@property (nonatomic, strong, readonly) NSError *_Nullable httpURLError;
/**
用来配置协议中HTTP的请求参数和解析
*/
@property (nonatomic, strong) QCloudHTTPRequestConfigure _Nonnull configureBlock;
/**
当前的ConfiureBlock为空的时候会调用该函数加载配置函数。
*/
- (void)loadConfigureBlock;
- (void)setConfigureBlock:(void (^_Nonnull)(QCloudRequestSerializer *_Nonnull requestSerializer,
QCloudResponseSerializer *_Nonnull responseSerializer))configBlock;
/**
构架RequestData加载自定义的参数
*/
- (BOOL)buildRequestData:(NSError *_Nullable __autoreleasing *_Nullable)error;
/**
构建真实网络请求需要的NSURLRequest
@param error 当出错的时候,表示出错信息
@return 用于构建真实网络请求的NSURLRequest
*/
- (NSURLRequest *_Nullable)buildURLRequest:(NSError *_Nullable __autoreleasing *_Nullable)error;
@end
#define SUPER_BUILD_REUQSET_DATA \
if (![super buildRequestData:error]) \
return NO;
@interface QCloudHTTPRequest (SubClass)
/**
将要开始发送请求的时候,将会调用该接口通知子类
*/
- (void)willStart;
/**
加载错误重试策略只有在第一次调用retryPolicy并且其值为空的时候会调用该方法来加载重试策略。子类可以重载该方法返回自己的重试策略。父类的默认行为是返回一个不进行重试的策略。
*/
- (void)loadRetryPolicy;
- (void)setConfigureBlock:(void (^_Nonnull)(QCloudRequestSerializer *_Nonnull requestSerializer,
QCloudResponseSerializer *_Nonnull responseSerializer))confBlock;
- (BOOL)prepareInvokeURLRequest:(NSMutableURLRequest *_Nonnull)urlRequest error:(NSError *_Nullable __autoreleasing *_Nullable)error;
@end
@class RNAsyncBenchMark;
#pragma deal with response
@interface QCloudHTTPRequest ()
/**
服务器返回response的时候处理可以在这里做处理看是否接受后序的数据
@param response 服务器返回的response主要包含头部
@return NSURLSessionResponseDisposition
*/
- (NSURLSessionResponseDisposition)reciveResponse:(NSURLResponse *_Nullable)response;
- (void)onReviveErrorResponse:(NSURLResponse *_Nullable)prsponse error:(NSError *_Nullable)error;
- (void)onReciveRespone:(NSURLResponse *_Nullable)response data:(NSData *_Nullable)data;
-(BOOL)needChangeHost;
+(BOOL)needChangeHost:(NSString *_Nullable)host;
@end

View File

@@ -0,0 +1,271 @@
//
// QCloudHTTPRequest.m
// QCloudNetworking
//
// Created by tencent on 15/9/25.
// Copyright © 2015 QCloudTernimalLab. All rights reserved.
//
#import "QCloudHTTPRequest.h"
#import "QCloudRequestData.h"
#import "QCloudRequestSerializer.h"
#import "QCloudHTTPRetryHanlder.h"
#import "QCloudNetEnv.h"
#import "QCloudHttpDNS.h"
#import "QCloudIntelligenceTimeOutAdapter.h"
#import "QCloudHTTPRequest_RequestID.h"
#import "QCloudHttpMetrics.h"
#import "QCloudLogger.h"
#import "QCloudObjectModel.h"
#import "QCloudSupervisory.h"
#import "QCloudHTTPSessionManager.h"
#import "NSError+QCloudNetworking.h"
#import "QCLOUDRestNet.h"
#import "QCloudService.h"
#import "NSDate+QCLOUD.h"
#import "NSDate+QCloudInternetDateTime.h"
#import "NSObject+HTTPHeadersContainer.h"
@interface QCloudHTTPRequest () {
BOOL _requesting;
}
@property (atomic, assign) BOOL isCancel;
@property (nonatomic, strong, readonly) NSMutableURLRequest *cachedURLRequest;
@property (nonatomic, strong, readonly) NSError *cachedURLRequestBuildError;
@property (nonatomic, strong) NSURLRequest *_Nullable urlRequest;
@end
@implementation QCloudHTTPRequest
@synthesize httpURLResponse = _httpURLResponse;
@synthesize httpURLError = _httpURLError;
- (void)__baseCommonInit {
_requestData = [QCloudRequestData new];
_requestSerializer = [QCloudRequestSerializer new];
_responseSerializer = [QCloudResponseSerializer new];
_requesting = NO;
_requestSerializer.timeoutInterval = [QCloudIntelligenceTimeOutAdapter recommendTimeOut];
// if request is download request ,timeoutInterval = 30
if (self.downloadingURL) {
_requestSerializer.timeoutInterval = 30;
}
_isCancel = NO;
}
- (NSHTTPURLResponse *)httpURLResponse {
return _httpURLResponse;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
[self __baseCommonInit];
return self;
}
- (void)notifyError:(NSError *)error {
[super notifyError:error];
[[QCloudSupervisory supervisory] recordRequest:self error:error];
}
- (void)notifySuccess:(id)object {
[super notifySuccess:object];
[[QCloudSupervisory supervisory] recordRequest:self error:nil];
}
- (void)loadConfigureBlock {
[self setConfigureBlock:^(QCloudRequestSerializer *requestSerializer, QCloudResponseSerializer *responseSerializer) {
requestSerializer.HTTPMethod = HTTPMethodGET;
[requestSerializer setSerializerBlocks:@[ QCloudURLFuseSimple ]];
//
[responseSerializer setSerializerBlocks:@[ QCloudAcceptRespnseCodeBlock([NSSet setWithObject:@(200)], nil) ]];
}];
}
- (QCloudHTTPRequestConfigure)configureBlock {
if (!_configureBlock) {
[self loadConfigureBlock];
}
return _configureBlock;
}
- (void)willStart {
QCloudLogDebugP(@"HTTP",@"[%llu] Will Start", self.requestID);
}
- (void)loadRetryPolicy {
_retryHandler = [QCloudHTTPRetryHanlder defaultRetryHandler];
}
- (QCloudHTTPRetryHanlder *)retryPolicy {
if (!_retryHandler) {
[self loadRetryPolicy];
}
return _retryHandler;
}
- (BOOL)buildRequestData:(NSError *__autoreleasing *)error {
__block QCloudRequestSerializer *reqSerializer = self.requestSerializer;
__block QCloudResponseSerializer *rspSerializer = self.responseSerializer;
if (self.configureBlock) {
self.configureBlock(reqSerializer, rspSerializer);
}
return YES;
}
- (void)clearBuildCache {
_cachedURLRequest = nil;
_cachedURLRequestBuildError = nil;
}
- (NSURL *)downloadingTempURL{
if(_downloadingURL){
return [NSURL URLWithString:[NSString stringWithFormat:@"%@.downloading",_downloadingURL.absoluteString]];
}else{
return nil;
}
}
- (NSURLRequest *)buildURLRequest:(NSError *__autoreleasing *)error {
if (![self buildRequestData:error]) {
return nil;
}
if (self.isRetry) {
[self.requestData setValue:@"true" forHTTPHeaderField:@"x-cos-sdk-retry"];
}
[self.benchMarkMan benginWithKey:kCalculateMD5STookTime];
NSURLRequest *request = [self.requestSerializer requestWithData:self.requestData error:error];
if ([request.allHTTPHeaderFields objectForKey:@"Content-MD5"]) {
[self.benchMarkMan markFinishWithKey:kCalculateMD5STookTime];
}
if (*error) {
QCloudLogErrorE(@"",@"[%@][%lld]序列化失败", self.class, self.requestID);
return nil;
}
QCloudLogDebugP(@"HTTP",@"SendingRequest [%lld]\n%@\n%@ \nrequest content:%@", self.requestID, request, request.allHTTPHeaderFields,
[[NSString alloc] initWithData:request.HTTPBody encoding:NSUTF8StringEncoding]);
self.urlRequest = request;
return request;
}
- (void)onReviveErrorResponse:(NSURLResponse *)response error:(NSError *)error {
_httpURLResponse = (NSHTTPURLResponse *)response;
_httpURLError = error;
if (NSURLErrorCancelled == error.code && [NSURLErrorDomain isEqualToString:error.domain]) {
error = [NSError qcloud_errorWithCode:QCloudNetworkErrorCodeCanceled message:@"UserCancelled:The request is canceled" infos:@{kQCloudErrorDetailCode:@(NSURLErrorCancelled)}];
}
_httpURLError.__originHTTPURLResponse__ = _httpURLResponse;
error.__originHTTPURLResponse__ = _httpURLResponse;
[self onError:error];
}
- (void)onReciveRespone:(NSHTTPURLResponse *)response data:(NSData *)data {
_responseData = data;
_httpURLResponse = response;
// //
// {
// NSUInteger headerLength = 0;
// NSDictionary *allHeaders = nil;
// if ([response respondsToSelector:@selector(allHeaderFields)]) {
// allHeaders = [response allHeaderFields];
// }
// if (allHeaders) {
// if (response.allHeaderFields) {
// NSData *headerData = [NSJSONSerialization dataWithJSONObject:allHeaders options:0 error:0];
// headerLength = headerData.length;
// }
// }
// }
NSString *dateStr = [[response allHeaderFields] objectForKey:@"Date"];
NSDate *serverTime = nil;
NSDate *deviceTime = [NSDate date];
if ([dateStr length] > 0) {
serverTime = [NSDate qcloud_dateFromRFC822String:dateStr];
} else {
// The response header does not have the 'Date' field.
// This should not happen.
QCloudLogErrorE(@"",@"Date header does not exist. Not able to fix the time");
}
NSTimeInterval skewTime = 0;
if (serverTime) {
skewTime = [deviceTime timeIntervalSinceDate:serverTime];
}
// If the time difference between the device and the server is large, fix device time
QCloudLogDebugP(@"HTTP",@"skewTime: %f", skewTime);
if (skewTime >= 1 * 60) {
[NSDate qcloud_setTimeDeviation:skewTime];
}
NSError *localError;
id outputObject = [self.responseSerializer decodeWithWithResponse:response data:data error:&localError];
if (localError) {
localError.__originHTTPURLResponse__ = response;
localError.__originHTTPResponseData__ = data;
QCloudLogErrorE(@"HTTP",@"[%@][%lld] %@", [self class], self.requestID, localError);
if ([self isFixTime:localError]) {
[NSDate qcloud_setTimeDeviation:skewTime];
}
[self onError:localError];
} else {
QCloudLogDebugP(@"HTTP",@"[%@][%lld] RESPONSE \n%@ ", [self class], self.requestID, [outputObject qcloud_modelToJSONString]);
[outputObject set__originHTTPURLResponse__:response];
[outputObject set__originHTTPResponseData__:data];
[self onSuccess:outputObject];
}
}
// Error code to be fix
- (BOOL)isFixTime:(NSError *)error {
if ([error.userInfo[@"Code"] isEqualToString:@"RequestTimeTooSkewed"]
|| ([error.userInfo[@"Code"] isEqualToString:@"AccessDenied"] || [error.userInfo[@"Message"] isEqualToString:@"Request has expired"])) {
return YES;
}
return NO;
}
- (BOOL)prepareInvokeURLRequest:(NSMutableURLRequest *)urlRequest error:(NSError *__autoreleasing *)error {
return YES;
}
- (void)cancel {
[super cancel];
[[QCloudHTTPSessionManager shareClient] cancelRequestWithID:(int)self.requestID];
}
- (NSURLSessionResponseDisposition)reciveResponse:(NSURLResponse *)response {
return NSURLSessionResponseAllow;
}
-(BOOL)needChangeHost{
NSString * host = self.urlRequest.URL.host;
return [QCloudHTTPRequest needChangeHost:host];
}
+(BOOL)needChangeHost:(NSString *)host{
if(!host){
return NO;
}
if([host rangeOfString:@".cos.accelerate.myqcloud.com"].length > 0){
return NO;
}
if([host rangeOfString:@"service.cos.myqcloud.com"].length > 0){
return NO;
}
if([host rangeOfString:@".myqcloud.com"].length > 0 && [host rangeOfString:@"cos."].length > 0 && [host rangeOfString:@".cos."].length == 0){
return NO;
}
if([host rangeOfString:@".myqcloud.com"].length > 0 && [host rangeOfString:@".cos."].length > 0){
return YES;
}
return NO;
}
- (void)setEndpoint:(QCloudEndPoint *)endpoint{
super.endpoint = endpoint;
self.requestData.endpoint = endpoint;
}
@end

View File

@@ -0,0 +1,32 @@
//
// QCloudHTTPRequestDelegate.h
// QCloudNetworking
//
// Created by tencent on 15/9/30.
// Copyright © 2015年 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef void (^QCloudRequestFinishBlock)(id outputObject, NSError *error);
@class QCloudAbstractRequest;
@protocol QCloudHTTPRequestDelegate <NSObject>
@optional
- (void)QCloudHTTPRequestDidFinished:(QCloudAbstractRequest *)request succeedWithObject:(id)object;
- (void)QCloudHTTPRequestDidFinished:(QCloudAbstractRequest *)request failed:(NSError *)object;
- (void)QCloudHTTPRequest:(QCloudAbstractRequest *)request
sendBytes:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;
- (void)QCloudHTTPRequest:(QCloudAbstractRequest *)request
bytesDownload:(int64_t)bytesDownload
totalBytesDownload:(int64_t)totalBytesDownload
totalBytesExpectedToDownload:(int64_t)totalBytesExpectedToDownload;
- (void)QCloudHTTPRequest:(QCloudAbstractRequest *)request
bytesDownload:(int64_t)bytesDownload
totalBytesDownload:(int64_t)totalBytesDownload
totalBytesExpectedToDownload:(int64_t)totalBytesExpectedToDownload
receiveData:(NSData *)data;
@end

View File

@@ -0,0 +1,12 @@
//
// QCloudHTTPRequest_RequestID.h
// QCloudTernimalLab_CommonLogic
//
// Created by tencent on 5/23/16.
// Copyright © 2016 QCloudTernimalLab. All rights reserved.
//
#import "QCloudHTTPRequest.h"
@interface QCloudHTTPRequest ()
@end

View File

@@ -0,0 +1,190 @@
//
// QCloudRequestData.h
// QCloudNetworking
//
// Created by tencent on 15/9/24.
// Copyright © 2015年 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QCloudHTTPMultiDataStream.h"
#import "QCloudEndPoint.h"
NS_ASSUME_NONNULL_BEGIN
extern NSString *const HTTPHeaderUserAgent;
extern NSString *const emergencyHost;
/**
网络请求参数的容器类
*/
@interface QCloudRequestData<BodyType> : NSObject
@property (nonatomic, strong, readonly) NSDictionary *queryParamters;
/**
指定接口级请求域名
*/
@property (nonatomic, strong, nullable) QCloudEndPoint * endpoint;
/**
数据的编码格式
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
服务器地址
*/
@property (strong, nonatomic ,nullable) NSString *serverURL;
@property (strong, nonatomic ,nullable) NSString *bucket;
@property (strong, nonatomic ,nullable) NSString *appId;
@property (strong, nonatomic ,nullable) NSString *region;
/// 重试时是否需要更换域名
@property (assign, nonatomic) BOOL needChangeHost;
/**
* 统一资源标识符,用来标识调用的具体的资源地址
*/
@property (nonatomic, strong,nullable) NSString *URIMethod;
/**
如果URIMethod不足以表示所有的命令字的时候可以在改字段中按照顺序添加URI分片将会按照顺序组装起来。
*/
@property (nonatomic, strong,nullable) NSArray *URIComponents;
/**
HTTP headers参数用来配置Request
*/
@property (nonatomic, strong, nonnull, readonly) NSDictionary *httpHeaders;
/**
所有的参数填充在这里面的参数为不需要添加在HTTPHeaders里面的参数
*/
@property (nonnull, strong, nonatomic, readonly) NSDictionary *allParamters;
/**
使用multipart/form-data上传数据时所需要的数据流默认为空只有当添加了form data后该字段才有值
*/
@property (nonatomic, strong, readonly, nullable) QCloudHTTPMultiDataStream *multiDataStream;
@property (nullable, strong) NSString *boundary;
/**
请求中要使用到的Cookies
*/
@property (nonatomic, strong, nullable, readonly) NSArray *cookies;
@property (nullable, strong) BodyType directBody;
/**
清除所有参数
*/
- (void)clean;
/**
添加类型为NSString的参数
@param paramter 添加的参数
@param key 关键字
*/
- (void)setParameter:(nonnull id)paramter withKey:(nonnull NSString *)key;
/**
添加类型为NSNumber的请求参数
@param paramter 添加的参数
@param key 参数对应的关键字
*/
- (void)setNumberParamter:(nonnull NSNumber *)paramter withKey:(nonnull NSString *)key;
- (void)setQueryStringParamter:(nonnull NSString *)paramter withKey:(nonnull NSString *)key;
/**
通过指定的Key获取
@param key 参数的Key
@return 获取到的参数值
*/
- (id)paramterForKey:(NSString *)key;
/**
添加HTTP header中的信息
其中User-Agent等有默认信息
@param value 要添加的信息
@param field 对应的关键字
*/
- (void)setValue:(nonnull id)value forHTTPHeaderField:(nonnull NSString *)field;
- (NSString *)valueForHttpKey:(NSString *)key;
/**
通过URL Paramater字符串的方式来添加参数
@param paramters 参数字符串按照xx=xx&xx=xx的形式
*/
- (void)setParamatersWithString:(nonnull NSString *)paramters;
/**
通过字典的形式添加参数
@param paramters 参数字典容器
*/
- (void)setParametersInDictionary:(nonnull NSDictionary *)paramters;
/**
删除指定Key的Header
@param key 要删除的Header
*/
- (void)removeHTTPHeaderForKey:(NSString *)key;
/**
手动添加Cookie
@param domain 域
@param path 路径
@param name 名称
@param value 值
*/
- (void)addCookieWithDomain:(nonnull NSString *)domain path:(nonnull NSString *)path name:(nonnull NSString *)name value:(nonnull id)value;
/**
通过KeyValue形式添加FormData的参数
@param key key
@param value value
@return 是否添加成功
*/
- (BOOL)appendFormDataKey:(NSString *)key value:(NSString *)value;
/**
添加文件内容部分
@param fileURL URL
@param name 文件名
@param fileName 文件名
@param mimeType mimeType
@param paramerts 头部参数
@param error error
@return 成功与否
*/
- (BOOL)appendPartWithFileURL:(nonnull NSURL *)fileURL
name:(nonnull NSString *)name
fileName:(nonnull NSString *)fileName
mimeType:(nonnull NSString *)mimeType
headerParamters:(nullable NSDictionary *)paramerts
error:(NSError *_Nullable __autoreleasing *)error;
/**
添加分片文件内容部分
@param fileURL url
@param name name
@param fileName fileName
@param offset offset
@param sliceLength sliceLength
@param mimeType mimeType
@param paramerts parameters
@param error error
@return 成功与否
*/
- (BOOL)appendPartWithFileURL:(nonnull NSURL *)fileURL
name:(nonnull NSString *)name
fileName:(nonnull NSString *)fileName
offset:(int64_t)offset
sliceLength:(int)sliceLength
mimeType:(nonnull NSString *)mimeType
headerParamters:(nullable NSDictionary *)paramerts
error:(NSError *_Nullable __autoreleasing *)error;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,390 @@
//
// QCloudRequestData.m
// QCloudNetworking
//
// Created by tencent on 15/9/24.
// Copyright © 2015 QCloudTernimalLab. All rights reserved.
//
#import "QCloudRequestData.h"
#import "NSError+QCloudNetworking.h"
#import "QCloudHTTPBodyPart.h"
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#endif
#define String_ENSURE_NOT_NIL_PARAMTER(p) \
if (p == nil) \
return nil;
#define ENSURE_NOT_NIL_PARAMTER(p) \
if (p == nil) \
return;
#define B_ENSURE_NOT_NIL_PARAMTER(p) \
if (p == nil) \
return NO;
NSDictionary *QCloudURLDecodePatamters(NSString *str) {
NSRange rangeOfQ = [str rangeOfString:@"?"];
NSString *subStr = str;
if (rangeOfQ.location != NSNotFound) {
subStr = [str substringFromIndex:rangeOfQ.location + rangeOfQ.length];
}
NSArray *coms = [subStr componentsSeparatedByString:@"&"];
if (coms.count == 0) {
return nil;
}
NSMutableDictionary *paramters = [NSMutableDictionary new];
for (NSString *s in coms) {
NSArray *kv = [s componentsSeparatedByString:@"="];
if (kv.count != 2) {
continue;
}
NSString *key = kv[0];
NSString *value = kv[1];
paramters[key] = value;
}
return paramters;
}
NSString *const HTTPHeaderUserAgent = @"User-Agent";
NSString *const emergencyHost = @"tencentcos.cn";
@interface QCloudRequestData () {
NSMutableDictionary *_paramters;
NSMutableDictionary *_httpHeaders;
NSMutableDictionary *_queryParamters;
}
@end
@implementation QCloudRequestData
@synthesize multiDataStream = _multiDataStream;
- (void)loadDefaultHTTPHeaders {
static NSDictionary *httpHeaders;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if TARGET_OS_IOS
NSString *userAgent =
[NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)",
[[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey]
?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey],
[[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]
?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey],
[[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_MAC
NSString* userAgent = @"Test-Mac-Agent";
#endif
httpHeaders = @ { @"Connection" : @"keep-alive", HTTPHeaderUserAgent : userAgent };
});
_httpHeaders = [httpHeaders mutableCopy];
}
- (void)__dataCommonInit {
_paramters = [NSMutableDictionary new];
_cookies = [NSMutableArray new];
_stringEncoding = NSUTF8StringEncoding;
_queryParamters = [NSMutableDictionary new];
[self loadDefaultHTTPHeaders];
}
- (id)init {
self = [super init];
if (!self) {
return self;
}
[self __dataCommonInit];
return self;
}
- (NSDictionary *)queryParamters {
return [_queryParamters copy];
}
- (NSString *)URIMethod {
if (!_URIMethod) {
return @"";
} else {
return _URIMethod;
}
}
- (NSDictionary *)allParamters {
return [_paramters copy];
}
- (void)setParameter:(nonnull id)paramter withKey:(nonnull NSString *)key {
#ifdef DEBUG
NSParameterAssert(key);
#else
ENSURE_NOT_NIL_PARAMTER(paramter);
#endif
if (!paramter || [paramter isKindOfClass:[NSNull class]]) {
return;
}
_paramters[key] = paramter;
}
- (void)setNumberParamter:(nonnull NSNumber *)paramter withKey:(nonnull NSString *)key {
[self setParameter:[paramter stringValue] withKey:key];
}
- (void)setQueryStringParamter:(nonnull NSString *)paramter withKey:(nonnull NSString *)key {
NSParameterAssert(key);
if (!paramter || [paramter isKindOfClass:[NSNull class]]) {
paramter = @"";
}
if (![paramter isKindOfClass:[NSString class]]) {
paramter = [NSString stringWithFormat:@"%@", paramter];
}
_queryParamters[key] = paramter;
}
- (void)setValue:(nonnull id)value forHTTPHeaderField:(nonnull NSString *)field {
#ifdef DEBUG
NSParameterAssert(field);
#else
ENSURE_NOT_NIL_PARAMTER(field);
#endif
[_httpHeaders setValue:value forKey:field];
}
- (NSString *)valueForHttpKey:(NSString *)key {
#ifdef DEBUG
NSParameterAssert(key);
#else
String_ENSURE_NOT_NIL_PARAMTER(key);
#endif
return [_httpHeaders valueForKey:key];
}
- (void)addCookieWithDomain:(NSString *)domain path:(NSString *)path name:(NSString *)name value:(id)value {
#ifdef DEBUG
NSParameterAssert(domain);
NSParameterAssert(path);
NSParameterAssert(name);
NSParameterAssert(value);
#else
ENSURE_NOT_NIL_PARAMTER(domain);
ENSURE_NOT_NIL_PARAMTER(path);
ENSURE_NOT_NIL_PARAMTER(name);
ENSURE_NOT_NIL_PARAMTER(value);
#endif
NSDictionary *info = @{ NSHTTPCookieValue : value, NSHTTPCookieName : name, NSHTTPCookieDomain : domain, NSHTTPCookiePath : path };
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:info];
NSMutableArray *cookies = [self.cookies mutableCopy];
NSInteger index = NSNotFound;
for (int i = 0; i < cookies.count; i++) {
NSHTTPCookie *c = cookies[i];
if ([c.domain isEqualToString:cookie.domain] && [c.path isEqualToString:cookie.path] && [c.name isEqualToString:cookie.name]) {
index = i;
}
}
if (index != NSNotFound) {
[cookies replaceObjectAtIndex:index withObject:cookie];
} else {
[cookies addObject:cookie];
}
_cookies = cookies;
}
- (void)setParamatersWithString:(NSString *)paramters {
NSDictionary *dic = QCloudURLDecodePatamters(paramters);
NSAssert(dic, @"paramters 字符串解析出现问题,没有成功解析出字典");
if (dic) {
for (NSString *key in dic.allKeys) {
[self setParameter:dic[key] withKey:key];
}
}
}
- (void)setParametersInDictionary:(NSDictionary *)paramters {
if (paramters) {
for (NSString *key in paramters.allKeys) {
[self setParameter:paramters[key] withKey:key];
}
}
}
- (QCloudHTTPMultiDataStream *)multiDataStream {
if (!_multiDataStream) {
_multiDataStream = [[QCloudHTTPMultiDataStream alloc] initWithStringEncoding:_stringEncoding];
_multiDataStream.stringEncoding = _stringEncoding;
}
return _multiDataStream;
}
- (BOOL)appendFormDataKey:(NSString *)key value:(NSString *)value {
#ifdef DEBUG
NSParameterAssert(key);
NSParameterAssert(value);
#else
B_ENSURE_NOT_NIL_PARAMTER(key);
B_ENSURE_NOT_NIL_PARAMTER(value);
#endif
if (![value isKindOfClass:[NSString class]]) {
value = [NSString stringWithFormat:@"%@", value];
}
QCloudHTTPBodyPart *part = [[QCloudHTTPBodyPart alloc] initWithData:[value dataUsingEncoding:NSUTF8StringEncoding]];
[part setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", key] forHeaderKey:@"Content-Disposition"];
[[self multiDataStream] appendBodyPart:part];
return YES;
}
- (BOOL)appendPartWithFileURL:(nonnull NSURL *)fileURL
name:(nonnull NSString *)name
fileName:(nonnull NSString *)fileName
mimeType:(nonnull NSString *)mimeType
headerParamters:(nullable NSDictionary *)paramerts
error:(NSError *_Nullable __autoreleasing *)error;
{
#ifdef DEBUG
NSParameterAssert(fileURL);
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
#else
B_ENSURE_NOT_NIL_PARAMTER(fileURL);
B_ENSURE_NOT_NIL_PARAMTER(name);
B_ENSURE_NOT_NIL_PARAMTER(fileName);
B_ENSURE_NOT_NIL_PARAMTER(mimeType);
#endif
if (![fileURL isFileURL]) {
if (error) {
*error = [NSError qcloud_errorWithCode:NSURLErrorBadURL
message:NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"QCloudNetworking", nil)];
}
return NO;
} else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
if (error) {
*error = [NSError qcloud_errorWithCode:NSURLErrorBadURL message:[NSString stringWithFormat:@"File URL not reachable. %@", fileURL]];
}
return NO;
}
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
if (!fileAttributes) {
return NO;
}
QCloudHTTPBodyPart *part = [[QCloudHTTPBodyPart alloc] initWithURL:fileURL withContentLength:[fileAttributes[NSFileSize] unsignedLongLongValue]];
[part setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forHeaderKey:@"Content-Disposition"];
[part setHeaderValueWithMap:paramerts];
[part setValue:mimeType forHeaderKey:@"Content-Type"];
[[self multiDataStream] appendBodyPart:part];
return YES;
}
- (BOOL)appendPartWithFileURL:(nonnull NSURL *)fileURL
name:(nonnull NSString *)name
fileName:(nonnull NSString *)fileName
offset:(int64_t)offset
sliceLength:(int)sliceLength
mimeType:(nonnull NSString *)mimeType
headerParamters:(nullable NSDictionary *)paramerts
error:(NSError *_Nullable __autoreleasing *)error {
#ifdef DEBUG
NSParameterAssert(fileURL);
NSParameterAssert(name);
NSParameterAssert(fileName);
NSParameterAssert(mimeType);
#else
B_ENSURE_NOT_NIL_PARAMTER(fileURL);
B_ENSURE_NOT_NIL_PARAMTER(name);
B_ENSURE_NOT_NIL_PARAMTER(fileName);
B_ENSURE_NOT_NIL_PARAMTER(mimeType);
#endif
if (![fileURL isFileURL]) {
if (error) {
*error = [NSError qcloud_errorWithCode:NSURLErrorBadURL
message:NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"QCloudNetworking", nil)];
}
return NO;
} else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
if (error) {
*error = [NSError qcloud_errorWithCode:NSURLErrorBadURL message:[NSString stringWithFormat:@"File URL not reachable. %@", fileURL]];
}
return NO;
}
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
if (!fileAttributes) {
return NO;
}
QCloudHTTPBodyPart *part = [[QCloudHTTPBodyPart alloc] initWithURL:fileURL offetSet:offset withContentLength:sliceLength];
[part setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forHeaderKey:@"Content-Disposition"];
[part setHeaderValueWithMap:paramerts];
[part setValue:mimeType forHeaderKey:@"Content-Type"];
[[self multiDataStream] appendBodyPart:part];
return YES;
}
- (void)setStringEncoding:(NSStringEncoding)stringEncoding {
_stringEncoding = stringEncoding;
_multiDataStream.stringEncoding = stringEncoding;
}
- (id)paramterForKey:(NSString *)key {
return [_paramters objectForKey:key];
}
- (void)removeHTTPHeaderForKey:(NSString *)key {
if (!key) {
return;
}
[_httpHeaders removeObjectForKey:key];
}
- (void)clean {
_queryParamters = nil;
self.stringEncoding = NSUTF8StringEncoding;
_serverURL = nil;
_URIMethod = nil;
_URIComponents = nil;
_httpHeaders = nil;
_paramters = nil;
_multiDataStream = nil;
_boundary = nil;
_cookies = nil;
_directBody = nil;
[self __dataCommonInit];
}
- (NSString *)description {
NSMutableString *str = [NSMutableString new];
if (self.httpHeaders.count) {
[str appendFormat:@"[HEADERS] \n%@\n", self.httpHeaders];
}
if (self.allParamters.count) {
[str appendFormat:@"[PARAMTERS] \n%@\n", self.allParamters];
}
if (self.multiDataStream.hasData) {
[str appendFormat:@"[MULTIDATA] \n%@\n", self.multiDataStream];
}
return str;
}
- (void)setServerURL:(NSString *)serverURL{
if (self.endpoint) {
_serverURL = [self.endpoint serverURLWithBucket:self.bucket appID:self.appId regionName:self.region].absoluteString;
}else if(self.needChangeHost){
_serverURL = [serverURL stringByReplacingOccurrencesOfString:@"myqcloud.com" withString:emergencyHost];
}else{
_serverURL = serverURL;
}
}
@end

View File

@@ -0,0 +1,129 @@
//
// QCloudRequestSerializer.h
// QCloudNetworking
//
// Created by tencent on 15/9/23.
// Copyright © 2015年 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
@class QCloudRequestData;
NS_ASSUME_NONNULL_BEGIN
FOUNDATION_EXTERN NSString *QCloudStrigngURLEncode(NSString *string, NSStringEncoding stringEncoding);
FOUNDATION_EXTERN NSString *QCloudURLEncodeParamters(NSDictionary *dic, BOOL willUrlEncoding, NSStringEncoding stringEncoding);
FOUNDATION_EXTERN NSString *QCloudURLEncodeUTF8(NSString *string);
FOUNDATION_EXTERN NSString *QCloudURLDecodeUTF8(NSString *string);
FOUNDATION_EXTERN NSString *QCloudNSURLEncode(NSString *url);
FOUNDATION_EXTERN NSDictionary *QCloudURLReadQuery(NSURL *url);
/**
HTTP POST 方法
*/
extern NSString *const HTTPMethodPOST;
/**
HTTP GET方法
*/
extern NSString *const HTTPMethodGET;
extern NSString *const HTTPHeaderHOST;
@class QCloudRequestData;
typedef NSMutableURLRequest *_Nullable (^QCloudRequestSerializerBlock)(NSMutableURLRequest *request, QCloudRequestData *data,
NSError *__autoreleasing *error);
/**
进行Request参数拼装的类此类可以配置HTTP相关的一些参数也可以配置协议相关的一些参数
*/
@interface QCloudRequestSerializer : NSObject
@property (nonnull, nonatomic, strong) NSString *HTTPMethod;
@property (nonatomic, assign) BOOL useCookies;
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
是否开启HTTPS验证默认为YES
*/
@property (nonatomic, assign) BOOL shouldAuthentication;
/**
是否处理cookies
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
是否开启pipeline功能
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
/**
设置根据requestData对请求的URL进行拼装的功能
*/
@property (nonatomic, strong, nullable) NSArray<QCloudRequestSerializerBlock> *serializerBlocks;
/**
是否开启GZIP压缩Response
*/
@property (nonatomic, assign) BOOL allowCompressedResponse;
/**
是否使用HTTPDNSPrefetch功能获取到IP
*/
@property (nonatomic, assign) BOOL HTTPDNSPrefetch;
- (NSMutableURLRequest *_Nullable)requestWithData:(QCloudRequestData *)data error:(NSError *__autoreleasing *)error;
@end
/**
按照Get请求拼参的方式将所有参数和URL拼接到URL中并获得URLRequet
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLAssembleWithParamters;
/**
只拼接ServerURL和MethodURL部分组成一个URL并获得URLRequest
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseSimple;
/**
在URL尾部按照?xx=xx&y=y的方式将所有参数拼接并获得URLRequest, @note 使用该方法将不会对Value进行URLEncode
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseWithParamters;
/**
在URL尾部按照?xx=xx&y=y的方式将所有参数拼接并获得URLRequest, @note 使用该方法将会对Value进行URLEncode
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseWithURLEncodeParamters;
/**
将所有参数按照xx=x&y=sdf的格式拼接在包体中并返回响应URLRequest
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLSerilizerURLEncodingBody;
/**
清除所有的头部参数
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLCleanAllHeader;
/**
将所有body参数按照JSON方式拼接到HTTPBody中并设置content-type为application/json
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseWithJSONParamters;
extern _Nonnull QCloudRequestSerializerBlock QCloudFuseMultiFormData;
/**
按照formdata方式将参数拼入到formdata中
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudFuseParamtersASMultiData;
/**
将一个KeyValueMap品入URL之中
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLSerilizerAppendURLParamters(NSDictionary *keyValueMaps);
/**
将requestData的URIMethod字段按照URL Paramters的形式拼装入url中
*/
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseURIMethodASURLParamters;
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseWithXMLParamters;
extern _Nonnull QCloudRequestSerializerBlock QCloudURLFuseContentMD5Base64StyleHeaders;
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,535 @@
//
// QCloudRequestSerializer.m
// QCloudNetworking
//
// Created by tencent on 15/9/23.
// Copyright © 2015 QCloudTernimalLab. All rights reserved.
//
#import "QCloudRequestSerializer.h"
#import "QCloudRequestData.h"
#import "NSHTTPCookie+QCloudNetworking.h"
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import "QCloudReachability.h"
#import "QCloudHTTPBodyPart.h"
#import "QCloudLogger.h"
#import "QCloudFileUtils.h"
#import "QCloudWeakProxy.h"
#import "QCloudXMLDictionary.h"
#import "QCloudEncryt.h"
#import "QCloudFileOffsetBody.h"
#import "QCloudURLHelper.h"
NSString *const HTTPMethodPOST = @"POST";
NSString *const HTTPMethodGET = @"GET";
NSString *const HTTPHeaderHOST = @"HOST";
NSString *const HTTPHeaderContentType = @"Content-Type";
NSString *const HTTPHeaderContentTypeURLEncode = @"application/x-www-form-urlencoded; charset=utf-8";
inline NSString *QCloudEnuserNoneNullString(NSString *str) {
if (!str) {
return @"";
} else {
return str;
}
}
NSString *QCloudStrigngURLEncode(NSString *string, NSStringEncoding stringEncoding) {
NSString *escaped_value = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
NULL, (CFStringRef)string, NULL, CFSTR(":/?#[]@!$ &'()*+,;=\"<>%{}|\\^`"), CFStringConvertNSStringEncodingToEncoding(stringEncoding)));
if (escaped_value) {
return escaped_value;
}
return @"";
}
NSString *QCloudStrigngURLEncodeWithoutSpecials(NSString *string, NSStringEncoding stringEncoding) {
NSString *escaped_value = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(
NULL, (CFStringRef)string, NULL, CFSTR("?#[]@!$ &'()*+,;=\"<>%{}|\\^~`"), CFStringConvertNSStringEncodingToEncoding(stringEncoding)));
if (escaped_value) {
return escaped_value;
}
return @"";
}
NSString *QCloudStringURLDecode(NSString *string, NSStringEncoding encoding) {
NSString *decoded = [string stringByReplacingPercentEscapesUsingEncoding:encoding];
return decoded;
}
NSString *QCloudURLEncodeUTF8(NSString *string) {
return QCloudStrigngURLEncode(string, NSUTF8StringEncoding);
}
NSString *QCloudURLDecodeUTF8(NSString *string) {
return QCloudStringURLDecode(string, NSUTF8StringEncoding);
}
NSString *QCloudNSURLEncode(NSString *url) {
url = url.lowercaseString;
NSArray *schemes = @[ @"http://", @"https://" ];
for (NSString *scheme in schemes) {
if ([url hasPrefix:scheme]) {
url = [url substringFromIndex:scheme.length];
url = [NSString stringWithFormat:@"%@%@", scheme, QCloudStrigngURLEncodeWithoutSpecials(url, NSUTF8StringEncoding)];
break;
}
}
return url;
}
NSString *QCloudEncodeURL(NSString *url) {
BOOL hasSubfix = [url hasSuffix:@"/"];
NSString *http = @"http://";
NSString *https = @"https://";
NSString *prefix = @"";
if ([url.lowercaseString hasPrefix:http]) {
url = [url substringFromIndex:http.length];
prefix = http;
} else if ([url.lowercaseString hasPrefix:https]) {
url = [url substringFromIndex:https.length];
prefix = https;
}
NSArray *compnents = [url componentsSeparatedByString:@"/"];
NSString *path = prefix;
for (NSString *component in compnents) {
path = QCloudPathJoin(path, QCloudStrigngURLEncode(component, NSUTF8StringEncoding));
}
if (hasSubfix) {
path = QCloudPathJoin(path, @"/");
}
return path;
}
NSDictionary *QCloudURLReadQuery(NSURL *url) {
NSString *query = url.query;
if (!query) {
return @ {};
}
NSMutableDictionary *queryDic = [NSMutableDictionary new];
NSArray *keyvalues = [query componentsSeparatedByString:@"&"];
for (NSString *kv in keyvalues) {
if (!kv.length) {
continue;
}
NSArray <NSString *>*vs = [kv componentsSeparatedByString:@"="];
if (vs.count == 2) {
if(vs.lastObject.length>0){
queryDic[QCloudStringURLDecode(vs[0], NSUTF8StringEncoding)] = QCloudStringURLDecode(vs[1], NSUTF8StringEncoding);
}
} else if (vs.count == 1) {
queryDic[QCloudStringURLDecode(vs.firstObject, NSUTF8StringEncoding)] = @"";
}
}
return queryDic;
}
NSString *QCloudURLEncodeParamters(NSDictionary *dic, BOOL willUrlEncoding, NSStringEncoding stringEncoding) {
NSArray *allKeys = dic.allKeys;
allKeys = [allKeys sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
NSMutableString *path = [NSMutableString new];
for (int i = 0; i < allKeys.count; i++) {
if (i > 0) {
[path appendString:@"&"];
}
NSString *key = allKeys[i];
NSString *value = dic[key];
if (willUrlEncoding) {
key = QCloudStrigngURLEncode(key, stringEncoding);
value = QCloudStrigngURLEncode(value, stringEncoding);
}
NSString *segement = [NSString stringWithFormat:@"%@=%@", key, value];
[path appendString:segement];
}
return [path copy];
}
NSString *QCloudURLAppendParamters(NSString *base, NSString *paramters) {
if (paramters.length == 0) {
return base;
}
if ([paramters hasPrefix:@"?"]) {
paramters = [paramters substringFromIndex:1];
}
NSRange range = [base rangeOfString:@"?"];
if (range.location != NSNotFound) {
if ([base hasSuffix:@"?"]) {
return [NSString stringWithFormat:@"%@%@", base, paramters];
} else {
if ([base hasSuffix:@"&"]) {
return [NSString stringWithFormat:@"%@%@", base, paramters];
} else {
return [NSString stringWithFormat:@"%@&%@", base, paramters];
}
}
} else {
return [NSString stringWithFormat:@"%@?%@", base, paramters];
}
}
QCloudRequestSerializerBlock QCloudURLAssembleWithParamters
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSString *path = QCloudPathJoin(data.serverURL, data.URIMethod);
path = QCloudURLAppendParamters(path, QCloudURLEncodeParamters(data.allParamters, NO, data.stringEncoding));
NSURL *url = [NSURL URLWithString:path];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
return urlRequest;
};
QCloudRequestSerializerBlock QCloudFuseParamtersASMultiData
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSArray *keys = data.allParamters.allKeys;
for (NSString *key in keys) {
NSString *value = data.allParamters[key];
NSCAssert([value isKindOfClass:[NSString class]], @"请传入NSString类型的Value Key:%@ Value:%@", key, value);
NSData *indata = [value dataUsingEncoding:data.stringEncoding];
QCloudHTTPBodyPart *part = [[QCloudHTTPBodyPart alloc] initWithData:indata];
[part setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", key] forHeaderKey:@"Content-Disposition"];
[data.multiDataStream insertBodyPart:part];
}
return request;
};
QCloudRequestSerializerBlock QCloudFuseMultiFormData = ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
if (data.multiDataStream.hasData) {
[data.multiDataStream setInitialAndFinalBoundaries];
[request setHTTPBodyStream:(NSInputStream *)[QCloudWeakProxy proxyWithTarget:data.multiDataStream]];
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", data.multiDataStream.boundary]
forHTTPHeaderField:@"Content-Type"];
[request setValue:[NSString stringWithFormat:@"%llu", [data.multiDataStream contentLength]] forHTTPHeaderField:@"Content-Length"];
}
return request;
};
NSString *QCloudURLFuseAllPathComponents(NSArray *componets) {
NSString *path = @"";
for (NSString *com in componets) {
if (com.length > 0) {
path = QCloudPathJoin(path, com);
}
}
path = QCloudPercentEscapedStringFromString(path);
return path;
}
QCloudRequestSerializerBlock QCloudURLFuseSimple = ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSMutableArray *coms = [NSMutableArray new];
if (data.URIMethod.length) {
[coms addObject:data.URIMethod];
}
if (data.URIComponents.count) {
[coms addObjectsFromArray:data.URIComponents];
}
NSString *path = QCloudURLFuseAllPathComponents(coms);
path = QCloudPathJoin(data.serverURL, path);
NSURL *url = [NSURL URLWithString:path];
if (nil == url) {
url = [NSURL URLWithString:QCloudURLEncodeUTF8(path)];
}
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
return urlRequest;
};
QCloudRequestSerializerBlock QCloudURLFuseWithParamters = ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSString *path = QCloudPathJoin(data.serverURL, data.URIMethod);
path = QCloudURLAppendParamters(path, QCloudURLEncodeParamters(data.allParamters, NO, data.stringEncoding));
NSURL *url = [NSURL URLWithString:path];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
return urlRequest;
};
QCloudRequestSerializerBlock QCloudURLFuseWithJSONParamters
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:data.allParamters options:NSJSONWritingPrettyPrinted error:error];
if (*error) {
return (NSMutableURLRequest *)nil;
}
[request setValue:[NSString stringWithFormat:@"application/json"] forHTTPHeaderField:@"Content-Type"];
[request setValue:[@([jsonData length]) stringValue] forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody:jsonData];
return request;
};
QCloudRequestSerializerBlock QCloudURLFuseWithXMLParamters
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
if (data.allParamters.count) {
NSString *str = [data.allParamters qcxml_XMLString];
[request setValue:[NSString stringWithFormat:@"application/xml"] forHTTPHeaderField:@"Content-Type"];
NSData *bodyData = [str dataUsingEncoding:NSUTF8StringEncoding];
[request setValue:[@([bodyData length]) stringValue] forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody:bodyData];
}
return request;
};
QCloudRequestSerializerBlock QCloudURLFuseWithURLEncodeParamters
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSString *urlStr = nil;
if (request.URL.absoluteString.length > 0) {
urlStr = request.URL.absoluteString;
} else {
urlStr = QCloudPathJoin(data.serverURL, data.URIMethod);
}
urlStr = QCloudURLAppendParamters(urlStr, QCloudURLEncodeParamters(data.allParamters, YES, data.stringEncoding));
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
return urlRequest;
};
QCloudRequestSerializerBlock QCloudURLFuseURIMethodASURLParamters
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSString *urlStr = nil;
if (request.URL.absoluteString.length > 0) {
urlStr = request.URL.absoluteString;
} else {
urlStr = data.serverURL;
NSMutableArray *coms = [NSMutableArray new];
if (data.URIComponents.count) {
[coms addObjectsFromArray:data.URIComponents];
}
NSString *path = QCloudURLFuseAllPathComponents(coms);
urlStr = QCloudPathJoin(urlStr, path);
}
NSMutableDictionary *methodParamters = [NSMutableDictionary new];
if (data.URIMethod) {
methodParamters[data.URIMethod] = @"";
urlStr = QCloudURLAppendParamters(urlStr, QCloudURLEncodeParamters(methodParamters, YES, data.stringEncoding));
if (urlStr.length && [urlStr hasSuffix:@"="]) {
urlStr = [urlStr substringToIndex:urlStr.length - 1];
}
}
NSURL *url = [NSURL URLWithString:urlStr];
if (!request) {
request = [[NSMutableURLRequest alloc] initWithURL:url];
} else {
[request setURL:url];
}
return request;
};
QCloudRequestSerializerBlock QCloudURLFuseContentMD5Base64StyleHeaders
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
if (request.HTTPBody) {
NSData *data = request.HTTPBody;
NSString *md5 = QCloudEncrytNSDataMD5Base64(data);
if (md5) {
[request setValue:md5 forHTTPHeaderField:@"Content-MD5"];
}
} else if (data.directBody) {
if ([data.directBody isKindOfClass:[NSData class]]) {
NSData *md5data = data.directBody;
NSString *md5 = QCloudEncrytNSDataMD5Base64(md5data);
if (md5) {
[request setValue:md5 forHTTPHeaderField:@"Content-MD5"];
}
} else if ([data.directBody isKindOfClass:[NSURL class]]) {
NSString *md5 = QCloudEncrytFileMD5Base64([(NSURL *)data.directBody path]);
if (md5) {
[request setValue:md5 forHTTPHeaderField:@"Content-MD5"];
}
} else if ([data.directBody isKindOfClass:[QCloudFileOffsetBody class]]) {
QCloudFileOffsetBody *body = (QCloudFileOffsetBody *)data.directBody;
NSString *md5 = QCloudEncrytFileOffsetMD5Base64(body.fileURL.path, body.offset, body.sliceLength);
if (md5) {
[request setValue:md5 forHTTPHeaderField:@"Content-MD5"];
}
}
}
return request;
};
QCloudRequestSerializerBlock QCloudURLSerilizerHTTPHeaderParamters
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSDictionary *allParamters = data.allParamters;
NSArray *allKeys = allParamters.allKeys;
for (NSString *key in allKeys) {
[request setValue:allParamters[key] forHTTPHeaderField:key];
}
return request;
};
QCloudRequestSerializerBlock QCloudURLSerilizerAppendURLParamters(NSDictionary *keyValueMaps) {
return ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSString *urlStr = nil;
if (request.URL.absoluteString.length > 0) {
urlStr = request.URL.absoluteString;
} else {
urlStr = QCloudPathJoin(data.serverURL, data.URIMethod);
}
urlStr = QCloudURLAppendParamters(urlStr, QCloudURLEncodeParamters(keyValueMaps, YES, data.stringEncoding));
if (urlStr.length && [urlStr hasSuffix:@"="]) {
urlStr = [urlStr substringToIndex:urlStr.length - 1];
}
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *urlRequest;
if (request) {
urlRequest = [request mutableCopy];
[urlRequest setURL:url];
} else {
urlRequest = [[NSMutableURLRequest alloc] initWithURL:url];
}
return urlRequest;
};
}
QCloudRequestSerializerBlock QCloudURLSerilizerURLEncodingBody
= ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
NSDictionary *allParamters = data.allParamters;
NSString *content = QCloudURLEncodeParamters(allParamters, YES, data.stringEncoding);
NSData *contentData = [content dataUsingEncoding:data.stringEncoding];
[request setHTTPBody:contentData];
[request setValue:HTTPHeaderContentTypeURLEncode forHTTPHeaderField:HTTPHeaderContentType];
[request setValue:[@(contentData.length) stringValue] forHTTPHeaderField:@"Content-Length"];
return request;
};
QCloudRequestSerializerBlock QCloudURLCleanAllHeader = ^(NSMutableURLRequest *request, QCloudRequestData *data, NSError *__autoreleasing *error) {
[request setValue:nil forHTTPHeaderField:@"Accept-Encoding"];
[request setValue:nil forHTTPHeaderField:@"Connection"];
[request setValue:nil forHTTPHeaderField:@"Cookie"];
[request setValue:@"" forHTTPHeaderField:HTTPHeaderUserAgent];
return request;
};
static NSArray *QCloudHTTPReqeustSerializerObservedKeyPath() {
static NSArray *paths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
paths = @[
NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)),
NSStringFromSelector(@selector(HTTPShouldSetCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)),
NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))
];
});
return paths;
}
static void *QCloudHTTPRequestSerializerObserverContext = &QCloudHTTPRequestSerializerObserverContext;
@interface QCloudRequestSerializer () {
NSMutableSet *_mutableChangedPaths;
NSDictionary *_defaultHTTPHeaders;
}
@end
@implementation QCloudRequestSerializer
@synthesize shouldAuthentication = _shouldAuthentication;
- (void)dealloc {
for (NSString *selector in QCloudHTTPReqeustSerializerObservedKeyPath()) {
if ([self respondsToSelector:NSSelectorFromString(selector)]) {
@try {
[self removeObserver:self forKeyPath:selector];
} @catch (NSException *exception) {
QCloudLogDebugE(@"Utils",@"没有该观察者");
}
}
}
}
- (void)__commonInit {
//
_mutableChangedPaths = [NSMutableSet new];
for (NSString *keyPath in QCloudHTTPReqeustSerializerObservedKeyPath()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:QCloudHTTPRequestSerializerObserverContext];
}
}
//
_HTTPMethod = HTTPMethodGET;
_allowCompressedResponse = NO;
_serializerBlocks = @[ QCloudURLFuseWithParamters ];
_HTTPDNSPrefetch = YES;
_useCookies = YES;
_shouldAuthentication = YES;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
[self __commonInit];
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *, id> *)change context:(void *)context {
if (context == QCloudHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[_mutableChangedPaths removeObject:keyPath];
} else {
[_mutableChangedPaths addObject:keyPath];
}
}
}
- (NSMutableURLRequest *)requestWithData:(QCloudRequestData *)data error:(NSError *__autoreleasing *)error {
NSMutableURLRequest *request = [NSMutableURLRequest new];
request.cachePolicy = self.cachePolicy;
request.timeoutInterval = self.timeoutInterval;
NSAssert(self.serializerBlocks.count != 0, @"没有添加任何的序列化匿名函数,请检查配置!!!");
NSError *localError;
for (QCloudRequestSerializerBlock sBlock in self.serializerBlocks) {
request = sBlock(request, data, &localError);
if (localError != nil) {
if (error != NULL) {
*error = localError;
}
return nil;
}
}
if (!request || *error) {
if (error != NULL) {
*error = [NSError errorWithDomain:@"com.tencent.qcloud.error"
code:-1112
userInfo:@{ NSLocalizedDescriptionKey : @"对request进行配置的时候出错请检查所有的配置Block" }];
}
return nil;
}
request.HTTPMethod = self.HTTPMethod;
//
for (NSString *keyPath in QCloudHTTPReqeustSerializerObservedKeyPath()) {
if ([_mutableChangedPaths containsObject:keyPath]) {
[request setValue:[self valueForKey:keyPath] forKey:keyPath];
}
}
//
if (data.queryParamters.count > 0) {
NSURL *url = request.URL;
NSString *urlString = url.absoluteString;
urlString = QCloudURLAppendParamters(urlString, QCloudURLEncodeParamters(data.queryParamters, YES, data.stringEncoding));
url = [NSURL URLWithString:urlString];
[request setURL:url];
}
//
// http headers
if (self.allowCompressedResponse) {
[data setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
}
//
NSDictionary *headers = [data.httpHeaders copy];
NSArray *allKeys = headers.allKeys;
for (NSString *key in allKeys) {
[request setValue:headers[key] forHTTPHeaderField:key];
}
if (_useCookies && request.URL) {
// Cokies
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:[request.URL absoluteURL]];
cookies = QCloudFuseAndUpdateCookiesArray(data.cookies, cookies);
NSDictionary *cookiesInfos = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[request setAllHTTPHeaderFields:cookiesInfos];
}
return request;
}
@end

View File

@@ -0,0 +1,22 @@
//
// QCloudResponseSerializer.h
// QCloudNetworking
//
// Created by tencent on 15/9/25.
// Copyright © 2015年 QCloudTernimalLab. All rights reserved.
//
#import <Foundation/Foundation.h>
typedef id (^QCloudResponseSerializerBlock)(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error);
@interface QCloudResponseSerializer : NSObject
@property (nonatomic, assign) BOOL waitForBodyData;
@property (nonatomic, strong) NSArray<QCloudResponseSerializerBlock> *serializerBlocks;
- (id)decodeWithWithResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error;
@end
FOUNDATION_EXTERN QCloudResponseSerializerBlock QCloudAcceptRespnseCodeBlock(NSSet *acceptCode, Class errorModel);
FOUNDATION_EXTERN QCloudResponseSerializerBlock QCloudResponseXMLSerializerBlock;
FOUNDATION_EXTERN QCloudResponseSerializerBlock QCloudResponseJSONSerilizerBlock;
FOUNDATION_EXTERN QCloudResponseSerializerBlock QCloudResponseAppendHeadersSerializerBlock;
FOUNDATION_EXTERN QCloudResponseSerializerBlock QCloudResponseDataAppendHeadersSerializerBlock;

View File

@@ -0,0 +1,193 @@
//
// QCloudResponseSerializer.m
// QCloudNetworking
//
// Created by tencent on 15/9/25.
// Copyright © 2015 QCloudTernimalLab. All rights reserved.
//
#import "QCloudResponseSerializer.h"
#import "QCloudLogger.h"
#import "NSError+QCloudNetworking.h"
#import "QCloudObjectModel.h"
#import "QCloudXMLDictionary.h"
typedef id (^QCloudResponseSerializerBlock)(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error);
QCloudResponseSerializerBlock QCloudResponseXMLSerializerBlock = ^(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error) {
if (![inputData isKindOfClass:[NSData class]]) {
if (NULL != error) {
*error = [NSError qcloud_errorWithCode:QCloudNetworkErrorCodeResponseDataTypeInvalid
message:[NSString stringWithFormat:@"ServerError:XML解析器读入的数据不是NSData"]];
}
return (id)nil;
}
if ([(NSData *)inputData length] == 0) {
NSDictionary *emptyDictionary = [[NSDictionary alloc] init];
return (id)emptyDictionary;
}
#ifdef DEBUG
#endif
QCloudXMLDictionaryParser *parser = [QCloudXMLDictionaryParser new];
NSDictionary *output = [parser dictionaryWithData:inputData];
if (!output) {
if (NULL != error) {
*error = [NSError qcloud_errorWithCode:QCloudNetworkErrorCodeResponseDataTypeInvalid
message:[NSString stringWithFormat:@"ServerError:尝试解析XML类型数据出错:\n%@",
[[NSString alloc] initWithData:inputData
encoding:NSUTF8StringEncoding]]];
}
return (id)nil;
}
if (output[@"Code"] && [output[@"__name"] isEqualToString:@"Error"]) {
*error = [NSError qcloud_errorWithCode:500
message:[NSString stringWithFormat:output[@"Code"], [[NSString alloc] initWithData:inputData
encoding:NSUTF8StringEncoding]]];
return (id)nil;
}
QCloudLogDebugR(@"HTTP",@"原始数据:%@", output);
return (id)output;
};
QCloudResponseSerializerBlock QCloudResponseAppendHeadersSerializerBlock
= ^(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error) {
NSMutableDictionary *allDatas = [NSMutableDictionary new];
if ([inputData isKindOfClass:[NSDictionary class]]) {
[allDatas addEntriesFromDictionary:(NSDictionary *)inputData];
}
[allDatas addEntriesFromDictionary:response.allHeaderFields];
return (id)allDatas;
};
QCloudResponseSerializerBlock QCloudResponseDataAppendHeadersSerializerBlock
= ^(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error) {
NSMutableDictionary *allDatas = [NSMutableDictionary new];
if ([inputData isKindOfClass:[NSDictionary class]]) {
[allDatas addEntriesFromDictionary:(NSDictionary *)inputData];
} else {
if (inputData != nil) {
[allDatas setObject:inputData forKey:@"data"];
}
}
[allDatas addEntriesFromDictionary:response.allHeaderFields];
return (id)allDatas;
};
QCloudResponseSerializerBlock QCloudAcceptRespnseCodeBlock(NSSet *acceptCode, Class errorModel) {
return ^(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error) {
void (^LoadDefaultError)(void) = ^() {
NSString *errorMessage = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
errorMessage = errorMessage ?: [NSString stringWithFormat:@"不接受该HTTP StatusCode %ld", (long)response.statusCode];
if (error != NULL) {
*error = [NSError qcloud_errorWithCode:(int)response.statusCode message:errorMessage];
}
};
if ([acceptCode containsObject:@(response.statusCode)]) {
return inputData;
} else {
NSString *contentType = [response.allHeaderFields objectForKey:@"Content-Type"];
NSDictionary *userInfo = nil;
if (contentType) {
if ([contentType.lowercaseString containsString:@"application/json"]) {
NSError *localError = nil;
NSDictionary *map = [NSJSONSerialization JSONObjectWithData:inputData options:0 error:&localError];
if (localError) {
LoadDefaultError();
} else {
userInfo = map;
}
} else if ([contentType.lowercaseString containsString:@"application/xml"]) {
QCloudXMLDictionaryParser *parser = [QCloudXMLDictionaryParser new];
NSDictionary *output = [parser dictionaryWithData:inputData];
if (output) {
userInfo = output;
}
}
}
if (userInfo) {
if (!errorModel) {
if (error != NULL) {
*error = [NSError errorWithDomain:kQCloudNetworkDomain code:response.statusCode userInfo:userInfo];
}
return (id)nil;
}
if ([errorModel respondsToSelector:@selector(toError:)]) {
if (error != NULL) {
*error = [errorModel toError:userInfo];
}
} else {
LoadDefaultError();
}
}
if ((error != NULL) && !(*error)) {
LoadDefaultError();
}
return (id)nil;
}
};
}
QCloudResponseSerializerBlock QCloudResponseJSONSerilizerBlock = ^(NSHTTPURLResponse *response, id inputData, NSError *__autoreleasing *error) {
if (![inputData isKindOfClass:[NSData class]]) {
if (error != NULL) {
*error = [NSError errorWithDomain:@"com.tencent.networking"
code:-1404
userInfo:@{ NSLocalizedDescriptionKey : @"数据非法,请传入合法数据" }];
}
return (id)nil;
}
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:inputData options:0 error:error];
if (*error || !jsonObject) {
NSString *str = [[NSString alloc] initWithData:inputData encoding:NSUTF8StringEncoding];
QCloudLogErrorE(@"HTTP",@"response data is %@", str);
return (id)nil;
}
QCloudLogDebugR(@"HTTP",@"GET JSON : \n %@", jsonObject);
return (id)(jsonObject);
};
@interface QCloudResponseSerializer () {
NSMutableArray *_serializerBlocks;
}
@end
@implementation QCloudResponseSerializer
- (void)__responseCommonInit {
_serializerBlocks = [NSMutableArray new];
[_serializerBlocks addObject:QCloudAcceptRespnseCodeBlock([NSSet setWithArray:@[ @(200) ]], nil)];
[_serializerBlocks addObject:QCloudResponseJSONSerilizerBlock];
_waitForBodyData = YES;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
[self __responseCommonInit];
return self;
}
- (id)decodeWithWithResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error {
NSError *localError;
id output = data;
for (QCloudResponseSerializerBlock block in _serializerBlocks) {
output = block(response, output, &localError);
if (localError) {
if (error != NULL) {
*error = localError;
}
return nil;
}
}
return output;
}
@end

View File

@@ -0,0 +1,11 @@
//
// QCloudURLHelper.h
// Pods
//
// Created by Dong Zhao on 2017/9/7.
//
//
#import <Foundation/Foundation.h>
FOUNDATION_EXTERN NSString *QCloudPercentEscapedStringFromString(NSString *string);

View File

@@ -0,0 +1,42 @@
//
// QCloudURLHelper.m
// Pods
//
// Created by Dong Zhao on 2017/9/7.
//
//
#import "QCloudURLHelper.h"
NSString *QCloudPercentEscapedStringFromString(NSString *string) {
static NSString *const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString *const kAFCharactersSubDelimitersToEncode = @"?!$&'()*+,;=";
NSMutableCharacterSet *allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet
removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length) {
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
}
return escaped;
}

View File

@@ -0,0 +1,107 @@
//
// QCloudXMLDictionary.h
//
// Version 1.4.1
//
// Created by Nick Lockwood on 15/11/2010.
// Copyright 2010 Charcoal Design. All rights reserved.
//
// Get the latest version of QCloudXMLDictionary from here:
//
// https://github.com/nicklockwood/QCloudXMLDictionary
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, QCloudXMLDictionaryAttributesMode) {
QCloudXMLDictionaryAttributesModePrefixed = 0, // default
QCloudXMLDictionaryAttributesModeDictionary,
QCloudXMLDictionaryAttributesModeUnprefixed,
QCloudXMLDictionaryAttributesModeDiscard
};
typedef NS_ENUM(NSInteger, QCloudXMLDictionaryNodeNameMode) {
QCloudXMLDictionaryNodeNameModeRootOnly = 0, // default
QCloudXMLDictionaryNodeNameModeAlways,
QCloudXMLDictionaryNodeNameModeNever
};
static NSString *const QCloudXMLDictionaryAttributesKey = @"__attributes";
static NSString *const QCloudXMLDictionaryCommentsKey = @"__comments";
static NSString *const QCloudXMLDictionaryTextKey = @"__text";
static NSString *const QCloudXMLDictionaryNodeNameKey = @"__name";
static NSString *const QCloudXMLDictionaryAttributePrefix = @"_";
@interface QCloudXMLDictionaryParser : NSObject <NSCopying>
+ (QCloudXMLDictionaryParser *)sharedInstance;
@property (nonatomic, assign) BOOL collapseTextNodes; // defaults to YES
@property (nonatomic, assign) BOOL stripEmptyNodes; // defaults to YES
@property (nonatomic, assign) BOOL trimWhiteSpace; // defaults to YES
@property (nonatomic, assign) BOOL alwaysUseArrays; // defaults to NO
@property (nonatomic, assign) BOOL preserveComments; // defaults to NO
@property (nonatomic, assign) BOOL wrapRootNode; // defaults to NO
@property (nonatomic, assign) QCloudXMLDictionaryAttributesMode attributesMode;
@property (nonatomic, assign) QCloudXMLDictionaryNodeNameMode nodeNameMode;
- (nullable NSDictionary<NSString *, id> *)dictionaryWithParser:(NSXMLParser *)parser;
- (nullable NSDictionary<NSString *, id> *)dictionaryWithData:(NSData *)data;
- (nullable NSDictionary<NSString *, id> *)dictionaryWithString:(NSString *)string;
- (nullable NSDictionary<NSString *, id> *)dictionaryWithFile:(NSString *)path;
@end
@interface NSDictionary (QCloudXMLDictionary)
+ (nullable NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLParser:(NSXMLParser *)parser;
+ (nullable NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLData:(NSData *)data;
+ (nullable NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLString:(NSString *)string;
+ (nullable NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLFile:(NSString *)path;
@property (nonatomic, readonly, copy, nullable) NSDictionary<NSString *, NSString *> *qcxml_attributes;
@property (nonatomic, readonly, copy, nullable) NSDictionary<NSString *, id> *qcxml_childNodes;
@property (nonatomic, readonly, copy, nullable) NSArray<NSString *> *qcxml_comments;
@property (nonatomic, readonly, copy, nullable) NSString *qcxml_nodeName;
@property (nonatomic, readonly, copy, nullable) NSString *qcxml_innerText;
@property (nonatomic, readonly, copy) NSString *qcxml_innerXML;
@property (nonatomic, readonly, copy) NSString *qcxml_XMLString;
- (nullable NSArray *)qcxml_arrayValueForKeyPath:(NSString *)keyPath;
- (nullable NSString *)qcxml_stringValueForKeyPath:(NSString *)keyPath;
- (nullable NSDictionary<NSString *, id> *)qcxml_dictionaryValueForKeyPath:(NSString *)keyPath;
@end
@interface NSString (QCloudXMLDictionary)
@property (nonatomic, readonly, copy) NSString *QCXMLEncodedString;
@end
NS_ASSUME_NONNULL_END
#pragma GCC diagnostic pop

View File

@@ -0,0 +1,450 @@
//
// QCloudXMLDictionary.m
//
// Version 1.4.1
//
// Created by Nick Lockwood on 15/11/2010.
// Copyright 2010 Charcoal Design. All rights reserved.
//
// Get the latest version of QCloudXMLDictionary from here:
//
// https://github.com/nicklockwood/QCloudXMLDictionary
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
#import "QCloudXMLDictionary.h"
#pragma GCC diagnostic ignored "-Wobjc-missing-property-synthesis"
#pragma GCC diagnostic ignored "-Wpartial-availability"
#pragma GCC diagnostic ignored "-Wdirect-ivar-access"
#pragma GCC diagnostic ignored "-Wformat-non-iso"
#pragma GCC diagnostic ignored "-Wgnu"
#import <Availability.h>
#if !__has_feature(objc_arc)
#error This class requires automatic reference counting
#endif
@interface QCloudXMLDictionaryParser () <NSXMLParserDelegate>
@property (nonatomic, strong) NSMutableDictionary<NSString *, id> *root;
@property (nonatomic, strong) NSMutableArray *stack;
@property (nonatomic, strong) NSMutableString *text;
@end
@implementation QCloudXMLDictionaryParser
+ (QCloudXMLDictionaryParser *)sharedInstance {
static dispatch_once_t once;
static QCloudXMLDictionaryParser *sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[QCloudXMLDictionaryParser alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
if ((self = [super init])) {
_collapseTextNodes = YES;
_stripEmptyNodes = YES;
_trimWhiteSpace = YES;
_alwaysUseArrays = NO;
_preserveComments = NO;
_wrapRootNode = NO;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
QCloudXMLDictionaryParser *copy = [[[self class] allocWithZone:zone] init];
copy.collapseTextNodes = _collapseTextNodes;
copy.stripEmptyNodes = _stripEmptyNodes;
copy.trimWhiteSpace = _trimWhiteSpace;
copy.alwaysUseArrays = _alwaysUseArrays;
copy.preserveComments = _preserveComments;
copy.attributesMode = _attributesMode;
copy.nodeNameMode = _nodeNameMode;
copy.wrapRootNode = _wrapRootNode;
return copy;
}
- (NSDictionary<NSString *, id> *)dictionaryWithParser:(NSXMLParser *)parser {
parser.delegate = self;
[parser parse];
id result = _root;
_root = nil;
_stack = nil;
_text = nil;
return result;
}
- (NSDictionary<NSString *, id> *)dictionaryWithData:(NSData *)data {
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setShouldResolveExternalEntities:NO];
return [self dictionaryWithParser:parser];
}
- (NSDictionary<NSString *, id> *)dictionaryWithString:(NSString *)string {
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
return [self dictionaryWithData:data];
}
- (NSDictionary<NSString *, id> *)dictionaryWithFile:(NSString *)path {
NSData *data = [NSData dataWithContentsOfFile:path];
return [self dictionaryWithData:data];
}
+ (NSString *)XMLStringForNode:(id)node withNodeName:(NSString *)nodeName {
if ([node isKindOfClass:[NSArray class]]) {
NSMutableArray<NSString *> *nodes = [NSMutableArray arrayWithCapacity:[node count]];
for (id individualNode in node) {
[nodes addObject:[self XMLStringForNode:individualNode withNodeName:nodeName]];
}
return [nodes componentsJoinedByString:@"\n"];
} else if ([node isKindOfClass:[NSDictionary class]]) {
NSDictionary<NSString *, NSString *> *attributes = [(NSDictionary *)node qcxml_attributes];
NSMutableString *attributeString = [NSMutableString string];
[attributes enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, __unused BOOL *stop) {
[attributeString appendFormat:@" %@=\"%@\"", key.description.QCXMLEncodedString, value.description.QCXMLEncodedString];
}];
NSString *innerXML = [node qcxml_innerXML];
if (innerXML.length) {
return [NSString stringWithFormat:@"<%1$@%2$@>%3$@</%1$@>", nodeName, attributeString, innerXML];
} else {
return [NSString stringWithFormat:@"<%@%@/>", nodeName, attributeString];
}
} else {
return [NSString stringWithFormat:@"<%1$@>%2$@</%1$@>", nodeName, [node description].QCXMLEncodedString];
}
}
- (void)endText {
if (_trimWhiteSpace) {
_text = [[_text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] mutableCopy];
}
if (_text.length) {
NSMutableDictionary *top = _stack.lastObject;
id existing = top[QCloudXMLDictionaryTextKey];
if ([existing isKindOfClass:[NSArray class]]) {
[existing addObject:_text];
} else if (existing) {
top[QCloudXMLDictionaryTextKey] = [@[ existing, _text ] mutableCopy];
} else {
top[QCloudXMLDictionaryTextKey] = _text;
}
}
_text = nil;
}
- (void)addText:(NSString *)text {
if (!_text) {
_text = [NSMutableString stringWithString:text];
} else {
[_text appendString:text];
}
}
- (void)parser:(__unused NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(__unused NSString *)namespaceURI
qualifiedName:(__unused NSString *)qName
attributes:(NSDictionary *)attributeDict {
if ([elementName isEqualToString:@"CommonPrefixes"] || [elementName isEqualToString:@"Key"]) {
self.trimWhiteSpace = NO;
}
[self endText];
NSMutableDictionary<NSString *, id> *node = [NSMutableDictionary dictionary];
switch (_nodeNameMode) {
case QCloudXMLDictionaryNodeNameModeRootOnly: {
if (!_root) {
node[QCloudXMLDictionaryNodeNameKey] = elementName;
}
break;
}
case QCloudXMLDictionaryNodeNameModeAlways: {
node[QCloudXMLDictionaryNodeNameKey] = elementName;
break;
}
case QCloudXMLDictionaryNodeNameModeNever: {
break;
}
}
if (attributeDict.count) {
switch (_attributesMode) {
case QCloudXMLDictionaryAttributesModePrefixed: {
for (NSString *key in attributeDict) {
node[[QCloudXMLDictionaryAttributePrefix stringByAppendingString:key]] = attributeDict[key];
}
break;
}
case QCloudXMLDictionaryAttributesModeDictionary: {
node[QCloudXMLDictionaryAttributesKey] = attributeDict;
break;
}
case QCloudXMLDictionaryAttributesModeUnprefixed: {
[node addEntriesFromDictionary:attributeDict];
break;
}
case QCloudXMLDictionaryAttributesModeDiscard: {
break;
}
}
}
if (!_root) {
_root = node;
_stack = [NSMutableArray arrayWithObject:node];
if (_wrapRootNode) {
_root = [NSMutableDictionary dictionaryWithObject:_root forKey:elementName];
[_stack insertObject:_root atIndex:0];
}
} else {
NSMutableDictionary<NSString *, id> *top = _stack.lastObject;
id existing = top[elementName];
if ([existing isKindOfClass:[NSArray class]]) {
[(NSMutableArray *)existing addObject:node];
} else if (existing) {
top[elementName] = [@[ existing, node ] mutableCopy];
} else if (_alwaysUseArrays) {
top[elementName] = [NSMutableArray arrayWithObject:node];
} else {
top[elementName] = node;
}
[_stack addObject:node];
}
}
- (NSString *)nameForNode:(NSDictionary<NSString *, id> *)node inDictionary:(NSDictionary<NSString *, id> *)dict {
if (node.qcxml_nodeName) {
return node.qcxml_nodeName;
} else {
for (NSString *name in dict) {
id object = dict[name];
if (object == node) {
return name;
} else if ([object isKindOfClass:[NSArray class]] && [(NSArray *)object containsObject:node]) {
return name;
}
}
}
return nil;
}
- (void)parser:(__unused NSXMLParser *)parser
didEndElement:(__unused NSString *)elementName
namespaceURI:(__unused NSString *)namespaceURI
qualifiedName:(__unused NSString *)qName {
[self endText];
NSMutableDictionary<NSString *, id> *top = _stack.lastObject;
[_stack removeLastObject];
if (!top.qcxml_attributes && !top.qcxml_childNodes && !top.qcxml_comments) {
NSMutableDictionary<NSString *, id> *newTop = _stack.lastObject;
NSString *nodeName = [self nameForNode:top inDictionary:newTop];
if (nodeName) {
id parentNode = newTop[nodeName];
NSString *innerText = top.qcxml_innerText;
if (innerText && _collapseTextNodes) {
if ([parentNode isKindOfClass:[NSArray class]]) {
parentNode[[parentNode count] - 1] = innerText;
} else {
newTop[nodeName] = innerText;
}
} else if (!innerText) {
if (_stripEmptyNodes) {
if ([parentNode isKindOfClass:[NSArray class]]) {
[(NSMutableArray *)parentNode removeLastObject];
} else {
[newTop removeObjectForKey:nodeName];
}
} else if (!_collapseTextNodes) {
top[QCloudXMLDictionaryTextKey] = @"";
}
}
}
}
}
- (void)parser:(__unused NSXMLParser *)parser foundCharacters:(NSString *)string {
[self addText:string];
}
- (void)parser:(__unused NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock {
[self addText:[[NSString alloc] initWithData:CDATABlock encoding:NSUTF8StringEncoding]];
}
- (void)parser:(__unused NSXMLParser *)parser foundComment:(NSString *)comment {
if (_preserveComments) {
NSMutableDictionary<NSString *, id> *top = _stack.lastObject;
NSMutableArray<NSString *> *comments = top[QCloudXMLDictionaryCommentsKey];
if (!comments) {
comments = [@[ comment ] mutableCopy];
top[QCloudXMLDictionaryCommentsKey] = comments;
} else {
[comments addObject:comment];
}
}
}
@end
@implementation NSDictionary (QCloudXMLDictionary)
+ (NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLParser:(NSXMLParser *)parser {
return [[[QCloudXMLDictionaryParser sharedInstance] copy] dictionaryWithParser:parser];
}
+ (NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLData:(NSData *)data {
return [[[QCloudXMLDictionaryParser sharedInstance] copy] dictionaryWithData:data];
}
+ (NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLString:(NSString *)string {
return [[[QCloudXMLDictionaryParser sharedInstance] copy] dictionaryWithString:string];
}
+ (NSDictionary<NSString *, id> *)qcxml_dictionaryWithXMLFile:(NSString *)path {
return [[[QCloudXMLDictionaryParser sharedInstance] copy] dictionaryWithFile:path];
}
- (nullable NSDictionary<NSString *, NSString *> *)qcxml_attributes {
NSDictionary<NSString *, NSString *> *attributes = self[QCloudXMLDictionaryAttributesKey];
if (attributes) {
return attributes.count ? attributes : nil;
} else {
NSMutableDictionary<NSString *, id> *filteredDict = [NSMutableDictionary dictionaryWithDictionary:self];
[filteredDict removeObjectsForKeys:@[ QCloudXMLDictionaryCommentsKey, QCloudXMLDictionaryTextKey, QCloudXMLDictionaryNodeNameKey ]];
for (NSString *key in filteredDict.allKeys) {
[filteredDict removeObjectForKey:key];
if ([key hasPrefix:QCloudXMLDictionaryAttributePrefix]) {
filteredDict[[key substringFromIndex:QCloudXMLDictionaryAttributePrefix.length]] = self[key];
}
}
return filteredDict.count ? filteredDict : nil;
}
return nil;
}
- (nullable NSDictionary *)qcxml_childNodes {
NSMutableDictionary *filteredDict = [self mutableCopy];
[filteredDict removeObjectsForKeys:@[
QCloudXMLDictionaryAttributesKey, QCloudXMLDictionaryCommentsKey, QCloudXMLDictionaryTextKey, QCloudXMLDictionaryNodeNameKey
]];
for (NSString *key in filteredDict.allKeys) {
if ([key hasPrefix:QCloudXMLDictionaryAttributePrefix]) {
[filteredDict removeObjectForKey:key];
}
}
return filteredDict.count ? filteredDict : nil;
}
- (nullable NSArray *)qcxml_comments {
return self[QCloudXMLDictionaryCommentsKey];
}
- (nullable NSString *)qcxml_nodeName {
return self[QCloudXMLDictionaryNodeNameKey];
}
- (id)qcxml_innerText {
id text = self[QCloudXMLDictionaryTextKey];
if ([text isKindOfClass:[NSArray class]]) {
return [text componentsJoinedByString:@"\n"];
} else {
return text;
}
}
- (NSString *)qcxml_innerXML {
NSMutableArray *nodes = [NSMutableArray array];
for (NSString *comment in [self qcxml_comments]) {
[nodes addObject:[NSString stringWithFormat:@"<!--%@-->", [comment QCXMLEncodedString]]];
}
NSDictionary *childNodes = [self qcxml_childNodes];
for (NSString *key in childNodes) {
[nodes addObject:[QCloudXMLDictionaryParser XMLStringForNode:childNodes[key] withNodeName:key]];
}
NSString *text = [self qcxml_innerText];
if (text) {
[nodes addObject:[text QCXMLEncodedString]];
}
return [nodes componentsJoinedByString:@"\n"];
}
- (NSString *)qcxml_XMLString {
if (self.count == 1 && ![self qcxml_nodeName]) {
// ignore outermost dictionary
return [self qcxml_innerXML];
} else {
return [QCloudXMLDictionaryParser XMLStringForNode:self withNodeName:[self qcxml_nodeName] ?: @"root"];
}
}
- (nullable NSArray *)qcxml_arrayValueForKeyPath:(NSString *)keyPath {
id value = [self valueForKeyPath:keyPath];
if (value && ![value isKindOfClass:[NSArray class]]) {
return @[ value ];
}
return value;
}
- (nullable NSString *)qcxml_stringValueForKeyPath:(NSString *)keyPath {
id value = [self valueForKeyPath:keyPath];
if ([value isKindOfClass:[NSArray class]]) {
value = ((NSArray *)value).firstObject;
}
if ([value isKindOfClass:[NSDictionary class]]) {
return [(NSDictionary *)value qcxml_innerText];
}
return value;
}
- (nullable NSDictionary<NSString *, id> *)qcxml_dictionaryValueForKeyPath:(NSString *)keyPath {
id value = [self valueForKeyPath:keyPath];
if ([value isKindOfClass:[NSArray class]]) {
value = [value count] ? value[0] : nil;
}
if ([value isKindOfClass:[NSString class]]) {
return @{ QCloudXMLDictionaryTextKey : value };
}
return value;
}
@end
@implementation NSString (QCloudXMLDictionary)
- (NSString *)QCXMLEncodedString {
return [[[[[self stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"] stringByReplacingOccurrencesOfString:@"<" withString:@"&lt;"]
stringByReplacingOccurrencesOfString:@">"
withString:@"&gt;"] stringByReplacingOccurrencesOfString:@"\""
withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"\'"
withString:@"&apos;"];
}
@end

View File

@@ -0,0 +1,148 @@
/***************************************************************************
*
* XMLWriter: An XML stream writer for iOS.
* This file is part of the XSWI library - https://skjolber.github.io/xswi
*
* Copyright (C) 2010 by Thomas Rørvik Skjølberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
****************************************************************************/
#import <Foundation/Foundation.h>
// xml stream writer
@protocol QCloudXMLStreamWriter
- (void)writeStartDocument;
- (void)writeStartDocumentWithVersion:(NSString *)version;
- (void)writeStartDocumentWithEncodingAndVersion:(NSString *)encoding version:(NSString *)version;
- (void)writeStartElement:(NSString *)localName;
- (void)writeEndElement; // automatic end element (mirrors previous start element at the same level)
- (void)writeEndElement:(NSString *)localName;
- (void)writeEmptyElement:(NSString *)localName;
- (void)writeEndDocument; // write any remaining end elements
- (void)writeAttribute:(NSString *)localName value:(NSString *)value;
- (void)writeCharacters:(NSString *)text;
- (void)writeComment:(NSString *)comment;
- (void)writeProcessingInstruction:(NSString *)target data:(NSString *)data;
- (void)writeCData:(NSString *)cdata;
// return the written xml string buffer
- (NSMutableString *)toString;
// return the written xml as data, set to the encoding used in the writeStartDocumentWithEncodingAndVersion method (UTF-8 per default)
- (NSData *)toData;
// flush the buffers, if any
- (void)flush;
// close the writer and buffers, if any
- (void)close;
- (void)setPrettyPrinting:(NSString *)indentation withLineBreak:(NSString *)lineBreak;
@end
// xml stream writer with namespace support
@protocol NSXMLStreamWriter <QCloudXMLStreamWriter>
- (void)writeStartElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName;
- (void)writeEndElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName;
- (void)writeEmptyElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName;
- (void)writeAttributeWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName value:(NSString *)value;
// set a namespace and prefix
- (void)setPrefix:(NSString *)prefix namespaceURI:(NSString *)namespaceURI;
// write (and set) a namespace and prefix
- (void)writeNamespace:(NSString *)prefix namespaceURI:(NSString *)namespaceURI;
// set the default namespace (empty prefix)
- (void)setDefaultNamespace:(NSString *)namespaceURI;
// write (and set) the default namespace
- (void)writeDefaultNamespace:(NSString *)namespaceURI;
- (NSString *)getPrefix:(NSString *)namespaceURI;
- (NSString *)getNamespaceURI:(NSString *)prefix;
@end
@interface QCloudXMLWriter : NSObject <NSXMLStreamWriter> {
// the current output buffer
NSMutableString *writer;
// the target encoding
NSString *encoding;
// the number current levels
int level;
// is the element open, i.e. the end bracket has not been written yet
BOOL openElement;
// does the element contain characters, cdata, comments
BOOL emptyElement;
// the element stack. one per element level
NSMutableArray *elementLocalNames;
NSMutableArray *elementNamespaceURIs;
// the namespace array. zero or more namespace attributes can be defined per element level
NSMutableArray *namespaceURIs;
// the namespace count. one per element level
NSMutableArray *namespaceCounts;
// the namespaces which have been written to the stream
NSMutableArray *namespaceWritten;
// mapping of namespace URI to prefix and visa versa. Corresponds in size to the namespaceURIs array.
NSMutableDictionary *namespaceURIPrefixMap;
NSMutableDictionary *prefixNamespaceURIMap;
// tag indentation
NSString *indentation;
// line break
NSString *lineBreak;
// if true, then write elements without children as <start /> instead of <start></start>
BOOL automaticEmptyElements;
}
@property (nonatomic, retain, readwrite) NSString *indentation;
@property (nonatomic, retain, readwrite) NSString *lineBreak;
@property (nonatomic, assign, readwrite) BOOL automaticEmptyElements;
@property (nonatomic, readonly) int level;
// helpful for formatting, special needs
// write linebreak, if any
- (void)writeLinebreak;
// write indentation, if any
- (void)writeIndentation;
// write end of start element, so that the start tag is complete
- (void)writeCloseStartElement;
// write any outstanding namespace declaration attributes in a start element
- (void)writeNamespaceAttributes;
// write escaped text to the stream
- (void)writeEscape:(NSString *)value;
// wrote unescaped text to the stream
- (void)write:(NSString *)value;
@end

View File

@@ -0,0 +1,760 @@
/***************************************************************************
*
* XMLWriter: An XML stream writer for iOS.
* This file is part of the XSWI library - https://skjolber.github.io/xswi
*
* Copyright (C) 2010 by Thomas Rørvik Skjølberg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
****************************************************************************/
#import "QCloudXMLWriter.h"
#define NSBOOL(_X_) ((_X_) ? (id)kCFBooleanTrue : (id)kCFBooleanFalse)
@interface QCloudXMLWriter (UtilityMethods)
// methods for internal use only
// pop the namespace stack, removing any namespaces which become out-of-scope
- (void)popNamespaceStack;
// push the namespace stack, denoting the namespaces whihch are in-scope
- (void)pushNamespaceStack;
// add namespace and local name to the top of the element stack
- (void)pushElementStack:(NSString *)namespaceURI localName:(NSString *)localName;
// remove the top member of the element stack
- (void)popElementStack;
// write close element, optionally as empty element
- (void)writeCloseElement:(BOOL)empty;
// write namespace attribute to stream
- (void)writeNamespaceToStream:(NSString *)prefix namespaceURI:(NSString *)namespaceURI;
// write a length of text to the stream with escaping
- (void)writeEscapeCharacters:(const UniChar *)characters length:(NSUInteger)length;
@end
static NSString *const EMPTY_STRING = @"";
static NSString *const XML_NAMESPACE_URI = @"http://www.w3.org/XML/1998/namespace";
static NSString *const XML_NAMESPACE_URI_PREFIX = @"xml";
static NSString *const XMLNS_NAMESPACE_URI = @"http://www.w3.org/2000/xmlns/";
static NSString *const XMLNS_NAMESPACE_URI_PREFIX = @"xmlns";
static NSString *const XSI_NAMESPACE_URI = @"http://www.w3.org/2001/XMLSchema/";
static NSString *const XSI_NAMESPACE_URI_PREFIX = @"xsi";
@implementation QCloudXMLWriter
@synthesize automaticEmptyElements, indentation, lineBreak, level;
- (QCloudXMLWriter *)init {
self = [super init];
if (self != nil) {
// intialize variables
writer = [[NSMutableString alloc] init];
level = 0;
openElement = NO;
emptyElement = NO;
elementLocalNames = [[NSMutableArray alloc] init];
elementNamespaceURIs = [[NSMutableArray alloc] init];
namespaceURIs = [[NSMutableArray alloc] init];
namespaceCounts = [[NSMutableArray alloc] init];
namespaceWritten = [[NSMutableArray alloc] init];
namespaceURIPrefixMap = [[NSMutableDictionary alloc] init];
prefixNamespaceURIMap = [[NSMutableDictionary alloc] init];
// load default custom behaviour
automaticEmptyElements = YES;
// setup default xml namespaces. assume both are previously known.
[namespaceCounts addObject:[NSNumber numberWithInt:2]];
[self setPrefix:XML_NAMESPACE_URI_PREFIX namespaceURI:XML_NAMESPACE_URI];
[self setPrefix:XMLNS_NAMESPACE_URI_PREFIX namespaceURI:XMLNS_NAMESPACE_URI];
}
return self;
}
- (void)pushNamespaceStack {
// step namespace count - add the current namespace count
NSNumber *previousCount = [namespaceCounts lastObject];
if ([namespaceURIs count] == [previousCount unsignedIntegerValue]) {
// the count is still the same
[namespaceCounts addObject:previousCount];
} else {
// the count has changed, save the it
NSNumber *count = [NSNumber numberWithInt:(int)[namespaceURIs count]];
[namespaceCounts addObject:count];
}
}
- (void)writeNamespaceAttributes {
if (openElement) {
// write namespace attributes in the namespace stack
NSNumber *previousCount = [namespaceCounts lastObject];
for (NSUInteger i = [previousCount unsignedIntegerValue]; i < [namespaceURIs count]; i++) {
// did we already write this namespace?
id written = [namespaceWritten objectAtIndex:i];
if (written == NSBOOL(NO)) {
// write namespace
NSString *namespaceURI = [namespaceURIs objectAtIndex:i];
NSString *prefix = [namespaceURIPrefixMap objectForKey:namespaceURI];
[self writeNamespaceToStream:prefix namespaceURI:namespaceURI];
[namespaceWritten replaceObjectAtIndex:i withObject:NSBOOL(YES)];
} else {
// already written namespace
}
}
} else {
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]);
}
}
- (void)popNamespaceStack {
// step namespaces one level down
if ([namespaceCounts lastObject] != [namespaceCounts objectAtIndex:([namespaceCounts count] - 2)]) {
// remove namespaces which now are out of scope, i.e. between the current and the previus count
NSNumber *previousCount = [namespaceCounts lastObject];
NSNumber *currentCount = [namespaceCounts objectAtIndex:([namespaceCounts count] - 2)];
for (NSUInteger i = [previousCount unsignedIntegerValue] - 1; i >= [currentCount unsignedIntegerValue]; i--) {
NSString *removedNamespaceURI = [namespaceURIs objectAtIndex:i];
NSString *removedPrefix = [namespaceURIPrefixMap objectForKey:removedNamespaceURI];
[prefixNamespaceURIMap removeObjectForKey:removedPrefix];
[namespaceURIPrefixMap removeObjectForKey:removedNamespaceURI];
[namespaceURIs removeLastObject];
[namespaceWritten removeLastObject];
}
} else {
// not necessary to remove any namespaces
}
[namespaceCounts removeLastObject];
}
- (void)setPrefix:(NSString *)prefix namespaceURI:(NSString *)namespaceURI {
if (!namespaceURI) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Namespace cannot be NULL" userInfo:NULL]);
}
if (!prefix) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Prefix cannot be NULL" userInfo:NULL]);
}
if ([namespaceURIPrefixMap objectForKey:namespaceURI]) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Name namespace %@ has already been set", namespaceURI]
userInfo:NULL]);
}
if ([prefixNamespaceURIMap objectForKey:prefix]) {
// raise exception
if ([prefix length]) {
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Prefix %@ has already been set", prefix]
userInfo:NULL]);
} else {
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Default namespace has already been set" userInfo:NULL]);
}
}
// increase the namespaces and add prefix mapping
[namespaceURIs addObject:namespaceURI];
[namespaceURIPrefixMap setObject:prefix forKey:namespaceURI];
[prefixNamespaceURIMap setObject:namespaceURI forKey:prefix];
if (openElement) { // write the namespace now
[self writeNamespaceToStream:prefix namespaceURI:namespaceURI];
[namespaceWritten addObject:NSBOOL(YES)];
} else {
// write the namespace as the next start element is closed
[namespaceWritten addObject:NSBOOL(NO)];
}
}
- (NSString *)getPrefix:(NSString *)namespaceURI {
return [namespaceURIPrefixMap objectForKey:namespaceURI];
}
- (void)pushElementStack:(NSString *)namespaceURI localName:(NSString *)localName {
// save for end elements
[elementLocalNames addObject:localName];
if (namespaceURI) {
[elementNamespaceURIs addObject:namespaceURI];
} else {
[elementNamespaceURIs addObject:EMPTY_STRING];
}
}
- (void)popElementStack {
// remove element traces
[elementNamespaceURIs removeLastObject];
[elementLocalNames removeLastObject];
}
- (void)writeStartDocument {
[self writeStartDocumentWithEncodingAndVersion:NULL version:NULL];
}
- (void)writeStartDocumentWithVersion:(NSString *)version {
[self writeStartDocumentWithEncodingAndVersion:NULL version:version];
}
- (void)writeStartDocumentWithEncodingAndVersion:(NSString *)aEncoding version:(NSString *)version {
if ([writer length] != 0) {
// raise exception - Starting document which is not empty
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Document has already been started" userInfo:NULL]);
} else {
[self write:@"<?xml version=\""];
if (version) {
[self write:version];
} else {
// default to 1.0
[self write:@"1.0"];
}
[self write:@"\""];
if (aEncoding) {
[self write:@" encoding=\""];
[self write:aEncoding];
[self write:@"\""];
encoding = aEncoding;
}
[self write:@" ?>"];
}
}
- (void)writeEndDocument {
while (level > 0) {
[self writeEndElement];
}
}
- (void)writeStartElement:(NSString *)localName {
[self writeStartElementWithNamespace:NULL localName:localName];
}
- (void)writeCloseStartElement {
if (openElement) {
[self writeCloseElement:NO];
} else {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]);
}
}
- (void)writeCloseElement:(BOOL)empty {
[self writeNamespaceAttributes];
[self pushNamespaceStack];
if (empty) {
[self write:@" />"];
} else {
[self write:@">"];
}
openElement = NO;
}
- (void)writeEndElement:(NSString *)localName {
[self writeEndElementWithNamespace:NULL localName:localName];
}
- (void)writeEndElement {
if (openElement && automaticEmptyElements) {
// go for <START />
[self writeCloseElement:YES]; // write empty end element
[self popNamespaceStack];
[self popElementStack];
emptyElement = YES;
openElement = NO;
level -= 1;
} else {
NSString *namespaceURI = [elementNamespaceURIs lastObject];
NSString *localName = [elementLocalNames lastObject];
if (namespaceURI == EMPTY_STRING) {
[self writeEndElementWithNamespace:NULL localName:localName];
} else {
[self writeEndElementWithNamespace:namespaceURI localName:localName];
}
}
}
- (void)writeStartElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName {
if (openElement) {
[self writeCloseElement:NO];
}
[self writeLinebreak];
[self writeIndentation];
[self write:@"<"];
if (namespaceURI) {
NSString *prefix = [namespaceURIPrefixMap objectForKey:namespaceURI];
if (!prefix) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI]
userInfo:NULL]);
}
if ([prefix length]) {
[self write:prefix];
[self write:@":"];
}
}
[self write:localName];
[self pushElementStack:namespaceURI localName:localName];
openElement = YES;
emptyElement = YES;
level += 1;
}
- (void)writeEndElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName {
if (level <= 0) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"Cannot write more end elements than start elements." userInfo:NULL]);
}
level -= 1;
if (openElement) {
// go for <START><END>
[self writeCloseElement:NO];
} else {
if (emptyElement) {
// go for linebreak + indentation + <END>
[self writeLinebreak];
[self writeIndentation];
} else {
// go for <START>characters<END>
}
}
// write standard end element
[self write:@"</"];
if (namespaceURI) {
NSString *prefix = [namespaceURIPrefixMap objectForKey:namespaceURI];
if (!prefix) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI]
userInfo:NULL]);
}
if ([prefix length]) {
[self write:prefix];
[self write:@":"];
}
}
[self write:localName];
[self write:@">"];
[self popNamespaceStack];
[self popElementStack];
emptyElement = YES;
openElement = NO;
}
- (void)writeEmptyElement:(NSString *)localName {
if (openElement) {
[self writeCloseElement:NO];
}
[self writeLinebreak];
[self writeIndentation];
[self write:@"<"];
[self write:localName];
[self write:@" />"];
emptyElement = YES;
openElement = NO;
}
- (void)writeEmptyElementWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName {
if (openElement) {
[self writeCloseElement:NO];
}
[self writeLinebreak];
[self writeIndentation];
[self write:@"<"];
if (namespaceURI) {
NSString *prefix = [namespaceURIPrefixMap objectForKey:namespaceURI];
if (!prefix) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI]
userInfo:NULL]);
}
if ([prefix length]) {
[self write:prefix];
[self write:@":"];
}
}
[self write:localName];
[self write:@" />"];
emptyElement = YES;
openElement = NO;
}
- (void)writeAttribute:(NSString *)localName value:(NSString *)value {
[self writeAttributeWithNamespace:NULL localName:localName value:value];
}
- (void)writeAttributeWithNamespace:(NSString *)namespaceURI localName:(NSString *)localName value:(NSString *)value {
if (openElement) {
[self write:@" "];
if (namespaceURI) {
NSString *prefix = [namespaceURIPrefixMap objectForKey:namespaceURI];
if (!prefix) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Unknown namespace URI %@", namespaceURI]
userInfo:NULL]);
}
if ([prefix length]) {
[self write:prefix];
[self write:@":"];
}
}
[self write:localName];
[self write:@"=\""];
[self writeEscape:value];
[self write:@"\""];
} else {
// raise expection
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]);
}
}
- (void)setDefaultNamespace:(NSString *)namespaceURI {
[self setPrefix:EMPTY_STRING namespaceURI:namespaceURI];
}
- (void)writeNamespace:(NSString *)prefix namespaceURI:(NSString *)namespaceURI {
if (openElement) {
[self setPrefix:prefix namespaceURI:namespaceURI];
} else {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]);
}
}
- (void)writeDefaultNamespace:(NSString *)namespaceURI {
[self writeNamespace:EMPTY_STRING namespaceURI:namespaceURI];
}
- (NSString *)getNamespaceURI:(NSString *)prefix {
return [prefixNamespaceURIMap objectForKey:prefix];
}
- (void)writeNamespaceToStream:(NSString *)prefix namespaceURI:(NSString *)namespaceURI {
if (openElement) { // write the namespace now
[self write:@" "];
NSString *xmlnsPrefix = [self getPrefix:XMLNS_NAMESPACE_URI];
if (!xmlnsPrefix) {
// raise exception
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Cannot declare namespace without namespace %@", XMLNS_NAMESPACE_URI]
userInfo:NULL]);
}
[self write:xmlnsPrefix]; // xmlns
if ([prefix length]) {
// write xmlns:prefix="namespaceURI" attribute
[self write:@":"]; // colon
[self write:prefix]; // prefix
} else {
// write xmlns="namespaceURI" attribute
}
[self write:@"=\""];
[self writeEscape:namespaceURI];
[self write:@"\""];
} else {
@throw([NSException exceptionWithName:@"XMLWriterException" reason:@"No open start element" userInfo:NULL]);
}
}
- (void)writeCharacters:(NSString *)text {
if (openElement) {
[self writeCloseElement:NO];
}
[self writeEscape:text];
emptyElement = NO;
}
- (void)writeComment:(NSString *)comment {
if (openElement) {
[self writeCloseElement:NO];
}
[self write:@"<!--"];
[self write:comment]; // no escape
[self write:@"-->"];
emptyElement = NO;
}
- (void)writeProcessingInstruction:(NSString *)target data:(NSString *)data {
if (openElement) {
[self writeCloseElement:NO];
}
[self write:@"<![CDATA["];
[self write:target]; // no escape
[self write:@" "];
[self write:data]; // no escape
[self write:@"]]>"];
emptyElement = NO;
}
- (void)writeCData:(NSString *)cdata {
if (openElement) {
[self writeCloseElement:NO];
}
[self write:@"<![CDATA["];
[self write:cdata]; // no escape
[self write:@"]]>"];
emptyElement = NO;
}
- (void)write:(NSString *)value {
[writer appendString:value];
}
- (void)writeEscape:(NSString *)value {
if (!value) {
return;
}
const UniChar *characters = CFStringGetCharactersPtr((CFStringRef)value);
if (characters) {
// main flow
[self writeEscapeCharacters:characters length:[value length]];
} else {
// we need to read/copy the characters for some reason, from the docs of CFStringGetCharactersPtr:
// A pointer to a buffer of Unicode character or NULL if the internal storage of the CFString does not allow this to be returned efficiently.
// Whether or not this function returns a valid pointer or NULL depends on many factors, all of which depend on how the string was created and
// its properties. In addition, the function result might change between different releases and on different platforms. So do not count on
// receiving a non- NULL result from this function under any circumstances (except when the object is created with
// CFStringCreateMutableWithExternalCharactersNoCopy).
// we dont need the whole data length at once
NSMutableData *data = [NSMutableData dataWithLength:256 * sizeof(UniChar)];
if (!data) {
// raise exception - no more memory
@throw([NSException exceptionWithName:@"XMLWriterException"
reason:[NSString stringWithFormat:@"Could not allocate data buffer of %i unicode characters", 256]
userInfo:NULL]);
}
NSUInteger count = 0;
do {
NSUInteger length;
if (count + 256 < [value length]) {
length = 256;
} else {
length = [value length] - count;
}
[value getCharacters:[data mutableBytes] range:NSMakeRange(count, length)];
[self writeEscapeCharacters:[data bytes] length:length];
count += length;
} while (count < [value length]);
// buffers autorelease
}
}
- (void)writeEscapeCharacters:(const UniChar *)characters length:(NSUInteger)length {
NSUInteger rangeStart = 0;
CFIndex rangeLength = 0;
for (NSUInteger i = 0; i < length; i++) {
UniChar c = characters[i];
if (c <= 0xd7ff) {
if (c >= 0x20) {
switch (c) {
case 34: {
// write range if any
if (rangeLength) {
CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength);
}
[self write:@"&quot;"];
break;
}
// quot
case 38: {
// write range if any
if (rangeLength) {
CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength);
}
[self write:@"&amp;"];
break;
}
// amp;
case 60: {
// write range if any
if (rangeLength) {
CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength);
}
[self write:@"&lt;"];
break;
}
// lt;
case 62: {
// write range if any
if (rangeLength) {
CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength);
}
[self write:@"&gt;"];
break;
}
// gt;
default: {
// valid
rangeLength++;
// note: we dont need to escape char 39 for &apos; because we use double quotes exclusively
continue;
}
}
// set range start to next
rangeLength = 0;
rangeStart = i + 1;
} else {
if (c == '\n' || c == '\r' || c == '\t') {
// valid;
rangeLength++;
continue;
} else {
// invalid, skip
}
}
} else if (c <= 0xFFFD) {
// valid
rangeLength++;
continue;
} else {
// invalid, skip
}
// write range if any
if (rangeLength) {
CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength);
}
// set range start to next
rangeLength = 0;
rangeStart = i + 1;
}
// write range if any
if (rangeLength) {
// main flow will probably write all characters here
CFStringAppendCharacters((CFMutableStringRef)writer, characters + rangeStart, rangeLength);
}
}
- (void)writeLinebreak {
if (lineBreak) {
[self write:lineBreak];
}
}
- (void)writeIndentation {
if (indentation) {
for (int i = 0; i < level; i++) {
[self write:indentation];
}
}
}
- (void)flush {
// do nothing
}
- (void)close {
// do nothing
}
- (NSMutableString *)toString {
return writer;
}
- (NSData *)toData {
if (encoding) {
return [writer dataUsingEncoding:CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)encoding))
allowLossyConversion:NO];
} else {
return [writer dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];
}
}
- (void)setPrettyPrinting:(NSString *)aIndentation withLineBreak:(NSString *)aLineBreak {
self.indentation = aIndentation;
self.lineBreak = aLineBreak;
}
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudDomain.h
// TestHttps
//
// Created by tencent on 16/2/17.
// Copyright © 2016年 dzpqzb. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface QCloudDomain : NSObject
@property (nonatomic, strong, readonly) NSString *domain;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
- (instancetype)initWithDomain:(NSString *)domain;
@end

View File

@@ -0,0 +1,20 @@
//
// QCloudDomain.m
// TestHttps
//
// Created by tencent on 16/2/17.
// Copyright © 2016 dzpqzb. All rights reserved.
//
#import "QCloudDomain.h"
@implementation QCloudDomain
- (instancetype)initWithDomain:(NSString *)domain {
self = [super init];
if (!self) {
return self;
}
_domain = domain;
return self;
}
@end

View File

@@ -0,0 +1,16 @@
//
// QCloudHosts.h
// TestHttps
//
// Created by tencent on 16/2/17.
// Copyright © 2016年 dzpqzb. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface QCloudHosts : NSObject
- (void)putDomain:(NSString *)domain ip:(NSString *)ip;
- (NSArray *)queryIPForDomain:(NSString *)domain;
- (BOOL)checkContainsIP:(NSString *)ip;
- (void)clean;
@end

View File

@@ -0,0 +1,85 @@
//
// QCloudHosts.m
// TestHttps
//
// Created by tencent on 16/2/17.
// Copyright © 2016 dzpqzb. All rights reserved.
//
#import "QCloudHosts.h"
#import "QCloudDomain.h"
@implementation QCloudHosts {
NSMutableDictionary *_cache;
dispatch_queue_t _hostChangeQueue;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_hostChangeQueue = dispatch_queue_create("com.tencent.qcloud.host.resolve", DISPATCH_QUEUE_CONCURRENT);
_cache = [NSMutableDictionary new];
return self;
}
- (void)putDomain:(NSString *)domain ip:(NSString *)ip {
#ifdef DEBUG
NSParameterAssert(domain);
NSParameterAssert(ip);
#else
if (!domain) {
return;
}
if (!ip) {
return;
}
#endif
dispatch_barrier_async(_hostChangeQueue, ^{
NSMutableArray *array = [self->_cache objectForKey:domain];
if (!array) {
array = [NSMutableArray new];
}
if (![array containsObject:ip]) {
[array addObject:ip];
}
self->_cache[domain] = array;
});
}
- (NSArray *)queryIPForDomain:(NSString *)domain {
__block NSArray *array = nil;
dispatch_sync(_hostChangeQueue, ^(void) {
array = [[self->_cache objectForKey:domain] copy];
});
return array;
}
- (BOOL)checkContainsIP:(NSString *)ip {
if (!ip) {
return NO;
}
__block BOOL contained = NO;
dispatch_sync(_hostChangeQueue, ^{
for (NSArray *array in self->_cache.allValues) {
for (NSString *cachedIP in array) {
if ([cachedIP isEqualToString:ip]) {
contained = YES;
break;
}
}
if (contained) {
break;
}
}
});
return contained;
}
- (void)clean {
dispatch_barrier_async(_hostChangeQueue, ^{
[self->_cache removeAllObjects];
});
}
@end

View File

@@ -0,0 +1,78 @@
//
// QCloudHttpDNS.h
// TestHttps
//
// Created by tencent on 16/2/17.
// Copyright © 2016年 dzpqzb. All rights reserved.
//
#import <Foundation/Foundation.h>
extern NSString *const kQCloudHttpDNSCacheReady;
extern NSString *const kQCloudHttpDNSHost;
@class QCloudHosts;
@protocol QCloudHTTPDNSProtocol <NSObject>
@required
/**
解析domain返回对应的ip地址。注意ip地址需要是有效的形式(xxx.xxx.xxx.xxx)否则会导致出错
@param domain 需要解析的域名
@return ip地址
*/
- (NSString *)resolveDomain:(NSString *)domain;
@end
@interface QCloudHttpDNS : NSObject
@property (nonatomic, strong, readonly) QCloudHosts *hosts;
/**
实现自定义解析ip的代理当在记录里查询不到对应的host时会向代理去再次请求解析。
*/
@property (nonatomic, weak) id<QCloudHTTPDNSProtocol> delegate;
+ (instancetype)shareDNS;
/**
对于跟定的域名进行DNS缓存操作
@param domain 需要缓存IP的域名
@param error 如果过程出错,该字段表示错误信息
@return 是否解析DNS成功
*/
- (BOOL)resolveDomain:(NSString *)domain error:(NSError **)error;
/**
对于URLRequest进行IP重定向如果改URLRequest原始指向的URL中的host对应的IP已经被解析了则进行重定向操作如果没有直接返回原始URLReqest
@param request 需要被重定向的URLRequest
@return 如果改URLRequest原始指向的URL中的host对应的IP已经被解析了则进行重定向操作如果没有直接返回原始URLReqest
*/
- (NSMutableURLRequest *)resolveURLRequestIfCan:(NSMutableURLRequest *)request;
/**
判断一个IP是否是被解析出来且被信任的
@param ip 需要进行判断的IP
@return 是否被信任
*/
- (BOOL)isTrustIP:(NSString *)ip;
/**
手动添加一条hosts记录
@param ip ip地址
@param domain 域名
*/
- (void)setIp:(NSString *)ip forDomain:(NSString *)domain;
- (NSString *)queryIPForHost:(NSString *)host;
- (NSArray *)queryIPsForHost:(NSString *)host;
/**
Ping IP列表尝试拿到一个可用的IP
*/
- (NSString *)findHealthyIpFor:(NSString *)host;
/**
获取host对应的ip列表
*/
- (void)prepareFetchIPListForHost:(NSString *)host port:(NSString *)port;
@end

View File

@@ -0,0 +1,271 @@
//
// QCloudHttpDNS.m
// TestHttps
//
// Created by tencent on 16/2/17.
// Copyright © 2016 dzpqzb. All rights reserved.
//
#import "QCloudHttpDNS.h"
#import "QCloudHosts.h"
#import "QCloudLogger.h"
#import "NSError+QCloudNetworking.h"
#import "QCloudPingTester.h"
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#import "QCloudThreadSafeMutableDictionary.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#define IP_ADDR_IPv4 @"&&ipv4"
#define IP_ADDR_IPv6 @"&&ipv6"
NSString *const kQCloudHttpDNSCacheReady = @"kQCloudHttpDNSCacheReady";
NSString *const kQCloudHttpDNSHost = @"host";
BOOL QCloudCheckIPVaild(NSString *ip) {
return YES;
}
@interface QCloudHttpDNS () <WHPingDelegate>
@property (nonatomic, strong) NSMutableArray<QCloudPingTester *> *pingTesters;
@end
@implementation QCloudHttpDNS {
QCloudHosts *_hosts;
QCloudThreadSafeMutableDictionary *_ipHostMap;
;
}
+ (instancetype)shareDNS {
static QCloudHttpDNS *dns = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dns = [QCloudHttpDNS new];
});
return dns;
}
- (QCloudHosts *)hosts {
return _hosts;
}
- (instancetype)init {
self = [super init];
if (!self) {
return self;
}
_hosts = [[QCloudHosts alloc] init];
_ipHostMap = [[QCloudThreadSafeMutableDictionary alloc] init];
_pingTesters = [NSMutableArray array];
return self;
}
- (BOOL)resolveDomain:(NSString *)domain error:(NSError **)error {
NSString *ip;
if (self.delegate && [self.delegate respondsToSelector:@selector(resolveDomain:)]) {
ip = [self.delegate resolveDomain:domain];
}
if (!ip) {
QCloudLogDebugE(@"HTTP",@"Cannot resolve domain %@", domain);
if (NULL != error) {
*error = [NSError qcloud_errorWithCode:kCFURLErrorDNSLookupFailed
message:[NSString stringWithFormat:@"NetworkException:无法解析域名 %@", domain]];
}
return NO;
}
if (QCloudCheckIPVaild(ip)) {
[_hosts putDomain:domain ip:[ip stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" "]]];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kQCloudHttpDNSCacheReady object:nil userInfo:@{ kQCloudHttpDNSHost : domain }];
return YES;
}
- (NSString *)queryIPForHost:(NSString *)host {
NSArray *hosts = [_hosts queryIPForDomain:host];
// always use the last(lastest) one
if (hosts.count) {
return hosts.lastObject;
}
return nil;
}
- (NSArray *)queryIPsForHost:(NSString *)host {
NSArray *ips = [_ipHostMap objectForKey:host];
NSMutableArray * ipStrs = [NSMutableArray new];
for (NSString *ip in ips) {
[ipStrs addObject:[NSString stringWithFormat:@"%@/%@",host,ip]];
}
return ipStrs.copy;
}
- (NSMutableURLRequest *)resolveURLRequestIfCan:(NSMutableURLRequest *)request {
if (!request) {
return request;
}
NSString *host = request.URL.host;
NSString *ip = [self queryIPForHost:host];
// Give it second chance to reslove domain by itself
if (!ip) {
NSError *resolveError;
[self resolveDomain:request.URL.host error:&resolveError];
}
ip = [self queryIPForHost:host];
if (!ip) {
return request;
}
NSString *url = request.URL.absoluteString;
NSRange range = [url rangeOfString:host];
NSString *originHost = request.URL.host;
if (range.location != NSNotFound && range.length > 0) {
url = [url stringByReplacingOccurrencesOfString:host withString:ip options:0 range:range];
NSMutableURLRequest *mReq = [request mutableCopy];
mReq.URL = [NSURL URLWithString:url];
[mReq setValue:originHost forHTTPHeaderField:@"Host"];
return mReq;
} else {
return request;
}
}
- (void)setIp:(NSString *)ip forDomain:(NSString *)domain {
if (QCloudCheckIPVaild(ip)) {
[_hosts putDomain:domain ip:ip];
}
}
- (BOOL)isTrustIP:(NSString *)ip {
NSString *regex = @"\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b";
NSPredicate *predictate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];
BOOL containsIP = [predictate evaluateWithObject:ip];
if (!containsIP) {
return NO;
}
return [_hosts checkContainsIP:ip];
}
- (NSString *)findHealthyIpFor:(NSString *)host {
NSArray *ipList = [_ipHostMap objectForKey:host];
if (ipList.count) {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self pingIp:ipList.lastObject host:host fulfil:sema];
dispatch_wait(sema, DISPATCH_TIME_FOREVER);
return [self queryIPForHost:host];
}
return nil;
}
- (void)pingIp:(NSString *)ip host:(NSString *)host fulfil:(dispatch_semaphore_t)sema {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *ipAdd;
if ([ip hasSuffix:IP_ADDR_IPv4]) {
ipAdd = [ip stringByReplacingOccurrencesOfString:IP_ADDR_IPv4 withString:@""];
} else if ([ip hasSuffix:IP_ADDR_IPv6]) {
ipAdd = [ip stringByReplacingOccurrencesOfString:IP_ADDR_IPv6 withString:@""];
}
QCloudPingTester *pingTester = [[QCloudPingTester alloc] initWithIp:ipAdd host:host fulfil:sema];
pingTester.delegate = self;
[self->_pingTesters addObject:pingTester];
[pingTester startPing];
});
}
- (void)pingTester:(QCloudPingTester *)pingTester didPingSucccessWithTime:(float)time withError:(NSError *)error {
if (!error) {
QCloudLogInfoPB(@"HTTP",@"ping的延迟是--->%f", time);
[pingTester stopPing];
[self setIp:pingTester.ip forDomain:pingTester.host];
dispatch_semaphore_signal(pingTester.sema);
} else {
QCloudLogInfoPB(@"HTTP",@"网络不通过ip[%@]", pingTester.ip);
[pingTester stopPing];
NSMutableArray *ipList = [[_ipHostMap objectForKey:pingTester.host] mutableCopy];
if (ipList.count) {
[ipList removeLastObject];
}
[_ipHostMap setObject:ipList forKey:pingTester.host];
if (ipList.count) {
[self pingIp:ipList.lastObject host:pingTester.host fulfil:pingTester.sema];
} else {
dispatch_semaphore_signal(pingTester.sema);
}
}
[_pingTesters removeObject:pingTester];
}
- (void)prepareFetchIPListForHost:(NSString *)host port:(NSString *)port {
NSArray *list = [_ipHostMap objectForKey:host];
if (![_ipHostMap objectForKey:host] || !list.count) {
list = getIPListFromToHost(host.UTF8String, port.UTF8String);
if (list) {
[_ipHostMap setObject:list forKey:host];
}
}
}
NSArray *getIPListFromToHost(const char *mHost, const char *mPort) {
NSMutableArray *ipList = [NSMutableArray array];
if (nil == mHost)
return NULL;
const char *newChar = "No";
//
struct addrinfo *res0;
//
struct addrinfo hints;
//
struct addrinfo *res;
int n;
//
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_DEFAULT;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((n = getaddrinfo(mHost, "http", &hints, &res0)) != 0) {
QCloudLogInfoPB(@"HTTP",@"getaddrinfo error: %s", gai_strerror(n));
return NULL;
}
struct sockaddr_in6 *addr6;
struct sockaddr_in *addr;
NSString *NewStr = NULL;
char ipbuf[32];
for (res = res0; res; res = res->ai_next) {
if (res->ai_family == AF_INET6) {
addr6 = (struct sockaddr_in6 *)res->ai_addr;
newChar = inet_ntop(AF_INET6, &addr6->sin6_addr, ipbuf, sizeof(ipbuf));
if(newChar == NULL){
continue;
}
NSString *TempA = [[NSString alloc] initWithCString:(const char *)newChar encoding:NSASCIIStringEncoding];
NSString *TempB = [NSString stringWithUTF8String:IP_ADDR_IPv6.UTF8String];
NewStr = [TempA stringByAppendingString:TempB];
} else {
addr = (struct sockaddr_in *)res->ai_addr;
newChar = inet_ntop(AF_INET, &addr->sin_addr, ipbuf, sizeof(ipbuf));
if(newChar == NULL){
continue;
}
NSString *TempA = [[NSString alloc] initWithCString:(const char *)newChar encoding:NSASCIIStringEncoding];
NSString *TempB = [NSString stringWithUTF8String:IP_ADDR_IPv4.UTF8String];
NewStr = [TempA stringByAppendingString:TempB];
}
[ipList addObject:NewStr];
QCloudLogInfoPB(@"HTTP",@"host[%s] ipList:%@", mHost, ipList);
}
if(res0!=NULL){
freeaddrinfo(res0);
}
return ipList;
}
@end
#pragma GCC diagnostic pop

View File

@@ -0,0 +1,40 @@
//
// WHPingTester.h
// BigVPN
//
// Created by wanghe on 2017/5/11.
// Copyright © 2017年 wanghe. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "QCloudSimplePing.h"
@class QCloudPingTester;
@protocol WHPingDelegate <NSObject>
@optional
- (void)pingTester:(QCloudPingTester *)pingTest didPingSucccessWithTime:(float)time withError:(NSError *)error;
@end
@interface QCloudPingTester : NSObject <SimplePingDelegate>
@property (nonatomic, weak, readwrite) id<WHPingDelegate> delegate;
@property (nonatomic, readonly) NSString *ip;
@property (nonatomic, readonly) NSString *host;
@property (nonatomic) dispatch_semaphore_t sema;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithIp:(NSString *)ip host:(NSString *)host fulfil:(dispatch_semaphore_t)sema NS_DESIGNATED_INITIALIZER;
- (void)startPing;
- (void)stopPing;
@end
typedef NS_ENUM(NSUInteger, WHPingStatus) {
WHPingStatusSending = 0 << 0,
WHPingStatusTimeout = 1 << 1,
WHPingStatusSended = 2 << 2,
};
@interface QCloudPingItem : NSObject
//@property(nonatomic, assign) WHPingStatus status;
@property (nonatomic, assign) uint16_t sequence;
@end

View File

@@ -0,0 +1,108 @@
//
// WHPingTester.m
// BigVPN
//
// Created by wanghe on 2017/5/11.
// Copyright © 2017 wanghe. All rights reserved.
//
#import "QCloudPingTester.h"
#import "QCloudCore.h"
@interface QCloudPingTester () <SimplePingDelegate> {
NSTimer *_timer;
NSDate *_beginDate;
}
@property (nonatomic, strong) QCloudSimplePing *simplePing;
@property (nonatomic) NSString *ip;
@property (nonatomic) NSString *host;
@property (nonatomic, strong) NSMutableArray<QCloudPingItem *> *pingItems;
@end
@implementation QCloudPingTester
- (instancetype)initWithIp:(NSString *)ip host:(NSString *)host fulfil:(dispatch_semaphore_t)sema {
if (self = [super init]) {
self.ip = ip;
self.host = host;
self.sema = sema;
self.simplePing = [[QCloudSimplePing alloc] initWithHostName:ip];
self.simplePing.delegate = self;
self.simplePing.addressStyle = SimplePingAddressStyleAny;
self.pingItems = [NSMutableArray new];
}
return self;
}
- (void)startPing {
[self.simplePing start];
}
- (void)stopPing {
[_timer invalidate];
_timer = nil;
[self.simplePing stop];
}
- (void)actionTimer {
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(sendPingData) userInfo:nil repeats:YES];
}
- (void)sendPingData {
[self.simplePing sendPingWithData:nil];
}
#pragma mark Ping Delegate
- (void)simplePing:(QCloudSimplePing *)pinger didStartWithAddress:(NSData *)address {
[self actionTimer];
}
- (void)simplePing:(QCloudSimplePing *)pinger didFailWithError:(NSError *)error {
QCloudLogError(@"[ERROR]ping失败,error: %@", error);
}
- (void)simplePing:(QCloudSimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {
QCloudPingItem *item = [QCloudPingItem new];
item.sequence = sequenceNumber;
[self.pingItems addObject:item];
_beginDate = [NSDate date];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if ([self.pingItems containsObject:item]) {
QCloudLogError(@"[ERROR]超时---->");
[self.pingItems removeObject:item];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(pingTester:didPingSucccessWithTime:withError:)]) {
[self.delegate pingTester:self didPingSucccessWithTime:0 withError:[NSError errorWithDomain:NSURLErrorDomain code:111 userInfo:nil]];
}
}
});
}
- (void)simplePing:(QCloudSimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error {
QCloudLogError(@"[ERROR]发包失败:%@", error);
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(pingTester:didPingSucccessWithTime:withError:)]) {
[self.delegate pingTester:self didPingSucccessWithTime:0 withError:error];
}
}
- (void)simplePing:(QCloudSimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber {
float delayTime = [[NSDate date] timeIntervalSinceDate:_beginDate] * 1000;
[self.pingItems enumerateObjectsUsingBlock:^(QCloudPingItem *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
if (obj.sequence == sequenceNumber) {
[self.pingItems removeObject:obj];
}
}];
if (self.delegate != nil && [self.delegate respondsToSelector:@selector(pingTester:didPingSucccessWithTime:withError:)]) {
[self.delegate pingTester:self didPingSucccessWithTime:delayTime withError:nil];
}
}
- (void)simplePing:(QCloudSimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet {
}
@end
@implementation QCloudPingItem
@end

View File

@@ -0,0 +1,275 @@
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this samples licensing information
Abstract:
An object wrapper around the low-level BSD Sockets ping function.
*/
@import Foundation;
#include <AssertMacros.h> // for __Check_Compile_Time
#include <sys/socket.h>
NS_ASSUME_NONNULL_BEGIN
@protocol SimplePingDelegate;
/*! Controls the IP address version used by SimplePing instances.
*/
typedef NS_ENUM(NSInteger, SimplePingAddressStyle) {
SimplePingAddressStyleAny, ///< Use the first IPv4 or IPv6 address found; the default.
SimplePingAddressStyleICMPv4, ///< Use the first IPv4 address found.
SimplePingAddressStyleICMPv6 ///< Use the first IPv6 address found.
};
/*! An object wrapper around the low-level BSD Sockets ping function.
* \details To use the class create an instance, set the delegate and call `-start`
* to start the instance on the current run loop. If things go well you'll soon get the
* `-simplePing:didStartWithAddress:` delegate callback. From there you can can call
* `-sendPingWithData:` to send a ping and you'll receive the
* `-simplePing:didReceivePingResponsePacket:sequenceNumber:` and
* `-simplePing:didReceiveUnexpectedPacket:` delegate callbacks as ICMP packets arrive.
*
* The class can be used from any thread but the use of any single instance must be
* confined to a specific thread and that thread must run its run loop.
*/
@interface QCloudSimplePing : NSObject
- (instancetype)init NS_UNAVAILABLE;
/*! Initialise the object to ping the specified host.
* \param hostName The DNS name of the host to ping; an IPv4 or IPv6 address in string form will
* work here.
* \returns The initialised object.
*/
- (instancetype)initWithHostName:(NSString *)hostName NS_DESIGNATED_INITIALIZER;
/*! A copy of the value passed to `-initWithHostName:`.
*/
@property (nonatomic, copy, readonly) NSString *hostName;
/*! The delegate for this object.
* \details Delegate callbacks are schedule in the default run loop mode of the run loop of the
* thread that calls `-start`.
*/
@property (nonatomic, weak, readwrite, nullable) id<SimplePingDelegate> delegate;
/*! Controls the IP address version used by the object.
* \details You should set this value before starting the object.
*/
@property (nonatomic, assign, readwrite) SimplePingAddressStyle addressStyle;
/*! The address being pinged.
* \details The contents of the NSData is a (struct sockaddr) of some form. The
* value is nil while the object is stopped and remains nil on start until
* `-simplePing:didStartWithAddress:` is called.
*/
@property (nonatomic, copy, readonly, nullable) NSData *hostAddress;
/*! The address family for `hostAddress`, or `AF_UNSPEC` if that's nil.
*/
@property (nonatomic, assign, readonly) sa_family_t hostAddressFamily;
/*! The identifier used by pings by this object.
* \details When you create an instance of this object it generates a random identifier
* that it uses to identify its own pings.
*/
@property (nonatomic, assign, readonly) uint16_t identifier;
/*! The next sequence number to be used by this object.
* \details This value starts at zero and increments each time you send a ping (safely
* wrapping back to zero if necessary). The sequence number is included in the ping,
* allowing you to match up requests and responses, and thus calculate ping times and
* so on.
*/
@property (nonatomic, assign, readonly) uint16_t nextSequenceNumber;
/*! Starts the object.
* \details You should set up the delegate and any ping parameters before calling this.
*
* If things go well you'll soon get the `-simplePing:didStartWithAddress:` delegate
* callback, at which point you can start sending pings (via `-sendPingWithData:`) and
* will start receiving ICMP packets (either ping responses, via the
* `-simplePing:didReceivePingResponsePacket:sequenceNumber:` delegate callback, or
* unsolicited ICMP packets, via the `-simplePing:didReceiveUnexpectedPacket:` delegate
* callback).
*
* If the object fails to start, typically because `hostName` doesn't resolve, you'll get
* the `-simplePing:didFailWithError:` delegate callback.
*
* It is not correct to start an already started object.
*/
- (void)start;
/*! Sends a ping packet containing the specified data.
* \details Sends an actual ping.
*
* The object must be started when you call this method and, on starting the object, you must
* wait for the `-simplePing:didStartWithAddress:` delegate callback before calling it.
* \param data Some data to include in the ping packet, after the ICMP header, or nil if you
* want the packet to include a standard 56 byte payload (resulting in a standard 64 byte
* ping).
*/
- (void)sendPingWithData:(nullable NSData *)data;
/*! Stops the object.
* \details You should call this when you're done pinging.
*
* It's safe to call this on an object that's stopped.
*/
- (void)stop;
@end
/*! A delegate protocol for the SimplePing class.
*/
@protocol SimplePingDelegate <NSObject>
@optional
/*! A SimplePing delegate callback, called once the object has started up.
* \details This is called shortly after you start the object to tell you that the
* object has successfully started. On receiving this callback, you can call
* `-sendPingWithData:` to send pings.
*
* If the object didn't start, `-simplePing:didFailWithError:` is called instead.
* \param pinger The object issuing the callback.
* \param address The address that's being pinged; at the time this delegate callback
* is made, this will have the same value as the `hostAddress` property.
*/
- (void)simplePing:(QCloudSimplePing *)pinger didStartWithAddress:(NSData *)address;
/*! A SimplePing delegate callback, called if the object fails to start up.
* \details This is called shortly after you start the object to tell you that the
* object has failed to start. The most likely cause of failure is a problem
* resolving `hostName`.
*
* By the time this callback is called, the object has stopped (that is, you don't
* need to call `-stop` yourself).
* \param pinger The object issuing the callback.
* \param error Describes the failure.
*/
- (void)simplePing:(QCloudSimplePing *)pinger didFailWithError:(NSError *)error;
/*! A SimplePing delegate callback, called when the object has successfully sent a ping packet.
* \details Each call to `-sendPingWithData:` will result in either a
* `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a
* `-simplePing:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you
* stop the object before you get the callback). These callbacks are currently delivered
* synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not
* considered API.
* \param pinger The object issuing the callback.
* \param packet The packet that was sent; this includes the ICMP header (`ICMPHeader`) and the
* data you passed to `-sendPingWithData:` but does not include any IP-level headers.
* \param sequenceNumber The ICMP sequence number of that packet.
*/
- (void)simplePing:(QCloudSimplePing *)pinger didSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;
/*! A SimplePing delegate callback, called when the object fails to send a ping packet.
* \details Each call to `-sendPingWithData:` will result in either a
* `-simplePing:didSendPacket:sequenceNumber:` delegate callback or a
* `-simplePing:didFailToSendPacket:sequenceNumber:error:` delegate callback (unless you
* stop the object before you get the callback). These callbacks are currently delivered
* synchronously from within `-sendPingWithData:`, but this synchronous behaviour is not
* considered API.
* \param pinger The object issuing the callback.
* \param packet The packet that was not sent; see `-simplePing:didSendPacket:sequenceNumber:`
* for details.
* \param sequenceNumber The ICMP sequence number of that packet.
* \param error Describes the failure.
*/
- (void)simplePing:(QCloudSimplePing *)pinger didFailToSendPacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber error:(NSError *)error;
/*! A SimplePing delegate callback, called when the object receives a ping response.
* \details If the object receives an ping response that matches a ping request that it
* sent, it informs the delegate via this callback. Matching is primarily done based on
* the ICMP identifier, although other criteria are used as well.
* \param pinger The object issuing the callback.
* \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that
* follows that in the ICMP message but does not include any IP-level headers.
* \param sequenceNumber The ICMP sequence number of that packet.
*/
- (void)simplePing:(QCloudSimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet sequenceNumber:(uint16_t)sequenceNumber;
/*! A SimplePing delegate callback, called when the object receives an unmatched ICMP message.
* \details If the object receives an ICMP message that does not match a ping request that it
* sent, it informs the delegate via this callback. The nature of ICMP handling in a
* BSD kernel makes this a common event because, when an ICMP message arrives, it is
* delivered to all ICMP sockets.
*
* IMPORTANT: This callback is especially common when using IPv6 because IPv6 uses ICMP
* for important network management functions. For example, IPv6 routers periodically
* send out Router Advertisement (RA) packets via Neighbor Discovery Protocol (NDP), which
* is implemented on top of ICMP.
*
* For more on matching, see the discussion associated with
* `-simplePing:didReceivePingResponsePacket:sequenceNumber:`.
* \param pinger The object issuing the callback.
* \param packet The packet received; this includes the ICMP header (`ICMPHeader`) and any data that
* follows that in the ICMP message but does not include any IP-level headers.
*/
- (void)simplePing:(QCloudSimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet;
@end
#pragma mark * ICMP On-The-Wire Format
/*! Describes the on-the-wire header format for an ICMP ping.
* \details This defines the header structure of ping packets on the wire. Both IPv4 and
* IPv6 use the same basic structure.
*
* This is declared in the header because clients of SimplePing might want to use
* it parse received ping packets.
*/
struct ICMPHeader {
uint8_t type;
uint8_t code;
uint16_t checksum;
uint16_t identifier;
uint16_t sequenceNumber;
// data...
};
typedef struct ICMPHeader ICMPHeader;
__Check_Compile_Time(sizeof(ICMPHeader) == 8);
__Check_Compile_Time(offsetof(ICMPHeader, type) == 0);
__Check_Compile_Time(offsetof(ICMPHeader, code) == 1);
__Check_Compile_Time(offsetof(ICMPHeader, checksum) == 2);
__Check_Compile_Time(offsetof(ICMPHeader, identifier) == 4);
__Check_Compile_Time(offsetof(ICMPHeader, sequenceNumber) == 6);
enum {
ICMPv4TypeEchoRequest = 8, ///< The ICMP `type` for a ping request; in this case `code` is always 0.
ICMPv4TypeEchoReply = 0 ///< The ICMP `type` for a ping response; in this case `code` is always 0.
};
enum {
ICMPv6TypeEchoRequest = 128, ///< The ICMP `type` for a ping request; in this case `code` is always 0.
ICMPv6TypeEchoReply = 129 ///< The ICMP `type` for a ping response; in this case `code` is always 0.
};
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,788 @@
#import "QCloudSimplePing.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#pragma mark * IPv4 and ICMPv4 On-The-Wire Format
/*! Describes the on-the-wire header format for an IPv4 packet.
* \details This defines the header structure of IPv4 packets on the wire. We need
* this in order to skip this header in the IPv4 case, where the kernel passes
* it to us for no obvious reason.
*/
struct IPv4Header {
uint8_t versionAndHeaderLength;
uint8_t differentiatedServices;
uint16_t totalLength;
uint16_t identification;
uint16_t flagsAndFragmentOffset;
uint8_t timeToLive;
uint8_t protocol;
uint16_t headerChecksum;
uint8_t sourceAddress[4];
uint8_t destinationAddress[4];
// options...
// data...
};
typedef struct IPv4Header IPv4Header;
__Check_Compile_Time(sizeof(IPv4Header) == 20);
__Check_Compile_Time(offsetof(IPv4Header, versionAndHeaderLength) == 0);
__Check_Compile_Time(offsetof(IPv4Header, differentiatedServices) == 1);
__Check_Compile_Time(offsetof(IPv4Header, totalLength) == 2);
__Check_Compile_Time(offsetof(IPv4Header, identification) == 4);
__Check_Compile_Time(offsetof(IPv4Header, flagsAndFragmentOffset) == 6);
__Check_Compile_Time(offsetof(IPv4Header, timeToLive) == 8);
__Check_Compile_Time(offsetof(IPv4Header, protocol) == 9);
__Check_Compile_Time(offsetof(IPv4Header, headerChecksum) == 10);
__Check_Compile_Time(offsetof(IPv4Header, sourceAddress) == 12);
__Check_Compile_Time(offsetof(IPv4Header, destinationAddress) == 16);
/*! Calculates an IP checksum.
* \details This is the standard BSD checksum code, modified to use modern types.
* \param buffer A pointer to the data to checksum.
* \param bufferLen The length of that data.
* \returns The checksum value, in network byte order.
*/
static uint16_t in_cksum(const void *buffer, size_t bufferLen) {
//
size_t bytesLeft;
int32_t sum;
const uint16_t *cursor;
union {
uint16_t us;
uint8_t uc[2];
} last;
uint16_t answer;
bytesLeft = bufferLen;
sum = 0;
cursor = buffer;
/*
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
*/
while (bytesLeft > 1) {
sum += *cursor;
cursor += 1;
bytesLeft -= 2;
}
/* mop up an odd byte, if necessary */
if (bytesLeft == 1) {
last.uc[0] = *(const uint8_t *)cursor;
last.uc[1] = 0;
sum += last.us;
}
/* add back carry outs from top 16 bits to low 16 bits */
sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = (uint16_t)~sum; /* truncate to 16 bits */
return answer;
}
#pragma mark * SimplePing
@interface QCloudSimplePing ()
// read/write versions of public properties
@property (nonatomic, copy, readwrite, nullable) NSData *hostAddress;
@property (nonatomic, assign, readwrite) uint16_t nextSequenceNumber;
// private properties
/*! True if nextSequenceNumber has wrapped from 65535 to 0.
*/
@property (nonatomic, assign, readwrite) BOOL nextSequenceNumberHasWrapped;
/*! A host object for name-to-address resolution.
*/
@property (nonatomic, strong, readwrite, nullable) CFHostRef host __attribute__((NSObject));
/*! A socket object for ICMP send and receive.
*/
@property (nonatomic, strong, readwrite, nullable) CFSocketRef socket __attribute__((NSObject));
@end
@implementation QCloudSimplePing
- (instancetype)initWithHostName:(NSString *)hostName {
NSParameterAssert(hostName != nil);
self = [super init];
if (self != nil) {
self->_hostName = [hostName copy];
self->_identifier = (uint16_t)arc4random();
}
return self;
}
- (void)dealloc {
[self stop];
// Double check that -stop took care of _host and _socket.
assert(self->_host == NULL);
assert(self->_socket == NULL);
}
- (sa_family_t)hostAddressFamily {
sa_family_t result;
result = AF_UNSPEC;
if ((self.hostAddress != nil) && (self.hostAddress.length >= sizeof(struct sockaddr))) {
result = ((const struct sockaddr *)self.hostAddress.bytes)->sa_family;
}
return result;
}
/*! Shuts down the pinger object and tell the delegate about the error.
* \param error Describes the failure.
*/
- (void)didFailWithError:(NSError *)error {
id<SimplePingDelegate> strongDelegate;
assert(error != nil);
// We retain ourselves temporarily because it's common for the delegate method
// to release its last reference to us, which causes -dealloc to be called here.
// If we then reference self on the return path, things go badly. I don't think
// that happens currently, but I've got into the habit of doing this as a
// defensive measure.
CFAutorelease(CFBridgingRetain(self));
[self stop];
strongDelegate = self.delegate;
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailWithError:)]) {
[strongDelegate simplePing:self didFailWithError:error];
}
}
/*! Shuts down the pinger object and tell the delegate about the error.
* \details This converts the CFStreamError to an NSError and then call through to
* -didFailWithError: to do the real work.
* \param streamError Describes the failure.
*/
- (void)didFailWithHostStreamError:(CFStreamError)streamError {
NSDictionary *userInfo;
NSError *error;
if (streamError.domain == kCFStreamErrorDomainNetDB) {
userInfo = @{ (id)kCFGetAddrInfoFailureKey : @(streamError.error) };
} else {
userInfo = nil;
}
error = [NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorUnknown userInfo:userInfo];
[self didFailWithError:error];
}
/*! Builds a ping packet from the supplied parameters.
* \param type The packet type, which is different for IPv4 and IPv6.
* \param payload Data to place after the ICMP header.
* \param requiresChecksum Determines whether a checksum is calculated (IPv4) or not (IPv6).
* \returns A ping packet suitable to be passed to the kernel.
*/
- (NSData *)pingPacketWithType:(uint8_t)type payload:(NSData *)payload requiresChecksum:(BOOL)requiresChecksum {
NSMutableData *packet;
ICMPHeader *icmpPtr;
packet = [NSMutableData dataWithLength:sizeof(*icmpPtr) + payload.length];
if (packet == nil) {
return nil;
}
icmpPtr = packet.mutableBytes;
icmpPtr->type = type;
icmpPtr->code = 0;
icmpPtr->checksum = 0;
icmpPtr->identifier = OSSwapHostToBigInt16(self.identifier);
icmpPtr->sequenceNumber = OSSwapHostToBigInt16(self.nextSequenceNumber);
memcpy(&icmpPtr[1], [payload bytes], [payload length]);
if (requiresChecksum) {
// The IP checksum routine returns a 16-bit number that's already in correct byte order
// (due to wacky 1's complement maths), so we just put it into the packet as a 16-bit unit.
icmpPtr->checksum = in_cksum(packet.bytes, packet.length);
}
return packet;
}
- (void)sendPingWithData:(NSData *)data {
int err;
NSData *payload;
NSData *packet;
ssize_t bytesSent;
id<SimplePingDelegate> strongDelegate;
// data may be nil
// NSParameterAssert(self.hostAddress != nil); // gotta wait for -simplePing:didStartWithAddress:
if (self.hostAddress == nil) {
return;
}
// Construct the ping packet.
payload = data;
if (payload == nil) {
payload = [[NSString stringWithFormat:@"%28zd bottles of beer on the wall", (ssize_t)99 - (size_t)(self.nextSequenceNumber % 100)]
dataUsingEncoding:NSASCIIStringEncoding];
assert(payload != nil);
// Our dummy payload is sized so that the resulting ICMP packet, including the ICMPHeader, is
// 64-bytes, which makes it easier to recognise our packets on the wire.
assert([payload length] == 56);
}
switch (self.hostAddressFamily) {
case AF_INET: {
packet = [self pingPacketWithType:ICMPv4TypeEchoRequest payload:payload requiresChecksum:YES];
} break;
case AF_INET6: {
packet = [self pingPacketWithType:ICMPv6TypeEchoRequest payload:payload requiresChecksum:NO];
} break;
default: {
return;
} break;
}
// assert(packet != nil);
if (packet == nil) {
return;
}
// Send the packet.
if (self.socket == NULL) {
bytesSent = -1;
err = EBADF;
} else {
bytesSent
= sendto(CFSocketGetNative(self.socket), packet.bytes, packet.length, 0, self.hostAddress.bytes, (socklen_t)self.hostAddress.length);
err = 0;
if (bytesSent < 0) {
err = errno;
}
}
// Handle the results of the send.
strongDelegate = self.delegate;
if ((bytesSent > 0) && (((NSUInteger)bytesSent) == packet.length)) {
// Complete success. Tell the client.
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didSendPacket:sequenceNumber:)]) {
[strongDelegate simplePing:self didSendPacket:packet sequenceNumber:self.nextSequenceNumber];
}
} else {
NSError *error;
// Some sort of failure. Tell the client.
if (err == 0) {
err = ENOBUFS; // This is not a hugely descriptor error, alas.
}
error = [NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil];
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didFailToSendPacket:sequenceNumber:error:)]) {
[strongDelegate simplePing:self didFailToSendPacket:packet sequenceNumber:self.nextSequenceNumber error:error];
}
}
self.nextSequenceNumber += 1;
if (self.nextSequenceNumber == 0) {
self.nextSequenceNumberHasWrapped = YES;
}
}
/*! Calculates the offset of the ICMP header within an IPv4 packet.
* \details In the IPv4 case the kernel returns us a buffer that includes the
* IPv4 header. We're not interested in that, so we have to skip over it.
* This code does a rough check of the IPv4 header and, if it looks OK,
* returns the offset of the ICMP header.
* \param packet The IPv4 packet, as returned to us by the kernel.
* \returns The offset of the ICMP header, or NSNotFound.
*/
+ (NSUInteger)icmpHeaderOffsetInIPv4Packet:(NSData *)packet {
// Returns the offset of the ICMPv4Header within an IP packet.
NSUInteger result;
const struct IPv4Header *ipPtr;
size_t ipHeaderLength;
result = NSNotFound;
if (packet.length >= (sizeof(IPv4Header) + sizeof(ICMPHeader))) {
ipPtr = (const IPv4Header *)packet.bytes;
if (((ipPtr->versionAndHeaderLength & 0xF0) == 0x40) && // IPv4
(ipPtr->protocol == IPPROTO_ICMP)) {
ipHeaderLength = (ipPtr->versionAndHeaderLength & 0x0F) * sizeof(uint32_t);
if (packet.length >= (ipHeaderLength + sizeof(ICMPHeader))) {
result = ipHeaderLength;
}
}
}
return result;
}
/*! Checks whether the specified sequence number is one we sent.
* \param sequenceNumber The incoming sequence number.
* \returns YES if the sequence number looks like one we sent.
*/
- (BOOL)validateSequenceNumber:(uint16_t)sequenceNumber {
if (self.nextSequenceNumberHasWrapped) {
// If the sequence numbers have wrapped that we can't reliably check
// whether this is a sequence number we sent. Rather, we check to see
// whether the sequence number is within the last 120 sequence numbers
// we sent. Note that the uint16_t subtraction here does the right
// thing regardless of the wrapping.
//
// Why 120? Well, if we send one ping per second, 120 is 2 minutes, which
// is the standard "max time a packet can bounce around the Internet" value.
return ((uint16_t)(self.nextSequenceNumber - sequenceNumber)) < (uint16_t)120;
} else {
return sequenceNumber < self.nextSequenceNumber;
}
}
/*! Checks whether an incoming IPv4 packet looks like a ping response.
* \details This routine modifies this `packet` data! It does this for two reasons:
*
* * It needs to zero out the `checksum` field of the ICMPHeader in order to do
* its checksum calculation.
*
* * It removes the IPv4 header from the front of the packet.
* \param packet The IPv4 packet, as returned to us by the kernel.
* \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
* \returns YES if the packet looks like a reasonable IPv4 ping response.
*/
- (BOOL)validatePing4ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
BOOL result;
NSUInteger icmpHeaderOffset;
ICMPHeader *icmpPtr;
uint16_t receivedChecksum;
uint16_t calculatedChecksum;
result = NO;
icmpHeaderOffset = [[self class] icmpHeaderOffsetInIPv4Packet:packet];
if (icmpHeaderOffset != NSNotFound) {
icmpPtr = (struct ICMPHeader *)(((uint8_t *)packet.mutableBytes) + icmpHeaderOffset);
receivedChecksum = icmpPtr->checksum;
icmpPtr->checksum = 0;
calculatedChecksum = in_cksum(icmpPtr, packet.length - icmpHeaderOffset);
icmpPtr->checksum = receivedChecksum;
if (receivedChecksum == calculatedChecksum) {
if ((icmpPtr->type == ICMPv4TypeEchoReply) && (icmpPtr->code == 0)) {
if (OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier) {
uint16_t sequenceNumber;
sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
if ([self validateSequenceNumber:sequenceNumber]) {
// Remove the IPv4 header off the front of the data we received, leaving us with
// just the ICMP header and the ping payload.
[packet replaceBytesInRange:NSMakeRange(0, icmpHeaderOffset) withBytes:NULL length:0];
*sequenceNumberPtr = sequenceNumber;
result = YES;
}
}
}
}
}
return result;
}
/*! Checks whether an incoming IPv6 packet looks like a ping response.
* \param packet The IPv6 packet, as returned to us by the kernel; note that this routine
* could modify this data but does not need to in the IPv6 case.
* \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
* \returns YES if the packet looks like a reasonable IPv4 ping response.
*/
- (BOOL)validatePing6ResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
BOOL result;
const ICMPHeader *icmpPtr;
result = NO;
if (packet.length >= sizeof(*icmpPtr)) {
icmpPtr = packet.bytes;
// In the IPv6 case we don't check the checksum because that's hard (we need to
// cook up an IPv6 pseudo header and we don't have the ingredients) and unnecessary
// (the kernel has already done this check).
if ((icmpPtr->type == ICMPv6TypeEchoReply) && (icmpPtr->code == 0)) {
if (OSSwapBigToHostInt16(icmpPtr->identifier) == self.identifier) {
uint16_t sequenceNumber;
sequenceNumber = OSSwapBigToHostInt16(icmpPtr->sequenceNumber);
if ([self validateSequenceNumber:sequenceNumber]) {
*sequenceNumberPtr = sequenceNumber;
result = YES;
}
}
}
}
return result;
}
/*! Checks whether an incoming packet looks like a ping response.
* \param packet The packet, as returned to us by the kernel; note that may end up modifying
* this data.
* \param sequenceNumberPtr A pointer to a place to start the ICMP sequence number.
* \returns YES if the packet looks like a reasonable IPv4 ping response.
*/
- (BOOL)validatePingResponsePacket:(NSMutableData *)packet sequenceNumber:(uint16_t *)sequenceNumberPtr {
BOOL result;
switch (self.hostAddressFamily) {
case AF_INET: {
result = [self validatePing4ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
} break;
case AF_INET6: {
result = [self validatePing6ResponsePacket:packet sequenceNumber:sequenceNumberPtr];
} break;
default: {
// assert(NO);
result = NO;
} break;
}
return result;
}
/*! Reads data from the ICMP socket.
* \details Called by the socket handling code (SocketReadCallback) to process an ICMP
* message waiting on the socket.
*/
- (void)readData {
int err;
struct sockaddr_storage addr;
socklen_t addrLen;
ssize_t bytesRead;
void *buffer;
enum { kBufferSize = 65535 };
// 65535 is the maximum IP packet size, which seems like a reasonable bound
// here (plus it's what <x-man-page://8/ping> uses).
buffer = malloc(kBufferSize);
// assert(buffer != NULL);
if (buffer == NULL) {
return;
}
// Actually read the data. We use recvfrom(), and thus get back the source address,
// but we don't actually do anything with it. It would be trivial to pass it to
// the delegate but we don't need it in this example.
addrLen = sizeof(addr);
bytesRead = recvfrom(CFSocketGetNative(self.socket), buffer, kBufferSize, 0, (struct sockaddr *)&addr, &addrLen);
err = 0;
if (bytesRead < 0) {
err = errno;
}
// Process the data we read.
if (bytesRead > 0) {
NSMutableData *packet;
id<SimplePingDelegate> strongDelegate;
uint16_t sequenceNumber;
packet = [NSMutableData dataWithBytes:buffer length:(NSUInteger)bytesRead];
// assert(packet != nil);
if (packet == nil) {
return;
}
// We got some data, pass it up to our client.
strongDelegate = self.delegate;
if ([self validatePingResponsePacket:packet sequenceNumber:&sequenceNumber]) {
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceivePingResponsePacket:sequenceNumber:)]) {
[strongDelegate simplePing:self didReceivePingResponsePacket:packet sequenceNumber:sequenceNumber];
}
} else {
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didReceiveUnexpectedPacket:)]) {
[strongDelegate simplePing:self didReceiveUnexpectedPacket:packet];
}
}
} else {
// We failed to read the data, so shut everything down.
if (err == 0) {
err = EPIPE;
}
[self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
}
free(buffer);
// Note that we don't loop back trying to read more data. Rather, we just
// let CFSocket call us again.
}
/*! The callback for our CFSocket object.
* \details This simply routes the call to our `-readData` method.
* \param s See the documentation for CFSocketCallBack.
* \param type See the documentation for CFSocketCallBack.
* \param address See the documentation for CFSocketCallBack.
* \param data See the documentation for CFSocketCallBack.
* \param info See the documentation for CFSocketCallBack; this is actually a pointer to the
* 'owning' object.
*/
static void SocketReadCallback(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
// This C routine is called by CFSocket when there's data waiting on our
// ICMP socket. It just redirects the call to Objective-C code.
QCloudSimplePing *obj;
obj = (__bridge QCloudSimplePing *)info;
assert([obj isKindOfClass:[QCloudSimplePing class]]);
#pragma unused(s)
assert(s == obj.socket);
#pragma unused(type)
assert(type == kCFSocketReadCallBack);
#pragma unused(address)
assert(address == nil);
#pragma unused(data)
assert(data == nil);
[obj readData];
}
/*! Starts the send and receive infrastructure.
* \details This is called once we've successfully resolved `hostName` in to
* `hostAddress`. It's responsible for setting up the socket for sending and
* receiving pings.
*/
- (void)startWithHostAddress {
int err;
int fd;
// assert(self.hostAddress != nil);
if (self.hostAddress == nil) {
return;
}
// Open the socket.
fd = -1;
err = 0;
switch (self.hostAddressFamily) {
case AF_INET: {
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (fd < 0) {
err = errno;
}
} break;
case AF_INET6: {
fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
if (fd < 0) {
err = errno;
}
} break;
default: {
err = EPROTONOSUPPORT;
} break;
}
if (err != 0) {
[self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
} else {
CFSocketContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
CFRunLoopSourceRef rls;
id<SimplePingDelegate> strongDelegate;
// Wrap it in a CFSocket and schedule it on the runloop.
self.socket = (CFSocketRef)CFAutorelease(CFSocketCreateWithNative(NULL, fd, kCFSocketReadCallBack, SocketReadCallback, &context));
assert(self.socket != NULL);
// The socket will now take care of cleaning up our file descriptor.
assert(CFSocketGetSocketFlags(self.socket) & kCFSocketCloseOnInvalidate);
fd = -1;
rls = CFSocketCreateRunLoopSource(NULL, self.socket, 0);
assert(rls != NULL);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
CFRelease(rls);
strongDelegate = self.delegate;
if ((strongDelegate != nil) && [strongDelegate respondsToSelector:@selector(simplePing:didStartWithAddress:)]) {
[strongDelegate simplePing:self didStartWithAddress:self.hostAddress];
}
}
assert(fd == -1);
}
/*! Processes the results of our name-to-address resolution.
* \details Called by our CFHost resolution callback (HostResolveCallback) when host
* resolution is complete. We just latch the first appropriate address and kick
* off the send and receive infrastructure.
*/
- (void)hostResolutionDone {
Boolean resolved;
NSArray *addresses;
// Find the first appropriate address.
addresses = (__bridge NSArray *)CFHostGetAddressing(self.host, &resolved);
if (resolved && (addresses != nil)) {
resolved = false;
for (NSData *address in addresses) {
const struct sockaddr *addrPtr;
addrPtr = (const struct sockaddr *)address.bytes;
if (address.length >= sizeof(struct sockaddr)) {
switch (addrPtr->sa_family) {
case AF_INET: {
if (self.addressStyle != SimplePingAddressStyleICMPv6) {
self.hostAddress = address;
resolved = true;
}
} break;
case AF_INET6: {
if (self.addressStyle != SimplePingAddressStyleICMPv4) {
self.hostAddress = address;
resolved = true;
}
} break;
}
}
if (resolved) {
break;
}
}
}
// We're done resolving, so shut that down.
[self stopHostResolution];
// If all is OK, start the send and receive infrastructure, otherwise stop.
if (resolved) {
[self startWithHostAddress];
} else {
[self didFailWithError:[NSError errorWithDomain:(NSString *)kCFErrorDomainCFNetwork code:kCFHostErrorHostNotFound userInfo:nil]];
}
}
/*! The callback for our CFHost object.
* \details This simply routes the call to our `-hostResolutionDone` or
* `-didFailWithHostStreamError:` methods.
* \param theHost See the documentation for CFHostClientCallBack.
* \param typeInfo See the documentation for CFHostClientCallBack.
* \param error See the documentation for CFHostClientCallBack.
* \param info See the documentation for CFHostClientCallBack; this is actually a pointer to
* the 'owning' object.
*/
static void HostResolveCallback(CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) {
// This C routine is called by CFHost when the host resolution is complete.
// It just redirects the call to the appropriate Objective-C method.
QCloudSimplePing *obj;
obj = (__bridge QCloudSimplePing *)info;
assert([obj isKindOfClass:[QCloudSimplePing class]]);
#pragma unused(theHost)
assert(theHost == obj.host);
#pragma unused(typeInfo)
assert(typeInfo == kCFHostAddresses);
if ((error != NULL) && (error->domain != 0)) {
[obj didFailWithHostStreamError:*error];
} else {
[obj hostResolutionDone];
}
}
- (void)start {
Boolean success;
CFHostClientContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL };
CFStreamError streamError;
assert(self.host == NULL);
assert(self.hostAddress == nil);
self.host = (CFHostRef)CFAutorelease(CFHostCreateWithName(NULL, (__bridge CFStringRef)self.hostName));
// assert(self.host != NULL);
if (self.host == NULL) {
return;
}
CFHostSetClient(self.host, HostResolveCallback, &context);
CFHostScheduleWithRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
success = CFHostStartInfoResolution(self.host, kCFHostAddresses, &streamError);
if (!success) {
[self didFailWithHostStreamError:streamError];
}
}
/*! Stops the name-to-address resolution infrastructure.
*/
- (void)stopHostResolution {
// Shut down the CFHost.
if (self.host != NULL) {
CFHostSetClient(self.host, NULL, NULL);
CFHostUnscheduleFromRunLoop(self.host, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
self.host = NULL;
}
}
/*! Stops the send and receive infrastructure.
*/
- (void)stopSocket {
if (self.socket != NULL) {
CFSocketInvalidate(self.socket);
self.socket = NULL;
}
}
- (void)stop {
[self stopHostResolution];
[self stopSocket];
// Junk the host address on stop. If the client calls -start again, we'll
// re-resolve the host name.
self.hostAddress = NULL;
}
@end

View File

@@ -0,0 +1,17 @@
//
// QCloudFileOffsetBody.h
// Pods
//
// Created by Dong Zhao on 2017/5/23.
//
//
#import <Foundation/Foundation.h>
@interface QCloudFileOffsetBody : NSObject
@property (nonatomic, strong, readonly) NSURL *fileURL;
@property (nonatomic, assign, readonly) NSUInteger offset;
@property (nonatomic, assign, readonly) NSUInteger sliceLength;
@property (nonatomic, assign) NSUInteger index;
- (instancetype)initWithFile:(NSURL *)fileURL offset:(NSUInteger)offset slice:(NSUInteger)slice;
@end

View File

@@ -0,0 +1,23 @@
//
// QCloudFileOffsetBody.m
// Pods
//
// Created by Dong Zhao on 2017/5/23.
//
//
#import "QCloudFileOffsetBody.h"
@implementation QCloudFileOffsetBody
- (instancetype)initWithFile:(NSURL *)fileURL offset:(NSUInteger)offset slice:(NSUInteger)slice {
self = [super init];
if (!self) {
return self;
}
_fileURL = fileURL;
_offset = offset;
_sliceLength = slice;
return self;
}
@end

View File

@@ -0,0 +1,19 @@
//
// QCloudFileOffsetStream.h
// Pods
//
// Created by Dong Zhao on 2017/5/22.
//
//
#import <Foundation/Foundation.h>
@interface QCloudFileOffsetStream : NSInputStream
@property (nonatomic, assign) NSUInteger offset;
@property (nonatomic, assign) NSUInteger sliceLength;
- (instancetype)initWithFileAtPath:(NSString *)path NS_UNAVAILABLE;
- (instancetype)initWithData:(NSData *)data NS_UNAVAILABLE;
- (instancetype)initWithURL:(NSURL *)url NS_UNAVAILABLE;
- (instancetype)initWithFileAtPath:(NSString *)path offset:(NSUInteger)offset slice:(NSUInteger)sliceLength;
@end

View File

@@ -0,0 +1,127 @@
//
// QCloudFileOffsetStream.m
// Pods
//
// Created by Dong Zhao on 2017/5/22.
//
//
#import "QCloudFileOffsetStream.h"
#import "QCloudFileUtils.h"
@interface NSStream ()
@property (readwrite) NSStreamStatus streamStatus;
@property (readwrite, copy) NSError *streamError;
@end
@interface NSInputStream ()
- (void)open;
- (void)close;
@end
@interface QCloudFileOffsetStream ()
@property (nonatomic, assign) NSUInteger contentLength;
@property (nonatomic, assign) NSUInteger readLength;
@property (nonatomic, assign) NSStreamStatus status;
@property (nonatomic, strong) NSFileHandle *fileReadHandler;
@property (nonatomic, strong) NSString *path;
@end
@implementation QCloudFileOffsetStream
@synthesize streamError;
@synthesize streamStatus;
- (instancetype)initWithFileAtPath:(NSString *)path offset:(NSUInteger)offset slice:(NSUInteger)sliceLength {
self = [super init];
if (!self) {
return nil;
}
_offset = offset;
_sliceLength = sliceLength;
_contentLength = QCloudFileSize(path);
_path = path;
return self;
}
- (void)open {
_fileReadHandler = [NSFileHandle fileHandleForReadingAtPath:self.path];
[_fileReadHandler seekToFileOffset:_offset];
self.status = NSStreamStatusOpen;
_readLength = 0;
}
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
if (self.status == NSStreamStatusClosed) {
return 0;
}
NSUInteger ureadLength = _sliceLength - _readLength;
NSUInteger willReadLength = MIN(ureadLength, len);
if (willReadLength <= 0) {
return 0;
}
NSData *data = [_fileReadHandler readDataOfLength:willReadLength];
memcpy(buffer, [data bytes], willReadLength);
_readLength += willReadLength;
return willReadLength;
}
- (NSError *)streamError {
return nil;
}
- (NSStreamStatus)streamStatus {
if (![self hasBytesAvailable]) {
self.status = NSStreamStatusAtEnd;
}
return self.status;
}
- (void)close {
[_fileReadHandler closeFile];
_fileReadHandler = nil;
self.status = NSStreamStatusClosed;
_readLength = 0;
}
- (BOOL)getBuffer:(uint8_t *_Nullable *_Nonnull)buffer length:(NSUInteger *)len {
return NO;
}
- (BOOL)hasBytesAvailable {
if (_offset + _readLength >= _contentLength) {
return NO;
}
if (_readLength >= _sliceLength) {
return NO;
}
if (_offset + _readLength < _sliceLength) {
return YES;
}
return NO;
}
- (id)propertyForKey:(__unused NSString *)key {
return nil;
}
- (BOOL)setProperty:(__unused id)property forKey:(__unused NSString *)key {
return NO;
}
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop forMode:(__unused NSString *)mode {
}
- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop forMode:(__unused NSString *)mode {
}
#pragma mark - Undocumented CFReadStream Bridged Methods
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop forMode:(__unused CFStringRef)aMode {
}
- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop forMode:(__unused CFStringRef)aMode {
}
- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags
callback:(__unused CFReadStreamClientCallBack)inCallback
context:(__unused CFStreamClientContext *)inContext {
return NO;
}
@end

Some files were not shown because too many files have changed in this diff Show More