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