Source/TNL_Project.m (258 lines of code) (raw):
//
// TNL_Project.m
// TwitterNetworkLayer
//
// Created on 5/24/14.
// Copyright © 2020 Twitter, Inc. All rights reserved.
//
#include <objc/runtime.h>
#import "TNL_Project.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark Functions
dispatch_source_t tnl_dispatch_timer_create_and_start(dispatch_queue_t queue,
NSTimeInterval interval,
NSTimeInterval leeway,
BOOL repeats,
dispatch_block_t fireBlock)
{
dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
int64_t repeatInterval = (int64_t)(interval * (double)NSEC_PER_SEC);
dispatch_source_set_timer(timerSource, dispatch_time(DISPATCH_TIME_NOW, repeatInterval), (repeats) ? (uint64_t)repeatInterval : DISPATCH_TIME_FOREVER, (uint64_t)(leeway * (double)NSEC_PER_SEC));
dispatch_source_set_event_handler(timerSource, fireBlock);
dispatch_resume(timerSource);
return timerSource;
}
NSString *TNLVersion()
{
TNLStaticAssert(TNL_PROJECT_VERSION >= 1.0 && TNL_PROJECT_VERSION <= 10.0, INVALID_TNL_VERSION);
#define __TNL_VERSION(version) @"" #version
#define _TNL_VERSION(version) __TNL_VERSION( version )
#define TNL_VERSION() _TNL_VERSION( TNL_PROJECT_VERSION )
return TNL_VERSION();
}
#pragma mark - Threading
NSOperationQueue *TNLNetworkOperationQueue()
{
static NSOperationQueue *sQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sQueue = [[NSOperationQueue alloc] init];
sQueue.name = @"tnl.network.queue";
sQueue.underlyingQueue = tnl_network_queue();
});
return sQueue;
}
dispatch_queue_t tnl_network_queue()
{
static dispatch_queue_t sQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sQueue = dispatch_queue_create("tnl.network.queue", DISPATCH_QUEUE_SERIAL);
});
return sQueue;
}
dispatch_queue_t tnl_coding_queue()
{
static dispatch_queue_t sQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sQueue = dispatch_queue_create("tnl.encode.decode.queue", DISPATCH_QUEUE_SERIAL);
});
return sQueue;
}
#pragma mark - Dynamic Loading
#if TARGET_OS_IOS || TARGET_OS_TV
Class TNLDynamicUIApplicationClass()
{
static Class sUIApplicationClass;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (!TNLIsExtension()) {
sUIApplicationClass = NSClassFromString(@"UIApplication");
TNLAssert(sUIApplicationClass != Nil);
}
});
return sUIApplicationClass;
}
UIApplication *TNLDynamicUIApplicationSharedApplication()
{
const Class UIApplicationClass = TNLDynamicUIApplicationClass();
return UIApplicationClass ? [UIApplicationClass sharedApplication] : nil;
}
#endif // IOS + TV
#pragma mark - Introspection
static BOOL _TNLIntrospection_ObjectImplementsProtocolInstanceMethod(id object,
Protocol *p,
BOOL avoidRespondsToSelector,
BOOL allYesAnyNo);
static BOOL _TNLIntrospection_ObjectImplementsProtocolInstanceMethod(id object,
Protocol *p,
BOOL avoidRespondsToSelector,
BOOL allYesAnyNo)
{
Class theClass = [object class];
BOOL hit = allYesAnyNo;
for (unsigned int loop = NO; loop <= YES && (hit == allYesAnyNo); loop++) {
unsigned int count = 0;
struct objc_method_description * methodList = protocol_copyMethodDescriptionList(p, !loop, YES, &count);
if (count > 0) {
for (unsigned int i = 0; i < count && (hit == allYesAnyNo); i++) {
if (avoidRespondsToSelector) {
hit = (NULL != class_getInstanceMethod(theClass, methodList[i].name));
} else {
hit = [object respondsToSelector:methodList[i].name];
}
}
}
free(methodList);
}
return hit;
}
NSArray *TNLIntrospection_SelectorsForProtocol(Protocol *p,
BOOL requiredMethods,
BOOL instanceMethods)
{
NSMutableArray *selectors;
unsigned int count = 0;
struct objc_method_description * methodList = protocol_copyMethodDescriptionList(p,
requiredMethods,
instanceMethods,
&count);
if (count > 0) {
selectors = [NSMutableArray arrayWithCapacity:count];
for (unsigned int i = 0; i < count; i++) {
[selectors addObject:[NSValue valueWithPointer:methodList[i].name]];
}
}
free(methodList);
return selectors;
}
BOOL TNLIntrospection_ObjectImplementsAnyProtocolInstanceMethod(id object,
Protocol *p,
BOOL avoidRespondsToSelector)
{
return _TNLIntrospection_ObjectImplementsProtocolInstanceMethod(object,
p,
avoidRespondsToSelector,
NO);
}
BOOL TNLIntrospection_ProtocolContainsIntanceMethodSelector(Protocol *p,
SEL selector)
{
struct objc_method_description method = protocol_getMethodDescription(p,
selector,
YES,
YES);
if (method.name != selector) {
method = protocol_getMethodDescription(p,
selector,
NO,
YES);
}
return method.name == selector;
}
BOOL TNLIntrospection_ObjectImplementsAllProtocolInstanceMethods(id object,
Protocol *p,
BOOL avoidRespondsToSelector)
{
return _TNLIntrospection_ObjectImplementsProtocolInstanceMethod(object,
p,
avoidRespondsToSelector,
YES);
}
#if DEBUG
// When debugging, it can be useful to note the object counts of certain objects
#ifndef TRACK_OBJECT_COUNTS
#define TRACK_OBJECT_COUNTS 0
#endif
#if TRACK_OBJECT_COUNTS
static NSMutableDictionary *sCounts = nil;
static dispatch_queue_t sCountsQueue = NULL;
static dispatch_source_t sCountsTimer = NULL;
@interface NSObject (DebugSwizzle)
- (void)tnl_dealloc;
- (id)init_TNL;
@end
static void LogConnectCounts(void)
{
TNLLogDebug(@"TNLCounts", @"\n**********\n\t%@\n**********", sCounts);
}
__attribute__((constructor))
static void ConnLoad(void)
{
method_exchangeImplementations(class_getInstanceMethod([NSObject class], NSSelectorFromString(@"dealloc")),
class_getInstanceMethod([NSObject class], @selector(tnl_dealloc)));
method_exchangeImplementations(class_getInstanceMethod([NSObject class], @selector(init)),
class_getInstanceMethod([NSObject class], @selector(init_TNL)));
sCounts = [NSMutableDictionary dictionary];
sCountsQueue = dispatch_queue_create("com.twitter.tnl.debug.object.count.queue", DISPATCH_QUEUE_SERIAL);
sCountsTimer = tnl_dispatch_timer_create_and_start(sCountsQueue, 4.0, 1.0, YES, ^{
LogConnectCounts();
});
}
void TNLIncrementObjectCount(Class class)
{
tnl_dispatch_async_autoreleasing(sCountsQueue, ^{
NSString *className = NSStringFromClass(class);
if (className) {
NSUInteger count = [sCounts[className] unsignedIntegerValue];
sCounts[className] = @(count + 1);
}
});
}
void TNLDecrementObjectCount(Class class)
{
tnl_dispatch_async_autoreleasing(sCountsQueue, ^{
NSString *className = NSStringFromClass(class);
if (className) {
NSUInteger count = [sCounts[className] unsignedIntegerValue];
TNLAssert(count != 0);
if (count > 0) {
sCounts[className] = @(count - 1);
}
}
});
}
@implementation NSObject (DebugSwizzle)
- (void)tnl_dealloc
{
Class c = [self class];
if ([NSStringFromClass(c) hasSuffix:@"URLSession"]) {
TNLDecrementObjectCount(c);
}
[self tnl_dealloc];
}
- (id)init_TNL
{
self = [self init_TNL];
if (self) {
Class c = [self class];
if ([NSStringFromClass(c) hasSuffix:@"URLSession"]) {
TNLIncrementObjectCount(c);
}
}
return self;
}
@end
#else // !TRACK_OBJECT_COUNTS
void TNLIncrementObjectCount(Class class)
{
}
void TNLDecrementObjectCount(Class class)
{
}
#endif
#endif // DEBUG
NSError *TNLErrorCreateWithCode(TNLErrorCode code)
{
return TNLErrorCreateWithCodeAndUserInfo(code, nil);
}
NSError *TNLErrorCreateWithCodeAndUnderlyingError(TNLErrorCode code,
NSError * __nullable underlyingError)
{
return TNLErrorCreateWithCodeAndUserInfo(code, (underlyingError) ? @{ NSUnderlyingErrorKey : underlyingError } : nil);
}
NSError *TNLErrorCreateWithCodeAndUserInfo(TNLErrorCode code,
NSDictionary * __nullable userInfo)
{
NSString *errorCodeString = TNLErrorCodeToString(code);
if (errorCodeString) {
NSMutableDictionary *mUserInfo = [userInfo mutableCopy] ?: [NSMutableDictionary dictionary];
mUserInfo[TNLErrorCodeStringKey] = errorCodeString;
userInfo = mUserInfo;
}
return [NSError errorWithDomain:TNLErrorDomain code:code userInfo:userInfo];
}
NS_ASSUME_NONNULL_END