TwitterImagePipeline/Project/TIP_ProjectCommon.h (189 lines of code) (raw):
//
// TIP_ProjectCommon.h
// TwitterImagePipeline
//
// Created on 3/5/15.
// Copyright © 2020 Twitter. All rights reserved.
//
// This header is kept in sync with other *_Common.h headers from sibling projects.
// This header is separate from TIP_Project.h which has TIP specific helper code.
#import <Foundation/Foundation.h>
#import <TwitterImagePipeline/TIPLogger.h>
NS_ASSUME_NONNULL_BEGIN
#pragma mark - File Name macro
/**
Helper macro for the file name macro.
`__FILE__` is the historical C macro that is replaced with the full file path of the current file being compiled (e.g. `/Users/username/workspace/project/source/subfolder/anotherfolder/implementation/file.c`)
`__FILE_NAME__` is the new C macro in clang that is replaced with the file name of the current file being compiled (e.g. `file.c`)
By default, if `__FILE_NAME__` is availble with the current compiler, it will be used.
This behavior can be overridden by providing a value for `TIP_FILE_NAME` to the compiler, like `-DTIP_FILE_NAME=__FILE__` or `-DTIP_FILE_NAME=\"redacted\"`
*/
#if !defined(TIP_FILE_NAME)
#ifdef __FILE_NAME__
#define TIP_FILE_NAME __FILE_NAME__
#else
#define TIP_FILE_NAME __FILE__
#endif
#endif
#pragma mark - Binary
FOUNDATION_EXTERN BOOL TIPIsExtension(void);
#pragma mark - Availability
// macros helpers to match against specific iOS versions and their mapped non-iOS platform versions
#define tip_available_ios_11 @available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)
#define tip_available_ios_12 @available(iOS 12, tvOS 12, macOS 10.14, watchOS 5, *)
#define tip_available_ios_13 @available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)
#define tip_available_ios_14 @available(iOS 14, tvOS 14, macOS 11.0, watchOS 7, *)
#if TARGET_OS_IOS
#define TIP_OS_VERSION_MAX_ALLOWED_IOS_14 (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)
#elif TARGET_OS_MACCATALYST
#define TIP_OS_VERSION_MAX_ALLOWED_IOS_14 (__IPHONE_OS_VERSION_MAX_ALLOWED >= 140000)
#elif TARGET_OS_TV
#define TIP_OS_VERSION_MAX_ALLOWED_IOS_14 (__TV_OS_VERSION_MAX_ALLOWED >= 140000)
#elif TARGET_OS_WATCH
#define TIP_OS_VERSION_MAX_ALLOWED_IOS_14 (__WATCH_OS_VERSION_MAX_ALLOWED >= 70000)
#elif TARGET_OS_OSX
#define TGF_OS_VERSION_MAX_ALLOWED_IOS_14 (__MAC_OS_X_VERSION_MAX_ALLOWED >= 110000)
#else
#warning Unexpected Target Platform
#define TIP_OS_VERSION_MAX_ALLOWED_IOS_14 (0)
#endif
#pragma mark - Bitmask Helpers
/** Does the `mask` have at least 1 of the bits in `flags` set */
#define TIP_BITMASK_INTERSECTS_FLAGS(mask, flags) (((mask) & (flags)) != 0)
/** Does the `mask` have all of the bits in `flags` set */
#define TIP_BITMASK_HAS_SUBSET_FLAGS(mask, flags) (((mask) & (flags)) == (flags))
/** Does the `mask` have none of the bits in `flags` set */
#define TIP_BITMASK_EXCLUDES_FLAGS(mask, flags) (((mask) & (flags)) == 0)
#pragma mark - Assert
FOUNDATION_EXTERN BOOL gTwitterImagePipelineAssertEnabled;
#if !defined(NS_BLOCK_ASSERTIONS)
#define TIPCAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
__TIPAssertTriggering(); \
NSString *__assert_fn__ = [NSString stringWithUTF8String:__PRETTY_FUNCTION__]; \
__assert_fn__ = __assert_fn__ ? __assert_fn__ : @"<Unknown Function>"; \
NSString *__assert_file__ = [NSString stringWithUTF8String:TIP_FILE_NAME]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
[[NSAssertionHandler currentHandler] handleFailureInFunction:__assert_fn__ \
file:__assert_file__ \
lineNumber:__LINE__ \
description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
#else // NS_BLOCK_ASSERTIONS defined
#define TIPCAssert(condition, desc, ...) do {} while (0)
#endif // NS_BLOCK_ASSERTIONS not defined
#define TIPAssert(expression) \
({ if (gTwitterImagePipelineAssertEnabled) { \
const BOOL __expressionValue = !!(expression); (void)__expressionValue; \
TIPCAssert(__expressionValue, @"assertion failed: (" #expression ")"); \
} })
#define TIPAssertMessage(expression, format, ...) \
({ if (gTwitterImagePipelineAssertEnabled) { \
const BOOL __expressionValue = !!(expression); (void)__expressionValue; \
TIPCAssert(__expressionValue, @"assertion failed: (" #expression ") message: %@", [NSString stringWithFormat:format, ##__VA_ARGS__]); \
} })
#define TIPAssertNever() TIPAssert(0 && "this line should never get executed" )
#pragma twitter startignoreformatting
// NOTE: TIPStaticAssert's msg argument should be valid as a variable. That is, composed of ASCII letters, numbers and underscore characters only.
#define __TIPStaticAssert(line, msg) TIPStaticAssert_##line##_##msg
#define _TIPStaticAssert(line, msg) __TIPStaticAssert( line , msg )
#define TIPStaticAssert(condition, msg) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wunused\"") \
typedef char _TIPStaticAssert( __LINE__ , msg ) [ (condition) ? 1 : -1 ] \
_Pragma("clang diagnostic pop" )
#pragma twitter endignoreformatting
#pragma mark - Logging
FOUNDATION_EXTERN id<TIPLogger> __nullable gTIPLogger;
#pragma twitter startignorestylecheck
#define TIPLog(level, ...) \
do { \
id<TIPLogger> const __logger = gTIPLogger; \
TIPLogLevel const __level = (level); \
if (__logger && (![__logger respondsToSelector:@selector(tip_canLogWithLevel:)] || [__logger tip_canLogWithLevel:__level])) { \
[__logger tip_logWithLevel:__level file:@(TIP_FILE_NAME) function:@(__FUNCTION__) line:__LINE__ message:[NSString stringWithFormat: __VA_ARGS__ ]]; \
} \
} while (0)
#define TIPLogError(...) TIPLog(TIPLogLevelError, __VA_ARGS__)
#define TIPLogWarning(...) TIPLog(TIPLogLevelWarning, __VA_ARGS__)
#define TIPLogInformation(...) TIPLog(TIPLogLevelInformation, __VA_ARGS__)
#define TIPLogDebug(...) TIPLog(TIPLogLevelDebug, __VA_ARGS__)
#pragma twitter endignorestylecheck
#pragma mark - Debugging Tools
#if DEBUG
FOUNDATION_EXTERN void __TIPAssertTriggering(void);
FOUNDATION_EXTERN BOOL TIPIsDebuggerAttached(void);
FOUNDATION_EXTERN void TIPTriggerDebugSTOP(void);
FOUNDATION_EXTERN BOOL TIPIsDebugSTOPOnAssertEnabled(void);
FOUNDATION_EXTERN void TIPSetDebugSTOPOnAssertEnabled(BOOL stopOnAssert);
#else
#define __TIPAssertTriggering() ((void)0)
#define TIPIsDebuggerAttached() (NO)
#define TIPTriggerDebugSTOP() ((void)0)
#define TIPIsDebugSTOPOnAssertEnabled() (NO)
#define TIPSetDebugSTOPOnAssertEnabled(stopOnAssert) ((void)0)
#endif
FOUNDATION_EXTERN BOOL TIPAmIBeingUnitTested(void);
#pragma mark - Style Check support
#pragma mark - Thread Sanitizer
// Macro to disable the thread-sanitizer for a particular method or function
#if defined(__has_feature)
# if __has_feature(thread_sanitizer)
# define TIP_THREAD_SANITIZER_DISABLED __attribute__((no_sanitize("thread")))
# else
# define TIP_THREAD_SANITIZER_DISABLED
# endif
#else
# define TIP_THREAD_SANITIZER_DISABLED
#endif
#pragma mark - Objective-C attribute support
#if defined(__has_attribute) && (defined(__IPHONE_14_0) || defined(__MAC_10_16) || defined(__MAC_11_0) || defined(__TVOS_14_0) || defined(__WATCHOS_7_0))
# define TIP_SUPPORTS_OBJC_DIRECT __has_attribute(objc_direct)
#else
# define TIP_SUPPORTS_OBJC_DIRECT 0
#endif
#if defined(__has_attribute)
# define TIP_SUPPORTS_OBJC_FINAL __has_attribute(objc_subclassing_restricted)
#else
# define TIP_SUPPORTS_OBJC_FINAL 0
#endif
#pragma mark - Objective-C Direct Support
#if TIP_SUPPORTS_OBJC_DIRECT
# define tip_nonatomic_direct nonatomic,direct
# define tip_atomic_direct atomic,direct
# define TIP_OBJC_DIRECT __attribute__((objc_direct))
# define TIP_OBJC_DIRECT_MEMBERS __attribute__((objc_direct_members))
#else
# define tip_nonatomic_direct nonatomic
# define tip_atomic_direct atomic
# define TIP_OBJC_DIRECT
# define TIP_OBJC_DIRECT_MEMBERS
#endif // #if TIP_SUPPORTS_OBJC_DIRECT
#pragma mark - Objective-C Final Support
#if TIP_SUPPORTS_OBJC_FINAL
# define TIP_OBJC_FINAL __attribute__((objc_subclassing_restricted))
#else
# define TIP_OBJC_FINAL
#endif // #if TIP_SUPPORTS_OBJC_FINAL
#pragma mark - tip_defer support
typedef void(^tip_defer_block_t)(void);
NS_INLINE void tip_deferFunc(__strong tip_defer_block_t __nonnull * __nonnull blockRef)
{
tip_defer_block_t actualBlock = *blockRef;
actualBlock();
}
#define _tip_macro_concat(a, b) a##b
#define tip_macro_concat(a, b) _tip_macro_concat(a, b)
#pragma twitter startignorestylecheck
#define tip_defer(deferBlock) \
__strong tip_defer_block_t tip_macro_concat(tip_stack_defer_block_, __LINE__) __attribute__((cleanup(tip_deferFunc), unused)) = deferBlock
#define TIPDeferRelease(ref) tip_defer(^{ if (ref) { CFRelease(ref); } })
#pragma twitter endignorestylecheck
#pragma mark - GCD helpers
// Autoreleasing dispatch functions.
// callers cannot use autoreleasing passthrough
//
// Example of what can't be done (autoreleasing passthrough):
//
// - (void)deleteFile:(NSString *)fileToDelete
// error:(NSError * __autoreleasing *)error
// {
// tip_dispatch_sync_autoreleasing(_config.queueForDiskCaches, ^{
// [[NSFileManager defaultFileManager] removeItemAtPath:fileToDelete
// /* will lead to crash if set to non-nil value --> */ error:error];
// });
// }
//
// Example of how to avoid passthrough crash:
//
// - (void)deleteFile:(NSString *)fileToDelete
// error:(NSError * __autoreleasing *)error
// {
// __block NSError *outerError = nil;
// tip_dispatch_sync_autoreleasing(_config.queueForDiskCaches, ^{
// NSError *innerError = nil;
// [[NSFileManager defaultFileManager] removeItemAtPath:fileToDelete
// error:&innerError];
// outerError = innerError;
// });
// if (error) {
// *error = outerError;
// }
// }
// Should pretty much ALWAYS use this for async dispatch
NS_INLINE void tip_dispatch_async_autoreleasing(dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_async(queue, ^{
@autoreleasepool {
block();
}
});
}
// Should pretty much ALWAYS use this for async barrier dispatch
NS_INLINE void tip_dispatch_barrier_async_autoreleasing(dispatch_queue_t queue, dispatch_block_t block)
{
dispatch_barrier_async(queue, ^{
@autoreleasepool {
block();
}
});
}
// Only need this in a tight loop, existing autorelease pool will take effect for dispatch_sync
NS_INLINE void tip_dispatch_sync_autoreleasing(dispatch_queue_t __attribute__((noescape)) queue, dispatch_block_t block)
{
dispatch_sync(queue, ^{
@autoreleasepool {
block();
}
});
}
NS_ASSUME_NONNULL_END