├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Extended ├── TIPXMP4Codec.h ├── TIPXMP4Codec.m ├── TIPXWebPCodec.h ├── TIPXWebPCodec.m └── WebP.framework │ ├── Headers │ ├── decode.h │ ├── encode.h │ └── types.h │ └── WebP ├── GraphicsRendererSpeed ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── Supporting Files │ ├── iceland_P3.jpg │ ├── iceland_sRGB.jpg │ ├── italy_P3.jpg │ ├── italy_sRGB.jpg │ ├── parrot_srgb.jpg │ ├── parrot_wide.jpg │ ├── shoes_adobeRGB.jpg │ ├── shoes_sRGB.jpg │ ├── webkit_P3.png │ └── webkit_sRGB.png ├── ViewController.h ├── ViewController.m └── main.m ├── ImageSpeedComparison ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── ViewController.h ├── ViewController.m ├── fireworks_original.gif ├── interlace.png ├── main.m ├── no-interlace.png ├── twitterfied.RLCP.jp2 ├── twitterfied.RPCL.jp2 ├── twitterfied.jp2 ├── twitterfied.jpg ├── twitterfied.pjpg ├── twitterfied.png ├── twitterfied.small.pjpg ├── twitterfied.tiff └── twitterfied.webp ├── LICENSE ├── README.md ├── Resources └── module.modulemap ├── TIP Sample App ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ └── first.pdf │ └── second.imageset │ │ ├── Contents.json │ │ └── second.pdf ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── InspectorViewController.h ├── InspectorViewController.m ├── PipelineCacheInspectionResultsViewController.h ├── PipelineCacheInspectionResultsViewController.m ├── PipelineInspectorViewController.h ├── PipelineInspectorViewController.m ├── SettingsViewController.h ├── SettingsViewController.m ├── TweetImageFetchRequest.h ├── TweetImageFetchRequest.m ├── TwitterAPI.h ├── TwitterAPI.m ├── TwitterSearchViewController.h ├── TwitterSearchViewController.m ├── ZoomingTweetImageViewController.h ├── ZoomingTweetImageViewController.m └── main.m ├── TIP Swift Sample App ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── first.imageset │ │ ├── Contents.json │ │ └── first.pdf │ └── second.imageset │ │ ├── Contents.json │ │ └── second.pdf ├── Base.lproj │ └── LaunchScreen.storyboard ├── Info.plist ├── Sample-Bridging-Header.h ├── TweetImageFetchRequest.swift ├── TwitterSearchViewController.swift └── ZoomingTweetImageViewController.swift ├── TIPTestsResources.bundle ├── 1538x2048.jpg ├── 200w.mp4 ├── Info.plist ├── allAlpha.png ├── carnival.bmp ├── carnival.jp2 ├── carnival.jpg ├── carnival.pjpg ├── carnival.png ├── carnival.tga ├── carnival.tiff ├── fireworks.gif ├── fireworks_original.gif ├── jpeg2k.jp2 ├── logo_only_final_reasonably_small.jpg ├── noAlpha.png ├── pink_outfit.jpg ├── sample.cr2 ├── sample.icns ├── sample.ico ├── sample.pict ├── sample.webp ├── single_frame.gif ├── someAlpha.png ├── twitterfied.jp2 ├── twitterfied.jpg ├── twitterfied.pjpg ├── twitterfied.png └── twitterfied.tiff ├── TwitterImagePipeline.framework └── Info.plist ├── TwitterImagePipeline.podspec ├── TwitterImagePipeline.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── GraphicsRendererSpeed.xcscheme │ ├── ImageSpeedComparison.xcscheme │ ├── TIP Sample App.xcscheme │ ├── TIP Swift Sample App.xcscheme │ ├── TwitterImagePipeline.framework.xcscheme │ └── libTwitterImagePipeline.a.xcscheme ├── TwitterImagePipeline ├── Project │ ├── NSData+TIPAdditions.h │ ├── NSData+TIPAdditions.m │ ├── NSDictionary+TIPAdditions.h │ ├── NSDictionary+TIPAdditions.m │ ├── NSOperationQueue+TIPSafety.h │ ├── NSOperationQueue+TIPSafety.m │ ├── TIPDefaultImageCodecs.h │ ├── TIPDefaultImageCodecs.m │ ├── TIPGlobalConfiguration+Project.h │ ├── TIPImageCache.h │ ├── TIPImageCacheEntry.h │ ├── TIPImageCacheEntry.m │ ├── TIPImageDiskCache.h │ ├── TIPImageDiskCache.m │ ├── TIPImageDiskCacheTemporaryFile.h │ ├── TIPImageDiskCacheTemporaryFile.m │ ├── TIPImageDownloadInternalContext.h │ ├── TIPImageDownloadInternalContext.m │ ├── TIPImageDownloader.h │ ├── TIPImageDownloader.m │ ├── TIPImageFetchDownloadInternal.h │ ├── TIPImageFetchMetrics+Project.h │ ├── TIPImageFetchOperation+Project.h │ ├── TIPImageMemoryCache.h │ ├── TIPImageMemoryCache.m │ ├── TIPImagePipeline+Project.h │ ├── TIPImagePipelineInspectionResult+Project.h │ ├── TIPImageRenderedCache.h │ ├── TIPImageRenderedCache.m │ ├── TIPImageStoreAndMoveOperations.h │ ├── TIPImageStoreAndMoveOperations.m │ ├── TIPInspectableCache.h │ ├── TIPLRUCache.h │ ├── TIPLRUCache.m │ ├── TIPPartialImage.h │ ├── TIPPartialImage.m │ ├── TIPTiming.h │ ├── TIPTiming.m │ ├── TIPURLStringCoding.h │ ├── TIPURLStringCoding.m │ ├── TIP_Project.h │ ├── TIP_Project.m │ ├── TIP_ProjectCommon.h │ └── TIP_ProjectCommon.m ├── TIPDefinitions.h ├── TIPError.h ├── TIPFileUtils.h ├── TIPFileUtils.m ├── TIPGlobalConfiguration.h ├── TIPGlobalConfiguration.m ├── TIPImageCodecCatalogue.h ├── TIPImageCodecCatalogue.m ├── TIPImageCodecs.h ├── TIPImageCodecs.m ├── TIPImageContainer.h ├── TIPImageContainer.m ├── TIPImageFetchDelegate.h ├── TIPImageFetchDownload.h ├── TIPImageFetchDownload.m ├── TIPImageFetchMetrics.h ├── TIPImageFetchMetrics.m ├── TIPImageFetchOperation.h ├── TIPImageFetchOperation.m ├── TIPImageFetchProgressiveLoadingPolicies.h ├── TIPImageFetchProgressiveLoadingPolicies.m ├── TIPImageFetchProgressiveLoadingPolicy+StaticClass.h ├── TIPImageFetchRequest.h ├── TIPImageFetchRequest.m ├── TIPImageFetchTransformer.h ├── TIPImageFetchable.h ├── TIPImagePipeline.h ├── TIPImagePipeline.m ├── TIPImagePipelineInspectionResult.h ├── TIPImagePipelineInspectionResult.m ├── TIPImageStoreRequest.h ├── TIPImageTypes.h ├── TIPImageTypes.m ├── TIPImageUtils.h ├── TIPImageUtils.m ├── TIPImageViewFetchHelper.h ├── TIPImageViewFetchHelper.m ├── TIPLogger.h ├── TIPProgressive.h ├── TIPSafeOperation.h ├── TIPSafeOperation.m ├── TwitterImagePipeline.h ├── UIImage+TIPAdditions.h ├── UIImage+TIPAdditions.m ├── UIView+TIPImageFetchable.h └── UIView+TIPImageFetchable.m ├── TwitterImagePipelineTests ├── Info.plist ├── TIPImageFetchDelegateTests.m ├── TIPImagePipelineTests.m ├── TIPImageTest.m ├── TIPImageViewTests.m ├── TIPProblematicImagesTest.m ├── TIPTestImageFetchDownloadInternalWithStubbing.h ├── TIPTestImageFetchDownloadInternalWithStubbing.m ├── TIPTestURLProtocol.h ├── TIPTestURLProtocol.m ├── TIPTests.Info.plist ├── TIPTests.h ├── TIPTests.m ├── TIPUtilitiesTests.m └── TwitterImagePipelineTests.m ├── build.sh └── placeholder.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | TwitterImagePipeline.xcodeproj/project.xcworkspace/xcuserdata 21 | TwitterImagePipeline.xcodeproj/xcuserdata 22 | 23 | ## Other 24 | *.xccheckout 25 | *.moved-aside 26 | *.xcuserstate 27 | *.xcscmblueprint 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 38 | # 39 | # Pods/ 40 | 41 | # Carthage 42 | # 43 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 44 | # Carthage/Checkouts 45 | 46 | Carthage/Build 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 54 | 55 | fastlane/report.xml 56 | fastlane/screenshots 57 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode9.3 3 | script: 4 | ./build.sh 5 | -------------------------------------------------------------------------------- /Extended/TIPXMP4Codec.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPXMP4Codec.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/16/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol TIPXMP4DecoderConfig; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | //! Custom image type for MP4 animated images, `@"public.mp4"` 16 | FOUNDATION_EXTERN NSString * const TIPXImageTypeMP4; 17 | 18 | /** 19 | Convenience codec for MP4 animation support. 20 | Requires AVFoundation.framework be linked. 21 | This codec is not bundled with __TIP__ to avoid bloating it with MP4 animated image stuff, 22 | but there's nothing preventing a consumer from using this decoder. 23 | @warning this codec (decoder) definitely works, however it loads the entire animation into RAM. 24 | An MP4 animation that is large (in duration and/or pixels) will lead to memory pressure. 25 | Many MP4 animations will lead to memory pressure. 26 | GIF using native iOS decoding does not have this issue as the UIImage has smarts to offload 27 | the memory usage transparently. 28 | To avoid these pressures, a developer should custom implement a similar behavior to what UIImage 29 | does under the hood for GIFs, which is use a ring buffer of decoded images - decoding and cycling 30 | through the buffer as the animation progresses. This is outside the scope of __TIP__ though. 31 | */ 32 | @interface TIPXMP4Codec : NSObject 33 | /** MP4 decoder */ 34 | @property (nonatomic, readonly) id tip_decoder; 35 | /** MP4 encoder (`nil` at the moment) */ 36 | @property (nonatomic, readonly, nullable) id tip_encoder; 37 | 38 | /** 39 | designated initializer 40 | @param decoderConfig optional `TIPXMP4DecoderConfig` for default decoding behavior 41 | */ 42 | - (instancetype)initWithDefaultDecoderConfig:(nullable id)decoderConfig NS_DESIGNATED_INITIALIZER; 43 | 44 | /** MP4 decoder default config */ 45 | @property(nonatomic, readonly, nullable) id defaultDecoderConfig; 46 | 47 | /** Construct a decoder config */ 48 | + (id)decoderConfigWithMaxDecodableFramesCount:(NSUInteger)max; 49 | 50 | @end 51 | 52 | /** config object for decoding behavior */ 53 | @protocol TIPXMP4DecoderConfig 54 | 55 | /** configure a max number of frames to decode, 0 == unlimited */ 56 | @property (nonatomic, readonly) NSUInteger maxDecodableFramesCount; 57 | 58 | @end 59 | 60 | NS_ASSUME_NONNULL_END 61 | -------------------------------------------------------------------------------- /Extended/TIPXWebPCodec.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPXWebPCodec.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 11/9/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | //! Custom image type for WebP, `@"google.webp"` 14 | FOUNDATION_EXTERN NSString * const TIPXImageTypeWebP; 15 | 16 | /** 17 | Convenience codec for WebP support. 18 | Requires WebP.framework from Google. 19 | This codec is not bundled with __TIP__ to avoid bloating the framework with WebP stuff, 20 | but there's nothing preventing a consumer from using this decoder. 21 | */ 22 | @interface TIPXWebPCodec : NSObject 23 | /** WebP decoder */ 24 | @property (nonatomic, readonly) id tip_decoder; 25 | /** WebP encoder */ 26 | @property (nonatomic, readonly) id tip_encoder; 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /Extended/WebP.framework/Headers/types.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010 Google Inc. All Rights Reserved. 2 | // 3 | // Use of this source code is governed by a BSD-style license 4 | // that can be found in the COPYING file in the root of the source 5 | // tree. An additional intellectual property rights grant can be found 6 | // in the file PATENTS. All contributing project authors may 7 | // be found in the AUTHORS file in the root of the source tree. 8 | // ----------------------------------------------------------------------------- 9 | // 10 | // Common types 11 | // 12 | // Author: Skal (pascal.massimino@gmail.com) 13 | 14 | #ifndef WEBP_WEBP_TYPES_H_ 15 | #define WEBP_WEBP_TYPES_H_ 16 | 17 | #include // for size_t 18 | 19 | #ifndef _MSC_VER 20 | #include 21 | #if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \ 22 | (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) 23 | #define WEBP_INLINE inline 24 | #else 25 | #define WEBP_INLINE 26 | #endif 27 | #else 28 | typedef signed char int8_t; 29 | typedef unsigned char uint8_t; 30 | typedef signed short int16_t; 31 | typedef unsigned short uint16_t; 32 | typedef signed int int32_t; 33 | typedef unsigned int uint32_t; 34 | typedef unsigned long long int uint64_t; 35 | typedef long long int int64_t; 36 | #define WEBP_INLINE __forceinline 37 | #endif /* _MSC_VER */ 38 | 39 | #ifndef WEBP_EXTERN 40 | // This explicitly marks library functions and allows for changing the 41 | // signature for e.g., Windows DLL builds. 42 | # if defined(__GNUC__) && __GNUC__ >= 4 43 | # define WEBP_EXTERN(type) extern __attribute__ ((visibility ("default"))) type 44 | # else 45 | # define WEBP_EXTERN(type) extern type 46 | # endif /* __GNUC__ >= 4 */ 47 | #endif /* WEBP_EXTERN */ 48 | 49 | // Macro to check ABI compatibility (same major revision number) 50 | #define WEBP_ABI_IS_INCOMPATIBLE(a, b) (((a) >> 8) != ((b) >> 8)) 51 | 52 | #endif /* WEBP_WEBP_TYPES_H_ */ 53 | -------------------------------------------------------------------------------- /Extended/WebP.framework/WebP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/Extended/WebP.framework/WebP -------------------------------------------------------------------------------- /GraphicsRendererSpeed/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // GraphicsRendererSpeed 4 | // 5 | // Copyright © 2018 Twitter. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | @property (strong, nonatomic) UIWindow *window; 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /GraphicsRendererSpeed/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // GraphicsRendererSpeed 4 | // 5 | // Copyright © 2018 Twitter. All rights reserved. 6 | // 7 | 8 | #import "AppDelegate.h" 9 | 10 | @interface AppDelegate () 11 | @end 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | // Override point for customization after application launch. 18 | return YES; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/iceland_P3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/iceland_P3.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/iceland_sRGB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/iceland_sRGB.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/italy_P3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/italy_P3.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/italy_sRGB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/italy_sRGB.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/parrot_srgb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/parrot_srgb.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/parrot_wide.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/parrot_wide.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/shoes_adobeRGB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/shoes_adobeRGB.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/shoes_sRGB.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/shoes_sRGB.jpg -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/webkit_P3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/webkit_P3.png -------------------------------------------------------------------------------- /GraphicsRendererSpeed/Supporting Files/webkit_sRGB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/GraphicsRendererSpeed/Supporting Files/webkit_sRGB.png -------------------------------------------------------------------------------- /GraphicsRendererSpeed/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // GraphicsRendererSpeed 4 | // 5 | // Copyright © 2018 Twitter. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface ViewController : UIViewController 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /GraphicsRendererSpeed/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // GraphicsRendererSpeed 4 | // 5 | // Created on 4/17/18. 6 | // Copyright © 2018 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ImageSpeedComparison/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // ImageSpeedComparison 4 | // 5 | // Created on 9/4/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ImageSpeedComparison/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // ImageSpeedComparison 4 | // 5 | // Created on 9/4/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | #import "TIPXWebPCodec.h" 13 | 14 | @interface AppDelegate () 15 | @end 16 | 17 | @interface AppDelegate (Logger) 18 | @end 19 | 20 | @implementation AppDelegate 21 | 22 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 23 | { 24 | [TIPGlobalConfiguration sharedInstance].logger = self; 25 | [TIPImageCodecCatalogue sharedInstance][TIPXImageTypeWebP] = [[TIPXWebPCodec alloc] init]; 26 | return YES; 27 | } 28 | 29 | - (void)tip_logWithLevel:(TIPLogLevel)level file:(NSString *)file function:(NSString *)function line:(int)line message:(NSString *)message 30 | { 31 | NSString *levelString = nil; 32 | switch (level) { 33 | case TIPLogLevelEmergency: 34 | case TIPLogLevelAlert: 35 | case TIPLogLevelCritical: 36 | case TIPLogLevelError: 37 | levelString = @"ERR"; 38 | break; 39 | case TIPLogLevelWarning: 40 | levelString = @"WRN"; 41 | break; 42 | case TIPLogLevelNotice: 43 | case TIPLogLevelInformation: 44 | levelString = @"INF"; 45 | break; 46 | case TIPLogLevelDebug: 47 | levelString = @"DBG"; 48 | break; 49 | } 50 | 51 | NSLog(@"[%@]: %@", levelString, message); 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /ImageSpeedComparison/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /ImageSpeedComparison/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /ImageSpeedComparison/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /ImageSpeedComparison/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // ImageSpeedComparison 4 | // 5 | // Created on 9/4/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /ImageSpeedComparison/fireworks_original.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/fireworks_original.gif -------------------------------------------------------------------------------- /ImageSpeedComparison/interlace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/interlace.png -------------------------------------------------------------------------------- /ImageSpeedComparison/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ImageSpeedComparison 4 | // 5 | // Created on 9/4/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ImageSpeedComparison/no-interlace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/no-interlace.png -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.RLCP.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.RLCP.jp2 -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.RPCL.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.RPCL.jp2 -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.jp2 -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.jpg -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.pjpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.pjpg -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.png -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.small.pjpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.small.pjpg -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.tiff -------------------------------------------------------------------------------- /ImageSpeedComparison/twitterfied.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/ImageSpeedComparison/twitterfied.webp -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); 2 | you may not use this file except in compliance with the License. 3 | You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | -------------------------------------------------------------------------------- /Resources/module.modulemap: -------------------------------------------------------------------------------- 1 | module TwitterImagePipeline { 2 | umbrella "." 3 | 4 | export * 5 | module * { 6 | export * 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /TIP Sample App/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TIP Sample App 4 | // 5 | // Created on 2/3/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TIPImagePipeline; 12 | 13 | #define APP_DELEGATE ((AppDelegate *)[UIApplication sharedApplication].delegate) 14 | 15 | @interface AppDelegate : UIResponder 16 | 17 | @property (strong, nonatomic) UIWindow *window; 18 | @property (strong, nonatomic) UITabBarController *tabBarController; 19 | @property (strong, nonatomic, readonly) TIPImagePipeline *imagePipeline; 20 | 21 | // Mutable settings 22 | 23 | @property (nonatomic) NSUInteger searchCount; 24 | @property (nonatomic) BOOL searchWebP; 25 | @property (nonatomic) BOOL usePlaceholder; 26 | @property (nonatomic, getter=isDebugInfoVisible) BOOL debugInfoVisible; 27 | 28 | // Methods 29 | 30 | - (void)incrementNetworkOperations; 31 | - (void)decrementNetworkOperations; 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /TIP Sample App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /TIP Sample App/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TIP Sample App/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIP Sample App/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /TIP Sample App/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TIP Sample App/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIP Sample App/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /TIP Sample App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TIP Sample App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UIStatusBarTintParameters 30 | 31 | UINavigationBar 32 | 33 | Style 34 | UIBarStyleDefault 35 | Translucent 36 | 37 | 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /TIP Sample App/InspectorViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // InspectorViewController.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/20/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface InspectorViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /TIP Sample App/PipelineCacheInspectionResultsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineCacheInspectionResultsViewController.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/21/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TIPImagePipeline; 12 | @protocol TIPImagePipelineInspectionResultEntry; 13 | 14 | @interface PipelineCacheInspectionResultsViewController : UIViewController 15 | 16 | @property (nonatomic, readonly) BOOL didClearAnyEntries; 17 | 18 | - (instancetype)initWithResults:(NSArray> *)results pipeline:(TIPImagePipeline *)pipeline; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /TIP Sample App/PipelineCacheInspectionResultsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineCacheInspectionResultsViewController.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/21/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "PipelineCacheInspectionResultsViewController.h" 12 | 13 | @interface PipelineCacheInspectionResultsViewController () 14 | { 15 | NSMutableArray> *_results; 16 | TIPImagePipeline *_pipeline; 17 | UITableView *_tableView; 18 | } 19 | 20 | @end 21 | 22 | @implementation PipelineCacheInspectionResultsViewController 23 | 24 | - (instancetype)initWithResults:(NSArray> *)results pipeline:(TIPImagePipeline *)pipeline 25 | { 26 | if (self = [self init]) { 27 | _pipeline = pipeline; 28 | _results = [results mutableCopy]; 29 | } 30 | return self; 31 | } 32 | 33 | - (void)viewDidLoad 34 | { 35 | [super viewDidLoad]; 36 | 37 | _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain]; 38 | _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 39 | _tableView.delegate = self; 40 | _tableView.dataSource = self; 41 | [self.view addSubview:_tableView]; 42 | } 43 | 44 | #pragma mark Table View 45 | 46 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 47 | { 48 | return 1; 49 | } 50 | 51 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 52 | { 53 | return (NSInteger)_results.count; 54 | } 55 | 56 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 57 | { 58 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EntryCell"]; 59 | if (!cell) { 60 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"EntryCell"]; 61 | cell.textLabel.numberOfLines = 3; 62 | cell.textLabel.lineBreakMode = NSLineBreakByTruncatingHead; 63 | } 64 | 65 | id entry = _results[(NSUInteger)indexPath.row]; 66 | cell.imageView.image = entry.image; 67 | cell.imageView.contentMode = UIViewContentModeScaleAspectFit; 68 | if (entry.progress < 1.f) { 69 | cell.textLabel.text = [NSString stringWithFormat:@"(%tu%%) %@\n%@\n%@", (NSUInteger)(entry.progress * 100.f), [NSByteCountFormatter stringFromByteCount:(long long)entry.bytesUsed countStyle:NSByteCountFormatterCountStyleBinary], NSStringFromCGSize(entry.dimensions), entry.identifier]; 70 | } else { 71 | cell.textLabel.text = [NSString stringWithFormat:@"%@\n%@\n%@", [NSByteCountFormatter stringFromByteCount:(long long)entry.bytesUsed countStyle:NSByteCountFormatterCountStyleBinary], NSStringFromCGSize(entry.dimensions), entry.identifier]; 72 | } 73 | 74 | return cell; 75 | } 76 | 77 | - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 78 | { 79 | return 100; 80 | } 81 | 82 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 83 | { 84 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 85 | 86 | const NSUInteger index = (NSUInteger)indexPath.row; 87 | id entry = _results[index]; 88 | UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"Clear entry?" message:entry.identifier preferredStyle:UIAlertControllerStyleActionSheet]; 89 | [alertVC addAction:[UIAlertAction actionWithTitle:@"Clear" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) { 90 | [self _private_clearEntryAtIndexPath:indexPath]; 91 | }]]; 92 | [alertVC addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:NULL]]; 93 | 94 | [self presentViewController:alertVC animated:YES completion:NULL]; 95 | } 96 | 97 | #pragma mark Private 98 | 99 | - (void)_private_clearEntryAtIndexPath:(NSIndexPath *)indexPath 100 | { 101 | const NSUInteger index = (NSUInteger)indexPath.row; 102 | _didClearAnyEntries = YES; 103 | id entry = _results[index]; 104 | [_pipeline clearImageWithIdentifier:entry.identifier]; 105 | [_results removeObjectAtIndex:index]; 106 | [_tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; 107 | } 108 | 109 | @end 110 | -------------------------------------------------------------------------------- /TIP Sample App/PipelineInspectorViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineInspectorViewController.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/21/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TIPImagePipelineInspectionResult; 12 | 13 | @interface PipelineInspectorViewController : UIViewController 14 | 15 | - (instancetype)initWithPipelineInspectionResult:(TIPImagePipelineInspectionResult *)result; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /TIP Sample App/PipelineInspectorViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PipelineInspectorViewController.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/21/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "PipelineCacheInspectionResultsViewController.h" 12 | #import "PipelineInspectorViewController.h" 13 | 14 | @interface PipelineInspectorViewController () 15 | { 16 | TIPImagePipelineInspectionResult *_result; 17 | UITableView *_tableView; 18 | BOOL _shouldAutoPop; 19 | 20 | PipelineCacheInspectionResultsViewController *_presentedResults; 21 | } 22 | 23 | @end 24 | 25 | @implementation PipelineInspectorViewController 26 | 27 | - (instancetype)initWithPipelineInspectionResult:(TIPImagePipelineInspectionResult *)result 28 | { 29 | if (self = [self init]) { 30 | _result = result; 31 | self.navigationItem.title = result.imagePipeline.identifier; 32 | } 33 | return self; 34 | } 35 | 36 | - (void)viewWillAppear:(BOOL)animated 37 | { 38 | [super viewWillAppear:animated]; 39 | 40 | if (_presentedResults) { 41 | _shouldAutoPop = _presentedResults.didClearAnyEntries; 42 | _presentedResults = nil; 43 | } 44 | } 45 | 46 | - (void)viewDidAppear:(BOOL)animated 47 | { 48 | [super viewDidAppear:animated]; 49 | if (_shouldAutoPop) { 50 | [self.navigationController popToRootViewControllerAnimated:YES]; 51 | } 52 | } 53 | 54 | - (void)viewDidLoad 55 | { 56 | [super viewDidLoad]; 57 | 58 | _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped]; 59 | _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; 60 | _tableView.delegate = self; 61 | _tableView.dataSource = self; 62 | [self.view addSubview:_tableView]; 63 | } 64 | 65 | #pragma mark Table View 66 | 67 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 68 | { 69 | return 1; 70 | } 71 | 72 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 73 | { 74 | return 4; 75 | } 76 | 77 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 78 | { 79 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"EntryGroupCell"]; 80 | if (!cell) { 81 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"EntryGroupCell"]; 82 | cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; 83 | } 84 | 85 | switch (indexPath.row) { 86 | case 0: 87 | cell.textLabel.text = [NSString stringWithFormat:@"Rendered Entries (%tu)", _result.completeRenderedEntries.count]; 88 | break; 89 | case 1: 90 | cell.textLabel.text = [NSString stringWithFormat:@"Memory Entries (%tu)", _result.completeMemoryEntries.count]; 91 | break; 92 | case 2: 93 | cell.textLabel.text = [NSString stringWithFormat:@"Incomplete Disk Entries (%tu)", _result.partialDiskEntries.count]; 94 | break; 95 | case 3: 96 | default: 97 | cell.textLabel.text = [NSString stringWithFormat:@"Complete Disk Entries (%tu)", _result.completeDiskEntries.count]; 98 | break; 99 | } 100 | 101 | return cell; 102 | } 103 | 104 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 105 | { 106 | [tableView deselectRowAtIndexPath:indexPath animated:YES]; 107 | 108 | NSArray> *entries = nil; 109 | NSString *name = nil; 110 | switch (indexPath.row) { 111 | case 0: 112 | entries = _result.completeRenderedEntries; 113 | name = @"Rendered"; 114 | break; 115 | case 1: 116 | entries = _result.completeMemoryEntries; 117 | name = @"Memory"; 118 | break; 119 | case 2: 120 | entries = _result.partialDiskEntries; 121 | name = @"Incomplete Disk"; 122 | break; 123 | case 3: 124 | default: 125 | entries = _result.completeDiskEntries; 126 | name = @"Complete Disk"; 127 | break; 128 | } 129 | 130 | _presentedResults = [[PipelineCacheInspectionResultsViewController alloc] initWithResults:entries pipeline:_result.imagePipeline]; 131 | _presentedResults.navigationItem.title = name; 132 | [self.navigationController pushViewController:_presentedResults animated:YES]; 133 | } 134 | 135 | @end 136 | -------------------------------------------------------------------------------- /TIP Sample App/SettingsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.h 3 | // TIP Sample App 4 | // 5 | // Created on 2/3/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SettingsViewController : UIViewController 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /TIP Sample App/TweetImageFetchRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // TweetImageFetchRequest.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/11/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TweetImageInfo; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface TweetImageFetchRequest : NSObject 16 | @property (nonatomic) BOOL forcePlaceholder; 17 | - (instancetype)initWithTweetImage:(TweetImageInfo *)tweet targetView:(nullable UIView *)view; 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /TIP Sample App/TweetImageFetchRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // TweetImageFetchRequest.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/11/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TweetImageFetchRequest.h" 11 | #import "TwitterAPI.h" 12 | 13 | @implementation TweetImageFetchRequest 14 | { 15 | TweetImageInfo *_tweetImage; 16 | NSURL *_imageURL; 17 | } 18 | 19 | @synthesize targetDimensions = _targetDimensions; 20 | @synthesize targetContentMode = _targetContentMode; 21 | 22 | - (instancetype)initWithTweetImage:(TweetImageInfo *)tweetImage targetView:(UIView *)view 23 | { 24 | if (self = [super init]) { 25 | _tweetImage = tweetImage; 26 | _targetContentMode = view.contentMode; 27 | _targetDimensions = TIPDimensionsFromView(view); 28 | } 29 | return self; 30 | } 31 | 32 | - (NSString *)imageIdentifier 33 | { 34 | return _tweetImage.baseURLString; 35 | } 36 | 37 | - (NSURL *)imageURL 38 | { 39 | if (!_imageURL) { 40 | if (self.forcePlaceholder) { 41 | _imageURL = [NSURL URLWithString:@"placeholder://placeholder.com/placeholder.jpg"]; 42 | } else { 43 | NSString *URLString = nil; 44 | if ([_tweetImage.baseURLString hasPrefix:@"https://pbs.twimg.com/media/"]) { 45 | NSString *variantName = TweetImageDetermineVariant(_tweetImage.originalDimensions, _targetDimensions, _targetContentMode); 46 | URLString = [NSString stringWithFormat:@"%@?format=%@&name=%@", _tweetImage.baseURLString, (APP_DELEGATE.searchWebP) ? @"webp" : _tweetImage.format, variantName]; 47 | } else { 48 | URLString = [NSString stringWithFormat:@"%@.%@", _tweetImage.baseURLString, _tweetImage.format]; 49 | } 50 | _imageURL = [NSURL URLWithString:URLString]; 51 | } 52 | } 53 | return _imageURL; 54 | } 55 | 56 | - (TIPImageFetchOptions)options 57 | { 58 | return (self.forcePlaceholder) ? TIPImageFetchTreatAsPlaceholder : TIPImageFetchNoOptions; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /TIP Sample App/TwitterAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterAPI.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/3/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface TweetImageInfo : NSObject 15 | 16 | @property (nonatomic, readonly, copy) NSString *baseURLString; 17 | @property (nonatomic, readonly, copy) NSString *format; 18 | @property (nonatomic, readonly) CGSize originalDimensions; 19 | 20 | @end 21 | 22 | @interface TweetInfo : NSObject 23 | 24 | @property (nonatomic, readonly, copy) NSString *handle; 25 | @property (nonatomic, readonly, copy) NSString *text; 26 | @property (nonatomic, readonly, copy) NSArray *images; 27 | 28 | @end 29 | 30 | @protocol TwitterAPIDelegate; 31 | 32 | @interface TwitterAPI : NSObject 33 | 34 | @property (nonatomic, weak) id delegate; 35 | 36 | + (instancetype)sharedInstance; 37 | 38 | - (void)searchForTerm:(NSString *)term count:(NSUInteger)count complete:(void (^)(NSArray *, NSError *))complete; 39 | 40 | @end 41 | 42 | @protocol TwitterAPIDelegate 43 | 44 | @optional 45 | - (void)APIWorkStarted:(TwitterAPI *)api; 46 | - (void)APIWorkFinished:(TwitterAPI *)api; 47 | 48 | @end 49 | 50 | FOUNDATION_EXTERN NSString *TweetImageDetermineVariant(CGSize aspectRatio, const CGSize targetDimensions, UIViewContentMode targetContentMode); 51 | 52 | NS_ASSUME_NONNULL_END 53 | -------------------------------------------------------------------------------- /TIP Sample App/TwitterSearchViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterSearchViewController.h 3 | // TIP Sample App 4 | // 5 | // Created on 2/3/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TwitterSearchViewController : UIViewController 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /TIP Sample App/ZoomingTweetImageViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ZoomingTweetImageViewController.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/11/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TweetImageInfo; 12 | 13 | @interface ZoomingTweetImageViewController : UIViewController 14 | 15 | - (instancetype)initWithTweetImage:(TweetImageInfo *)imageInfo; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /TIP Sample App/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TIP Sample App 4 | // 5 | // Created on 2/3/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) 13 | { 14 | @autoreleasepool { 15 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TIP Swift Sample App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /TIP Swift Sample App/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "first.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TIP Swift Sample App/Assets.xcassets/first.imageset/first.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIP Swift Sample App/Assets.xcassets/first.imageset/first.pdf -------------------------------------------------------------------------------- /TIP Swift Sample App/Assets.xcassets/second.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "second.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /TIP Swift Sample App/Assets.xcassets/second.imageset/second.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIP Swift Sample App/Assets.xcassets/second.imageset/second.pdf -------------------------------------------------------------------------------- /TIP Swift Sample App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TIP Swift Sample App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UIStatusBarTintParameters 30 | 31 | UINavigationBar 32 | 33 | Style 34 | UIBarStyleDefault 35 | Translucent 36 | 37 | 38 | 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /TIP Swift Sample App/Sample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Sample-Bridging-Header.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/2/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "InspectorViewController.h" 12 | #import "PipelineCacheInspectionResultsViewController.h" 13 | #import "PipelineInspectorViewController.h" 14 | #import "SettingsViewController.h" 15 | #import "TIPXMP4Codec.h" 16 | #import "TIPXWebPCodec.h" 17 | #import "TwitterAPI.h" 18 | -------------------------------------------------------------------------------- /TIP Swift Sample App/TweetImageFetchRequest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TweetImageFetchRequest.swift 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/3/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | //let kSMALL = "small" 10 | //let kMEDIUM = "medium" 11 | //let kLARGE = "large" 12 | // 13 | //struct VariantInfo { 14 | // var name: String 15 | // var dim: CGFloat 16 | // 17 | // init(_ name: String, dimensions dim: CGFloat) { 18 | // self.name = name 19 | // self.dim = dim 20 | // } 21 | //} 22 | // 23 | //let sVariantSizeMap: [VariantInfo] = [ 24 | // VariantInfo.init(kSMALL, dimensions: 680), 25 | // VariantInfo.init(kMEDIUM, dimensions: 1200), 26 | // VariantInfo.init(kLARGE, dimensions: 2048) 27 | //] 28 | 29 | class TweetImageFetchRequest: NSObject, TIPImageFetchRequest { 30 | 31 | var forcePlaceholder: Bool 32 | 33 | private let tweetImageInfo: TweetImageInfo 34 | private var internalImageURL: URL? 35 | 36 | @objc var targetDimensions: CGSize 37 | @objc var targetContentMode: UIViewContentMode 38 | 39 | @objc var imageIdentifier: String? { 40 | return self.tweetImageInfo.baseURLString 41 | } 42 | 43 | @objc var imageURL: URL { 44 | if nil == self.internalImageURL { 45 | if self.forcePlaceholder { 46 | self.internalImageURL = URL.init(string: "placeholder://placeholder.com/placeholder.jpg") 47 | } else { 48 | let URLString: String 49 | if self.tweetImageInfo.baseURLString.hasPrefix("https://pbs.twimg.com/media/") { 50 | let variantName = TweetImageDetermineVariant(self.tweetImageInfo.originalDimensions, self.targetDimensions, self.targetContentMode) 51 | let format = APP_DELEGATE().searchWebP ? "webp" : self.tweetImageInfo.format 52 | URLString = "\(self.tweetImageInfo.baseURLString)?format=\(format)&name=\(variantName)" 53 | } else { 54 | URLString = "\(self.tweetImageInfo.baseURLString).\(self.tweetImageInfo.format)" 55 | } 56 | self.internalImageURL = URL.init(string: URLString) 57 | } 58 | } 59 | 60 | return self.internalImageURL! 61 | } 62 | @objc var options: TIPImageFetchOptions { 63 | return self.forcePlaceholder ? [.treatAsPlaceholder] : [] 64 | } 65 | 66 | init(tweetImage tweet: TweetImageInfo, targetView view: UIView?) 67 | { 68 | self.forcePlaceholder = false 69 | self.tweetImageInfo = tweet 70 | self.targetContentMode = view?.contentMode ?? .center 71 | self.targetDimensions = TIPDimensionsFromView(view) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /TIPTestsResources.bundle/1538x2048.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/1538x2048.jpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/200w.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/200w.mp4 -------------------------------------------------------------------------------- /TIPTestsResources.bundle/Info.plist: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/Info.plist -------------------------------------------------------------------------------- /TIPTestsResources.bundle/allAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/allAlpha.png -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.bmp -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.jp2 -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.jpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.pjpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.pjpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.png -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.tga -------------------------------------------------------------------------------- /TIPTestsResources.bundle/carnival.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/carnival.tiff -------------------------------------------------------------------------------- /TIPTestsResources.bundle/fireworks.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/fireworks.gif -------------------------------------------------------------------------------- /TIPTestsResources.bundle/fireworks_original.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/fireworks_original.gif -------------------------------------------------------------------------------- /TIPTestsResources.bundle/jpeg2k.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/jpeg2k.jp2 -------------------------------------------------------------------------------- /TIPTestsResources.bundle/logo_only_final_reasonably_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/logo_only_final_reasonably_small.jpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/noAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/noAlpha.png -------------------------------------------------------------------------------- /TIPTestsResources.bundle/pink_outfit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/pink_outfit.jpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/sample.cr2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/sample.cr2 -------------------------------------------------------------------------------- /TIPTestsResources.bundle/sample.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/sample.icns -------------------------------------------------------------------------------- /TIPTestsResources.bundle/sample.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/sample.ico -------------------------------------------------------------------------------- /TIPTestsResources.bundle/sample.pict: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/sample.pict -------------------------------------------------------------------------------- /TIPTestsResources.bundle/sample.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/sample.webp -------------------------------------------------------------------------------- /TIPTestsResources.bundle/single_frame.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/single_frame.gif -------------------------------------------------------------------------------- /TIPTestsResources.bundle/someAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/someAlpha.png -------------------------------------------------------------------------------- /TIPTestsResources.bundle/twitterfied.jp2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/twitterfied.jp2 -------------------------------------------------------------------------------- /TIPTestsResources.bundle/twitterfied.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/twitterfied.jpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/twitterfied.pjpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/twitterfied.pjpg -------------------------------------------------------------------------------- /TIPTestsResources.bundle/twitterfied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/twitterfied.png -------------------------------------------------------------------------------- /TIPTestsResources.bundle/twitterfied.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/TIPTestsResources.bundle/twitterfied.tiff -------------------------------------------------------------------------------- /TwitterImagePipeline.framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(CURRENT_PROJECT_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /TwitterImagePipeline.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TwitterImagePipeline' 3 | s.version = '2.9.3' 4 | s.summary = 'Twitter Image Pipeline is a robust and performant image loading and caching framework for iOS' 5 | s.description = 'Twitter created a framework for image loading/caching in order to fulfill the numerous needs of Twitter for iOS including being fast, safe, modular and versatile.' 6 | s.homepage = 'https://github.com/twitter/ios-twitter-logging-service' 7 | s.license = { :type => 'Apache License, Version 2.0', :file => 'LICENSE' } 8 | s.author = { 'Twitter' => 'opensource@twitter.com' } 9 | s.source = { :git => 'https://github.com/twitter/ios-twitter-image-pipeline.git', :tag => s.version.to_s } 10 | s.ios.deployment_target = '7.0' 11 | 12 | s.subspec 'Default' do |sp| 13 | sp.source_files = 'TwitterImagePipeline/**/*' 14 | sp.public_header_files = 'TwitterImagePipeline/*.h' 15 | end 16 | 17 | s.subspec 'ObjC' do |sp| 18 | sp.source_files = 'TwitterImagePipeline/**/*.{h,m,c,cpp,mm}' 19 | sp.public_header_files = 'TwitterImagePipeline/*.h' 20 | end 21 | 22 | s.default_subspec = 'Default' 23 | end 24 | -------------------------------------------------------------------------------- /TwitterImagePipeline.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TwitterImagePipeline.xcodeproj/xcshareddata/xcschemes/GraphicsRendererSpeed.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /TwitterImagePipeline.xcodeproj/xcshareddata/xcschemes/ImageSpeedComparison.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /TwitterImagePipeline.xcodeproj/xcshareddata/xcschemes/TIP Sample App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /TwitterImagePipeline.xcodeproj/xcshareddata/xcschemes/TIP Swift Sample App.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /TwitterImagePipeline.xcodeproj/xcshareddata/xcschemes/libTwitterImagePipeline.a.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/NSData+TIPAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+TIPAdditions.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 9/9/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSData (TIPAdditions) 14 | - (NSData *)tip_safeSubdataNoCopyWithRange:(NSRange)range; 15 | - (NSString *)tip_hexStringValue; 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | 20 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/NSData+TIPAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+TIPAdditions.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 9/9/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSData+TIPAdditions.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @implementation NSData (TIPAdditions) 15 | 16 | - (NSData *)tip_safeSubdataNoCopyWithRange:(NSRange)range 17 | { 18 | if (range.location == 0 && range.length == self.length) { 19 | return self; 20 | } 21 | 22 | // TODO: optimize so that self.bytes doesn't have to be called, 23 | // since it triggers a copy of all bytes when the NSData has non-continuous data 24 | 25 | const void *bytePtr = self.bytes + range.location; 26 | NSData *data = [NSData dataWithBytesNoCopy:(void *)bytePtr length:range.length freeWhenDone:NO]; 27 | objc_setAssociatedObject(data, _cmd, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 28 | return data; 29 | } 30 | 31 | - (NSString *)tip_hexStringValue 32 | { 33 | static const unsigned char hexLookup[] = "0123456789abcdef"; 34 | const NSUInteger hexLength = self.length * 2; 35 | if (!hexLength) { 36 | return @""; 37 | } 38 | 39 | unichar* hexChars = (unichar*)malloc(sizeof(unichar) * (hexLength)); 40 | __block unichar *hexCharPtr = hexChars; 41 | [self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { 42 | unsigned char *bytePtr = (unsigned char *)bytes; 43 | for (NSUInteger i = 0; i < byteRange.length; ++i) { 44 | const unsigned char byte = *bytePtr++; 45 | *hexCharPtr++ = hexLookup[(byte >> 4) & 0xF]; 46 | *hexCharPtr++ = hexLookup[byte & 0xF]; 47 | } 48 | }]; 49 | 50 | return [[NSString alloc] initWithCharactersNoCopy:hexChars length:hexLength freeWhenDone:YES]; 51 | } 52 | 53 | @end 54 | 55 | NS_ASSUME_NONNULL_END 56 | 57 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/NSDictionary+TIPAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+TIPAdditions.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 9/9/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSDictionary (TIPAdditions) 14 | 15 | - (nullable NSArray *)tip_objectsForCaseInsensitiveKey:(NSString *)key; 16 | - (nullable NSSet *)tip_keysMatchingCaseInsensitiveKey:(NSString *)key; 17 | - (id)tip_copyWithLowercaseKeys; 18 | - (id)tip_copyWithUppercaseKeys; 19 | - (id)tip_mutableCopyWithLowercaseKeys; 20 | - (id)tip_mutableCopyWithUppercaseKeys; 21 | 22 | @end 23 | 24 | @interface NSMutableDictionary (TIPAdditions) 25 | 26 | - (void)tip_removeObjectsForCaseInsensitiveKey:(NSString *)key; 27 | - (void)tip_setObject:(id)object forCaseInsensitiveKey:(NSString *)key; 28 | - (void)tip_makeAllKeysLowercase; 29 | - (void)tip_makeAllKeysUppercase; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | 35 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/NSDictionary+TIPAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+TIPAdditions.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 9/9/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "NSDictionary+TIPAdditions.h" 10 | #import "TIP_Project.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @implementation NSDictionary (TIPAdditions) 15 | 16 | - (nullable NSSet *)tip_keysMatchingCaseInsensitiveKey:(NSString *)key 17 | { 18 | NSMutableSet *keys = nil; 19 | TIPAssert([key isKindOfClass:[NSString class]]); 20 | if ([key isKindOfClass:[NSString class]]) { 21 | for (NSString *otherKey in self.allKeys) { 22 | TIPAssert([key isKindOfClass:[NSString class]]); 23 | if ([otherKey caseInsensitiveCompare:key] == NSOrderedSame) { // TWITTER_STYLE_CASE_INSENSITIVE_COMPARE_NIL_PRECHECKED 24 | if (!keys) { 25 | keys = [NSMutableSet set]; 26 | } 27 | [keys addObject:otherKey]; 28 | } 29 | } 30 | } 31 | return keys; 32 | } 33 | 34 | - (nullable NSArray *)tip_objectsForCaseInsensitiveKey:(NSString *)key 35 | { 36 | TIPAssert(key); 37 | NSSet *keys = [self tip_keysMatchingCaseInsensitiveKey:key]; 38 | NSMutableArray *objects = (keys.count > 0) ? [NSMutableArray array] : nil; 39 | for (NSString *otherKey in keys) { 40 | [objects addObject:self[otherKey]]; 41 | } 42 | return objects; 43 | } 44 | 45 | - (id)tip_copyWithLowercaseKeys 46 | { 47 | return [self tip_copyToMutable:NO uppercase:NO]; 48 | } 49 | 50 | - (id)tip_copyWithUppercaseKeys 51 | { 52 | return [self tip_copyToMutable:NO uppercase:YES]; 53 | } 54 | 55 | - (id)tip_mutableCopyWithLowercaseKeys 56 | { 57 | return [self tip_copyToMutable:YES uppercase:NO]; 58 | } 59 | 60 | - (id)tip_mutableCopyWithUppercaseKeys 61 | { 62 | return [self tip_copyToMutable:YES uppercase:YES]; 63 | } 64 | 65 | - (id)tip_copyToMutable:(BOOL)mutable uppercase:(BOOL)uppercase 66 | { 67 | NSMutableDictionary *d = [[NSMutableDictionary alloc] init]; 68 | for (NSString *key in self) { 69 | d[(uppercase) ? key.uppercaseString : key.lowercaseString] = self[key]; 70 | } 71 | return (mutable) ? d : [d copy]; 72 | } 73 | 74 | @end 75 | 76 | @implementation NSMutableDictionary (TIPAdditions) 77 | 78 | - (void)tip_removeObjectsForCaseInsensitiveKey:(NSString *)key 79 | { 80 | TIPAssert(key); 81 | NSArray *keys = [[self tip_keysMatchingCaseInsensitiveKey:key] allObjects]; 82 | if (keys) { 83 | [self removeObjectsForKeys:keys]; 84 | } 85 | } 86 | 87 | - (void)tip_setObject:(id)object forCaseInsensitiveKey:(NSString *)key 88 | { 89 | TIPAssert(key); 90 | [self tip_removeObjectsForCaseInsensitiveKey:key]; 91 | self[key] = object; 92 | } 93 | 94 | - (void)tip_makeAllKeysLowercase 95 | { 96 | NSDictionary *d = [self tip_mutableCopyWithLowercaseKeys]; 97 | [self removeAllObjects]; 98 | [self addEntriesFromDictionary:d]; 99 | } 100 | 101 | - (void)tip_makeAllKeysUppercase 102 | { 103 | NSDictionary *d = [self tip_mutableCopyWithUppercaseKeys]; 104 | [self removeAllObjects]; 105 | [self addEntriesFromDictionary:d]; 106 | } 107 | 108 | @end 109 | 110 | NS_ASSUME_NONNULL_END 111 | 112 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/NSOperationQueue+TIPSafety.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSOperationQueue+TIPSafety.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/14/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | Long story short, QoS in iOS 8 can lead to a crash with async NSOperations. 15 | This is a workaround that doesn't eliminate the risk of the crash but mitigates it by 99.9%. 16 | */ 17 | @interface NSOperationQueue (TIPSafety) 18 | 19 | /** 20 | Same as `[NSOperationQueue addOperation:]` but with added safety. 21 | If _op_ returns `YES` for `isAsynchronous`, the operation will be retained for a period that 22 | extends beyond the lifetime of the operation executing to avoid a crash. 23 | If called on an OS version that doesn't have the bug, will just pass through to `addOperation:` 24 | */ 25 | - (void)tip_safeAddOperation:(NSOperation *)op; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPDefaultImageCodecs.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPDefaultImageCodecs.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 11/7/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageCodecs.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | #pragma mark - Codecs 14 | 15 | @interface TIPBasicCGImageSourceCodec : NSObject 16 | 17 | @property (nonatomic, readonly) id tip_decoder; 18 | @property (nullable, nonatomic, readonly) id tip_encoder; 19 | @property (nonatomic, readonly) BOOL tip_isAnimated; 20 | 21 | + (nullable instancetype)codecWithImageType:(NSString *)imageType; 22 | 23 | - (instancetype)init NS_UNAVAILABLE; 24 | + (instancetype)new NS_UNAVAILABLE; 25 | 26 | @end 27 | 28 | #pragma mark - Decoders 29 | 30 | @interface TIPBasicCGImageSourceDecoder : NSObject 31 | @end 32 | 33 | @interface TIPJPEGCGImageSourceDecoder : TIPBasicCGImageSourceDecoder 34 | @end 35 | 36 | #pragma mark - Encoders 37 | 38 | @interface TIPBasicCGImageSourceEncoder : NSObject 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPGlobalConfiguration+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPGlobalConfiguration+Project.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 10/1/15. 6 | // Copyright © 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPGlobalConfiguration.h" 10 | #import "TIPImageCache.h" 11 | 12 | @protocol TIPImageFetchDownload; 13 | @protocol TIPImageFetchDownloadContext; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface TIPGlobalConfiguration () 18 | 19 | // only accessible from queueForDiskCaches 20 | @property (nonatomic) SInt16 internalMaxCountForAllDiskCaches; 21 | @property (nonatomic) SInt64 internalMaxBytesForAllDiskCaches; 22 | @property (nonatomic) SInt64 internalMaxBytesForDiskCacheEntry; 23 | @property (nonatomic) SInt16 internalTotalCountForAllDiskCaches; 24 | @property (nonatomic) SInt64 internalTotalBytesForAllDiskCaches; 25 | 26 | // only accessible from queueForMemoryCaches 27 | @property (nonatomic) SInt16 internalMaxCountForAllMemoryCaches; 28 | @property (nonatomic) SInt64 internalMaxBytesForAllMemoryCaches; 29 | @property (nonatomic) SInt64 internalMaxBytesForMemoryCacheEntry; 30 | @property (nonatomic) SInt16 internalTotalCountForAllMemoryCaches; 31 | @property (nonatomic) SInt64 internalTotalBytesForAllMemoryCaches; 32 | 33 | // only accessible from main thread 34 | @property (nonatomic) SInt16 internalMaxCountForAllRenderedCaches; 35 | @property (nonatomic) SInt64 internalMaxBytesForAllRenderedCaches; 36 | @property (nonatomic) SInt64 internalMaxBytesForRenderedCacheEntry; 37 | @property (nonatomic) SInt16 internalTotalCountForAllRenderedCaches; 38 | @property (nonatomic) SInt64 internalTotalBytesForAllRenderedCaches; 39 | 40 | // shared queues 41 | // The TIP caches can execute a LOT of Cocoa code which can pile up with autoreleases. 42 | // Since queues don't immediately clear their autorelease pools, the time when these objects 43 | // will be disposed is undefined and can be very long lived. 44 | // Given the amount of large objects in TIP (images), we will be agressive with our autoreleasing 45 | // and will use `tip_dispatch_[a]sync_autoreleasing` functions to wrap TIP cache queue block 46 | // execution with `@autoreleasepool`. 47 | @property (nonatomic, readonly) dispatch_queue_t queueForMemoryCaches; 48 | @property (nonatomic, readonly) dispatch_queue_t queueForDiskCaches; 49 | - (dispatch_queue_t)queueForCachesOfType:(TIPImageCacheType)type; 50 | 51 | // other properties 52 | @property (atomic, readonly) NSArray> *allImagePipelineObservers; 53 | @property (atomic, nullable, strong) id internalLogger; 54 | @property (nonatomic, readonly) BOOL imageFetchDownloadProviderSupportsStubbing; 55 | 56 | // per cache type accessors 57 | - (SInt16)internalMaxCountForAllCachesOfType:(TIPImageCacheType)type; 58 | - (SInt16)internalTotalCountForAllCachesOfType:(TIPImageCacheType)type; 59 | - (SInt64)internalMaxBytesForAllCachesOfType:(TIPImageCacheType)type; 60 | - (SInt64)internalTotalBytesForAllCachesOfType:(TIPImageCacheType)type; 61 | - (SInt64)internalMaxBytesForCacheEntryOfType:(TIPImageCacheType)type; 62 | 63 | // methods 64 | 65 | - (void)enqueueImagePipelineOperation:(NSOperation *)op; 66 | 67 | - (void)postProblem:(NSString *)problemName 68 | userInfo:(NSDictionary *)userInfo; 69 | - (void)accessedCGContext:(BOOL)seriallyAccessed 70 | duration:(NSTimeInterval)duration 71 | isMainThread:(BOOL)mainThread; 72 | 73 | // must call from correct queue (queueForMemoryCaches for memory caches, queueForDiskCaches for disk caches and main queue for rendered caches) 74 | - (void)pruneAllCachesOfType:(TIPImageCacheType)type 75 | withPriorityCache:(nullable id)priorityCache; 76 | - (void)pruneAllCachesOfType:(TIPImageCacheType)type 77 | withPriorityCache:(nullable id)priorityCache 78 | toGlobalMaxBytes:(SInt64)globalMaxBytes 79 | toGlobalMaxCount:(SInt16)globalMaxCount; 80 | 81 | - (id)createImageFetchDownloadWithContext:(id)context; 82 | 83 | @end 84 | 85 | NS_ASSUME_NONNULL_END 86 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageCache.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 10/5/15. 6 | // Copyright © 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TIPLRUCache; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | typedef NS_ENUM(NSInteger, TIPImageCacheType) 16 | { 17 | TIPImageCacheTypeRendered, 18 | TIPImageCacheTypeMemory, 19 | TIPImageCacheTypeDisk, 20 | }; 21 | 22 | #pragma mark - TIPImageCache 23 | 24 | @protocol TIPImageCache 25 | @property (nonatomic, readonly) TIPImageCacheType cacheType; 26 | @property (nonatomic, readonly) TIPLRUCache *manifest; 27 | @property (nonatomic, readonly) NSUInteger totalCost; // not thread safe!! Be careful...should only be used for debugging/testing 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageCacheEntry.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageCacheEntry.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TIPImageCache.h" 12 | #import "TIPImageContainer.h" 13 | #import "TIPImageUtils.h" 14 | #import "TIPLRUCache.h" 15 | 16 | #pragma mark - External Forward Declarations 17 | 18 | @class TIPImageDiskCacheTemporaryFile; 19 | @class TIPPartialImage; 20 | 21 | #pragma mark - Forward Declarations for this Header 22 | 23 | @class TIPImageCacheEntryContext; // Abstract base class 24 | @class TIPCompleteImageEntryContext; 25 | @class TIPPartialImageEntryContext; 26 | 27 | @class TIPImageCacheEntry; // base class 28 | @class TIPImageMemoryCacheEntry; 29 | @class TIPImageDiskCacheEntry; 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | #pragma mark - Context Declarations 34 | 35 | // Abstract base class 36 | @interface TIPImageCacheEntryContext : NSObject 37 | 38 | @property (nonatomic) BOOL updateExpiryOnAccess; 39 | @property (nonatomic) BOOL treatAsPlaceholder; 40 | @property (nonatomic) NSTimeInterval TTL; 41 | @property (nonatomic, nullable) NSURL *URL; 42 | @property (nonatomic, nullable) NSDate *lastAccess; 43 | @property (nonatomic, getter=isAnimated) BOOL animated; 44 | 45 | @property (nonatomic) CGSize dimensions; // pixel size, not point size 46 | 47 | @end 48 | 49 | @interface TIPCompleteImageEntryContext : TIPImageCacheEntryContext 50 | 51 | @property (nonatomic, copy, nullable) NSString *imageType; 52 | 53 | @end 54 | 55 | @interface TIPPartialImageEntryContext : TIPImageCacheEntryContext 56 | 57 | @property (nonatomic) NSUInteger expectedContentLength; 58 | @property (nonatomic, copy, nullable) NSString *lastModified; 59 | 60 | @end 61 | 62 | #pragma mark - Cache Entry Declarations 63 | 64 | // Base class 65 | @interface TIPImageCacheEntry : NSObject 66 | 67 | @property (nonatomic, copy, nullable) NSString *identifier; 68 | 69 | @property (nonatomic, nullable) TIPImageContainer *completeImage; 70 | @property (nonatomic, nullable) TIPCompleteImageEntryContext *completeImageContext; 71 | 72 | @property (nonatomic, nullable) TIPPartialImage *partialImage; 73 | @property (nonatomic, nullable) TIPPartialImageEntryContext *partialImageContext; 74 | 75 | - (BOOL)isValid:(BOOL)mustHaveSomeImage; 76 | 77 | #pragma mark TIPLRUEntry 78 | 79 | @property (nonatomic, nullable) TIPImageCacheEntry *nextLRUEntry; 80 | @property (nonatomic, weak, nullable) TIPImageCacheEntry *previousLRUEntry; 81 | 82 | @end 83 | 84 | @interface TIPImageMemoryCacheEntry : TIPImageCacheEntry 85 | @end 86 | 87 | @interface TIPImageDiskCacheEntry : TIPImageCacheEntry 88 | 89 | @property (nonatomic, nullable) TIPImageDiskCacheTemporaryFile *tempFile; 90 | 91 | @end 92 | 93 | #pragma mark - Private 94 | 95 | @interface TIPImageCacheEntry (Access) 96 | 97 | - (nullable NSDate *)mostRecentAccess; 98 | 99 | @end 100 | 101 | @interface TIPImageCacheEntry (Store) 102 | @property (nonatomic, nullable) NSData *completeImageData; // only for storing 103 | @property (nonatomic, nullable, copy) NSString *completeImageFilePath; // only for storing 104 | @end 105 | 106 | @interface TIPImageMemoryCacheEntry (MemoryCache) 107 | 108 | // Used by Memory Cache 109 | @property (nonatomic, readonly) NSUInteger memoryCost; 110 | 111 | @end 112 | 113 | @interface TIPImageDiskCacheEntry (DiskCache) 114 | 115 | // Used by Disk Cache 116 | @property (nonatomic, readonly, nullable, copy) NSString *safeIdentifier; 117 | @property (nonatomic) NSUInteger completeFileSize; 118 | @property (nonatomic) NSUInteger partialFileSize; 119 | 120 | @end 121 | 122 | NS_ASSUME_NONNULL_END 123 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageDiskCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageDiskCache.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/3/15. 6 | // Copyright (c) 2015 Twitter, Inc. All rights reserved. 7 | // 8 | 9 | #import "TIPImageCacheEntry.h" 10 | #import "TIPInspectableCache.h" 11 | 12 | @class TIPImageDiskCacheTemporaryFile; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | typedef NS_OPTIONS(NSInteger, TIPImageDiskCacheFetchOptions) { 17 | TIPImageDiskCacheFetchOptionsNone = 0, // effectively a touch 18 | TIPImageDiskCacheFetchOptionCompleteImage = (1 << 0), 19 | TIPImageDiskCacheFetchOptionPartialImage = (1 << 1), 20 | TIPImageDiskCacheFetchOptionTemporaryFile = (1 << 2), 21 | 22 | TIPImageDiskCacheFetchOptionPartialImageIfNoCompleteImage = (1 << 3), 23 | TIPImageDiskCacheFetchOptionTemporaryFileIfNoCompleteImage = (1 << 4), 24 | }; 25 | 26 | @interface TIPImageDiskCache : NSObject 27 | 28 | @property (nonatomic, readonly, copy) NSString *cachePath; 29 | 30 | - (instancetype)initWithPath:(NSString *)cachePath; 31 | 32 | - (nullable TIPImageDiskCacheEntry *)imageEntryForIdentifier:(NSString *)identifier 33 | options:(TIPImageDiskCacheFetchOptions)options 34 | decoderConfigMap:(nullable NSDictionary *)decoderConfigMap; 35 | - (void)updateImageEntry:(TIPImageCacheEntry *)entry 36 | forciblyReplaceExisting:(BOOL)force; 37 | - (void)touchImageWithIdentifier:(NSString *)imageIdentifier 38 | orSaveImageEntry:(nullable TIPImageDiskCacheEntry *)entry; 39 | - (void)clearImageWithIdentifier:(NSString *)identifier; 40 | - (void)clearAllImages:(void (^ __nullable)(void))completion; 41 | - (void)prune; 42 | - (TIPImageDiskCacheTemporaryFile *)openTemporaryFileForImageIdentifier:(NSString *)imageIdentifier; 43 | - (nullable NSString *)copyImageEntryFileForIdentifier:(NSString *)identifier 44 | error:(out NSError * __nullable * __nullable)error; 45 | - (BOOL)renameImageEntryWithIdentifier:(NSString *)oldIdentifier 46 | toIdentifier:(NSString *)newIdentifier 47 | error:(NSError * __nullable * __nullable)error; 48 | 49 | @end 50 | 51 | @interface TIPImageDiskCache (TempFile) 52 | 53 | - (void)finalizeTemporaryFile:(TIPImageDiskCacheTemporaryFile *)tempFile 54 | withContext:(TIPImageCacheEntryContext *)context; 55 | - (void)clearTemporaryFilePath:(NSString *)filePath; 56 | 57 | @end 58 | 59 | @interface TIPImageDiskCache (PrivateExposed) 60 | - (TIPLRUCache *)diskCache_syncAccessManifest; 61 | - (nullable NSString *)diskCache_imageEntryFilePathForIdentifier:(NSString *)identifier 62 | hitShouldMoveEntryToHead:(BOOL)hitToHead 63 | context:(out TIPImageCacheEntryContext * __nullable * __nullable)context; 64 | - (void)diskCache_updateImageEntry:(TIPImageCacheEntry *)entry 65 | forciblyReplaceExisting:(BOOL)force; 66 | - (nullable TIPImageDiskCacheEntry *)diskCache_imageEntryForIdentifier:(NSString *)identifier 67 | options:(TIPImageDiskCacheFetchOptions)options 68 | decoderConfigMap:(nullable NSDictionary *)decoderConfigMap; 69 | @end 70 | 71 | NS_ASSUME_NONNULL_END 72 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageDiskCacheTemporaryFile.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageDiskCacheTemporaryFile.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TIPImageCacheEntry.h" 12 | 13 | @class TIPImageDiskCache; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface TIPImageDiskCacheTemporaryFile : NSObject 18 | 19 | @property (nonatomic, readonly, copy) NSString *imageIdentifier; 20 | 21 | - (NSUInteger)appendData:(nullable NSData *)data; 22 | - (void)finalizeWithContext:(TIPImageCacheEntryContext *)context; 23 | 24 | + (instancetype)new NS_UNAVAILABLE; 25 | - (instancetype)init NS_UNAVAILABLE; 26 | 27 | @end 28 | 29 | #pragma mark - Private 30 | 31 | @interface TIPImageDiskCacheTemporaryFile (DiskCache) 32 | @property (nonatomic, readonly, copy) NSString *temporaryPath; 33 | @property (nonatomic, readonly, copy) NSString *finalPath; 34 | - (instancetype)initWithIdentifier:(NSString *)identifier 35 | temporaryPath:(NSString *)tempPath 36 | finalPath:(NSString *)finalPath 37 | diskCache:(nullable TIPImageDiskCache *)diskCache; 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageDiskCacheTemporaryFile.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageDiskCacheTemporaryFile.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIP_Project.h" 10 | #import "TIPImageDiskCache.h" 11 | #import "TIPImageDiskCacheTemporaryFile.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface TIPImageDiskCacheTemporaryFile () 16 | 17 | @property (nonatomic, readonly, copy) NSString *temporaryPath; 18 | @property (nonatomic, readonly, copy) NSString *finalPath; 19 | @property (nonatomic, nullable, weak) TIPImageDiskCache *diskCache; 20 | 21 | - (instancetype)initWithIdentifier:(NSString *)identifier 22 | temporaryPath:(NSString *)tempPath 23 | finalPath:(NSString *)finalPath 24 | diskCache:(nullable TIPImageDiskCache *)diskCache; 25 | 26 | - (void)cleanupOpenFile; 27 | 28 | @end 29 | 30 | @implementation TIPImageDiskCacheTemporaryFile 31 | { 32 | FILE *_openFile; 33 | } 34 | 35 | - (instancetype)init 36 | { 37 | [self doesNotRecognizeSelector:_cmd]; 38 | abort(); 39 | } 40 | 41 | - (instancetype)initWithIdentifier:(NSString *)identifier 42 | temporaryPath:(NSString *)tempPath 43 | finalPath:(NSString *)finalPath 44 | diskCache:(nullable TIPImageDiskCache *)diskCache 45 | { 46 | TIPAssert(identifier != nil); 47 | TIPAssert(tempPath != nil); 48 | TIPAssert(finalPath != nil); 49 | 50 | if (self = [super init]) { 51 | _imageIdentifier = [identifier copy]; 52 | _temporaryPath = [tempPath copy]; 53 | _finalPath = [finalPath copy]; 54 | _diskCache = diskCache; 55 | 56 | _openFile = fopen(tempPath.UTF8String, "a"); 57 | } 58 | return self; 59 | } 60 | 61 | - (void)dealloc 62 | { 63 | [self cleanupOpenFile]; 64 | [_diskCache clearTemporaryFilePath:_temporaryPath]; 65 | } 66 | 67 | - (NSUInteger)appendData:(nullable NSData *)data 68 | { 69 | if (!_openFile) { 70 | return 0; 71 | } 72 | return fwrite(data.bytes, 1, data.length, _openFile); 73 | } 74 | 75 | - (void)finalizeWithContext:(TIPImageCacheEntryContext *)context 76 | { 77 | if (!_openFile) { 78 | return; 79 | } 80 | 81 | TIPAssert([context isKindOfClass:[TIPPartialImageEntryContext class]] || [context isKindOfClass:[TIPCompleteImageEntryContext class]]); 82 | 83 | [self cleanupOpenFile]; 84 | TIPImageDiskCache *diskCache = _diskCache; 85 | 86 | if ([context isKindOfClass:[TIPPartialImageEntryContext class]]) { 87 | TIPPartialImageEntryContext *partialContext = (id)context; 88 | if (!partialContext.lastModified) { 89 | // Can't resume without a Last-Modified date 90 | [diskCache clearTemporaryFilePath:_temporaryPath]; 91 | return; 92 | } 93 | } 94 | 95 | [diskCache finalizeTemporaryFile:self withContext:context]; 96 | } 97 | 98 | - (void)cleanupOpenFile 99 | { 100 | if (_openFile) { 101 | fflush(_openFile); 102 | fclose(_openFile); 103 | _openFile = NULL; 104 | } 105 | } 106 | 107 | @end 108 | 109 | NS_ASSUME_NONNULL_END 110 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageDownloadInternalContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageDownloadInternalContext.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 10/14/15. 6 | // Copyright © 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageDiskCacheTemporaryFile.h" 10 | #import "TIPImageDownloader.h" 11 | #import "TIPImageFetchDownload.h" 12 | #import "TIPImageFetchOperation+Project.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface TIPImageDownloadInternalContext : NSObject 17 | { 18 | @public 19 | 20 | __unsafe_unretained id __nullable _download; 21 | TIPImageDiskCacheTemporaryFile * __nullable _temporaryFile; 22 | TIPPartialImage * __nullable _partialImage; 23 | NSString * __nullable _lastModified; 24 | NSDictionary * __nullable _decoderConfigMap; 25 | NSError * __nullable _progressStateError; 26 | NSHTTPURLResponse * __nullable _response; 27 | NSUInteger _contentLength; 28 | 29 | struct { 30 | BOOL didRequestHydration:1; 31 | BOOL didStart:1; 32 | BOOL didReceiveResponse:1; 33 | BOOL responseStatusCodeIsFailure:1; 34 | BOOL didReceiveData:1; 35 | BOOL didComplete:1; 36 | } _flags; 37 | 38 | @private 39 | 40 | NSMutableArray> * __nonnull _delegates; 41 | } 42 | 43 | @property (nonatomic, copy, nullable) NSURLRequest *originalRequest; 44 | @property (nonatomic, copy, nullable) NSURLRequest *hydratedRequest; 45 | @property (nonatomic, nullable) id client; 46 | @property (nonatomic, nullable) dispatch_queue_t downloadQueue; 47 | 48 | @property (nonatomic, readonly) NSUInteger delegateCount; // computed property 49 | 50 | - (NSOperationQueuePriority)downloadPriority; 51 | - (nullable id)firstDelegate; 52 | - (BOOL)containsDelegate:(id)delegate; 53 | - (void)addDelegate:(id)delegate; 54 | - (void)removeDelegate:(id)delegate; 55 | - (void)executePerDelegateSuspendingQueue:(nullable dispatch_queue_t)queue 56 | block:(void(^)(id))block; 57 | + (void)executeDelegate:(id)delegate 58 | suspendingQueue:(nullable dispatch_queue_t)queue 59 | block:(void (^)(id))block; 60 | 61 | @end 62 | 63 | NS_ASSUME_NONNULL_END 64 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageDownloadInternalContext.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageDownloadInternalContext.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 10/14/15. 6 | // Copyright © 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIP_Project.h" 10 | #import "TIPError.h" 11 | #import "TIPGlobalConfiguration+Project.h" 12 | #import "TIPImageDownloadInternalContext.h" 13 | #import "TIPTiming.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @implementation TIPImageDownloadInternalContext 18 | 19 | - (instancetype)init 20 | { 21 | self = [super init]; 22 | if (self) { 23 | _delegates = [NSMutableArray array]; 24 | } 25 | return self; 26 | } 27 | 28 | - (NSUInteger)delegateCount 29 | { 30 | return _delegates.count; 31 | } 32 | 33 | - (nullable TIPImageFetchOperation *)associatedImageFetchOperation 34 | { 35 | for (id delegate in _delegates) { 36 | if ([delegate isKindOfClass:[TIPImageFetchOperation class]]) { 37 | return (id)delegate; 38 | } 39 | } 40 | return nil; 41 | } 42 | 43 | - (nullable id)firstDelegate 44 | { 45 | return _delegates.firstObject; 46 | } 47 | 48 | - (NSOperationQueuePriority)downloadPriority 49 | { 50 | NSOperationQueuePriority pri = NSOperationQueuePriorityVeryLow; 51 | for (id delegate in _delegates) { 52 | const NSOperationQueuePriority delegatePriority = delegate.imageDownloadRequest.imageDownloadPriority; 53 | pri = MAX(pri, delegatePriority); 54 | } 55 | return pri; 56 | } 57 | 58 | - (BOOL)containsDelegate:(id)delegate 59 | { 60 | return [_delegates containsObject:delegate]; 61 | } 62 | 63 | - (void)addDelegate:(id)delegate 64 | { 65 | [_delegates addObject:delegate]; 66 | } 67 | 68 | - (void)removeDelegate:(id)delegate 69 | { 70 | NSUInteger count = _delegates.count; 71 | [_delegates removeObject:delegate]; 72 | if (count > _delegates.count) { 73 | id download = _download; 74 | [TIPImageDownloadInternalContext executeDelegate:delegate suspendingQueue:NULL block:^(id blockDelegate) { 75 | [blockDelegate imageDownload:(id)download didCompleteWithPartialImage:nil lastModified:nil byteSize:0 imageType:nil image:nil imageRenderLatency:0.0 statusCode:0 error:[NSError errorWithDomain:TIPImageFetchErrorDomain code:TIPImageFetchErrorCodeCancelled userInfo:nil]]; 76 | }]; 77 | } 78 | } 79 | 80 | - (void)executePerDelegateSuspendingQueue:(nullable dispatch_queue_t)queue 81 | block:(void(^)(id))block 82 | { 83 | for (id delegate in _delegates) { 84 | [TIPImageDownloadInternalContext executeDelegate:delegate 85 | suspendingQueue:queue block:block]; 86 | } 87 | } 88 | 89 | + (void)executeDelegate:(id)delegate 90 | suspendingQueue:(nullable dispatch_queue_t)queue 91 | block:(void (^)(id))block 92 | { 93 | dispatch_queue_t delegateQueue = [delegate respondsToSelector:@selector(imageDownloadDelegateQueue)] ? delegate.imageDownloadDelegateQueue : NULL; 94 | if (delegateQueue) { 95 | if (queue) { 96 | dispatch_suspend(queue); 97 | } 98 | tip_dispatch_async_autoreleasing(delegateQueue, ^{ 99 | block(delegate); 100 | if (queue) { 101 | dispatch_resume(queue); 102 | } 103 | }); 104 | } else { 105 | block(delegate); 106 | } 107 | } 108 | 109 | @end 110 | 111 | NS_ASSUME_NONNULL_END 112 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageDownloader.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/3/15. 6 | // Copyright (c) 2015 Twitter, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TIPImageCodecs.h" 12 | #import "TIPImageFetchRequest.h" 13 | #import "TIPPartialImage.h" 14 | 15 | @class TIPImageResponse; 16 | @class TIPImageDiskCacheTemporaryFile; 17 | @class TIPImagePipeline; 18 | 19 | @protocol TIPImageDownloadContext; 20 | @protocol TIPImageDownloadRequest; 21 | @protocol TIPImageDownloadDelegate; 22 | 23 | NS_ASSUME_NONNULL_BEGIN 24 | 25 | FOUNDATION_EXTERN NSString * const TIPImageDownloaderCancelSource; 26 | 27 | typedef void(^TIPImageDownloaderResumeInfoBlock)(NSUInteger alreadyDownloadedBytes, 28 | NSString * __nullable lastModified); 29 | 30 | @interface TIPImageDownloader : NSObject 31 | 32 | + (instancetype)sharedInstance; 33 | - (instancetype)init NS_UNAVAILABLE; 34 | + (instancetype)new NS_UNAVAILABLE; 35 | 36 | - (id)fetchImageWithDownloadDelegate:(id)delegate; 37 | - (void)removeDelegate:(id)delegate 38 | forContext:(id)context; 39 | - (void)updatePriorityOfContext:(id)context; 40 | 41 | @end 42 | 43 | @protocol TIPImageDownloadContext 44 | @end 45 | 46 | @protocol TIPImageDownloadRequest 47 | 48 | @required 49 | 50 | // Image identity - either returning `nil` is an invalid request 51 | - (nullable NSURL *)imageDownloadURL; 52 | - (nullable NSString *)imageDownloadIdentifier; 53 | 54 | // HTTP Request info 55 | - (nullable NSDictionary *)imageDownloadHeaders; 56 | 57 | // Request behavior info 58 | - (NSOperationQueuePriority)imageDownloadPriority; 59 | - (nullable TIPImageFetchHydrationBlock)imageDownloadHydrationBlock; 60 | - (nullable NSDictionary *)decoderConfigMap; 61 | 62 | // Loaded image behavior info 63 | - (NSTimeInterval)imageDownloadTTL; 64 | - (TIPImageFetchOptions)imageDownloadOptions; 65 | 66 | // Resume info 67 | - (nullable NSString *)imageDownloadLastModified; 68 | - (nullable TIPPartialImage *)imageDownloadPartialImageForResuming; 69 | - (nullable TIPImageDiskCacheTemporaryFile *)imageDownloadTemporaryFileForResuming; 70 | 71 | @end 72 | 73 | @protocol TIPImageDownloadDelegate 74 | 75 | @required 76 | 77 | // Execution 78 | 79 | - (id)imageDownloadRequest; 80 | 81 | - (nullable dispatch_queue_t)imageDownloadDelegateQueue; 82 | 83 | - (nullable TIPImagePipeline *)imagePipeline; 84 | 85 | - (TIPImageDiskCacheTemporaryFile *)regenerateImageDownloadTemporaryFileForImageDownload:(id)context; 86 | 87 | // Events 88 | 89 | - (void)imageDownloadDidStart:(id)context; 90 | 91 | - (void)imageDownload:(id)context 92 | didResetFromPartialImage:(TIPPartialImage *)oldPartialImage; 93 | 94 | - (void)imageDownload:(id)op 95 | didAppendBytes:(NSUInteger)byteCount 96 | toPartialImage:(TIPPartialImage *)partialImage 97 | result:(TIPImageDecoderAppendResult)result; 98 | 99 | - (void)imageDownload:(id)op 100 | didCompleteWithPartialImage:(nullable TIPPartialImage *)partialImage 101 | lastModified:(nullable NSString *)lastModified 102 | byteSize:(NSUInteger)bytes 103 | imageType:(nullable NSString *)imageType 104 | image:(nullable TIPImageContainer *)image 105 | imageRenderLatency:(NSTimeInterval)latency 106 | statusCode:(NSInteger)statusCode 107 | error:(nullable NSError *)error; 108 | 109 | @end 110 | 111 | NS_ASSUME_NONNULL_END 112 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageFetchDownloadInternal.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchDownloadInternal.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/24/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageFetchDownload.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TIPImageFetchDownloadInternal : NSObject 14 | - (NSURLSession *)URLSession; 15 | - (instancetype)initWithContext:(id)context; 16 | @end 17 | 18 | @interface TIPImageFetchDownloadProviderInternal : NSObject 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageFetchMetrics+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchMetrics+Project.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/19/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageFetchMetrics.h" 10 | 11 | @class TIPPartialImage; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface TIPImageFetchMetrics () 16 | 17 | - (instancetype)initProject; 18 | 19 | - (void)startWithSource:(TIPImageLoadSource)source; 20 | - (void)endSource; 21 | - (void)cancelSource; 22 | 23 | - (void)convertNetworkMetricsToResumedNetworkMetrics; 24 | - (void)addNetworkMetrics:(nullable id)metrics 25 | forRequest:(NSURLRequest *)request 26 | imageType:(nullable NSString *)imageType 27 | imageSizeInBytes:(NSUInteger)sizeInBytes 28 | imageDimensions:(CGSize)dimensions; 29 | 30 | - (void)previewWasHit:(NSTimeInterval)renderLatency; 31 | - (void)progressiveFrameWasHit:(NSTimeInterval)renderLatency; 32 | - (void)finalWasHit:(NSTimeInterval)renderLatency synchronously:(BOOL)sync; 33 | 34 | @end 35 | 36 | @interface TIPImageFetchMetricInfo () 37 | 38 | - (instancetype)initWithSource:(TIPImageLoadSource)source startTime:(uint64_t)startMachTime; 39 | 40 | - (void)end; 41 | - (void)cancel; 42 | - (void)hit:(TIPImageFetchLoadResult)result 43 | renderLatency:(NSTimeInterval)renderLatency 44 | synchronously:(BOOL)sync; 45 | - (void)addNetworkMetrics:(nullable id)metrics 46 | forRequest:(NSURLRequest *)request 47 | imageType:(nullable NSString *)imageType 48 | imageSizeInBytes:(NSUInteger)sizeInBytes 49 | imageDimensions:(CGSize)dimensions; 50 | - (void)flipLoadSourceFromNetworkToNetworkResumed; 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageFetchOperation+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchOperation+Project.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageDownloader.h" 10 | #import "TIPImageFetchOperation.h" 11 | 12 | @class TIPImagePipeline; 13 | @class TIPImageCacheEntry; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface TIPImageFetchOperation (Project) 18 | 19 | @property (nonatomic, readonly, nullable, copy) NSString *imageIdentifier; 20 | @property (nonatomic, readonly, nullable) NSURL *imageURL; 21 | @property (nonatomic, readonly, nullable, copy) NSString *transformerIdentifier; 22 | 23 | - (instancetype)initWithImagePipeline:(TIPImagePipeline *)pipeline 24 | request:(id)request 25 | delegate:(id)delegate; 26 | 27 | - (void)completeOperationEarlyWithImageEntry:(TIPImageCacheEntry *)entry 28 | transformed:(BOOL)transformed 29 | sourceImageDimensions:(CGSize)sourceDims; 30 | - (void)willEnqueue; 31 | - (BOOL)supportsLoadingFromSource:(TIPImageLoadSource)source; 32 | - (BOOL)supportsLoadingFromRenderedCache; 33 | 34 | @end 35 | 36 | @interface TIPImageFetchOperation (Testing) 37 | - (id)associatedDownloadContext; 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageMemoryCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPMemoryCache.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/3/15. 6 | // Copyright (c) 2015 Twitter, Inc. All rights reserved. 7 | // 8 | 9 | #import "TIPInspectableCache.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TIPImageMemoryCache : NSObject 14 | 15 | - (nullable TIPImageMemoryCacheEntry *)imageEntryForIdentifier:(NSString *)identifier; 16 | - (void)updateImageEntry:(TIPImageCacheEntry *)entry 17 | forciblyReplaceExisting:(BOOL)force; 18 | - (void)touchImageWithIdentifier:(NSString *)identifier; 19 | - (void)clearImageWithIdentifier:(NSString *)identifier; 20 | - (void)clearAllImages:(nullable void (^)(void))completion; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImagePipeline+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImagePipeline+Project.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageCacheEntry.h" 10 | #import "TIPImageFetchDelegate.h" 11 | #import "TIPImagePipeline.h" 12 | 13 | @class TIPImageDiskCache; 14 | @class TIPImageMemoryCache; 15 | @class TIPImageRenderedCache; 16 | @class TIPImageDownloader; 17 | @class TIPImageStoreOperation; 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | FOUNDATION_EXTERN NSString * const TIPImagePipelineDidStandUpImagePipelineNotification; 22 | FOUNDATION_EXTERN NSString * const TIPImagePipelineDidTearDownImagePipelineNotification; // object will be nil 23 | 24 | @interface TIPImagePipeline () 25 | 26 | @property (nonatomic, readonly, nullable) TIPImageRenderedCache *renderedCache; 27 | @property (nonatomic, readonly, nullable) TIPImageMemoryCache *memoryCache; 28 | @property (nonatomic, readonly, nullable) TIPImageDiskCache *diskCache; 29 | @property (nonatomic, readonly, nullable) TIPImageDownloader *downloader; 30 | 31 | - (TIPImageStoreOperation *)storeOperationWithRequest:(id)request 32 | completion:(nullable TIPImagePipelineOperationCompletionBlock)completion; 33 | - (void)postCompletedEntry:(TIPImageCacheEntry *)entry 34 | manual:(BOOL)manual; 35 | - (nullable id)cacheOfType:(TIPImageCacheType)type; 36 | 37 | + (NSDictionary *)allRegisteredImagePipelines; 38 | 39 | @end 40 | 41 | @interface TIPSimpleImageFetchDelegate : NSObject 42 | @end 43 | 44 | NS_ASSUME_NONNULL_END 45 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImagePipelineInspectionResult+Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImagePipelineInspectionResult+Project.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/15/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImagePipelineInspectionResult.h" 10 | 11 | @class TIPImageCacheEntry; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface TIPImagePipelineInspectionResult (Project) 16 | 17 | - (nullable instancetype)initWithImagePipeline:(TIPImagePipeline *)imagePipeline; 18 | 19 | - (void)addEntries:(NSArray> *)entries; 20 | - (void)addEntry:(id)entry; 21 | 22 | @end 23 | 24 | @interface TIPImagePipelineInspectionResultEntry : NSObject 25 | 26 | // Protocol properties 27 | @property (nonatomic, copy, nullable) NSString *identifier; 28 | @property (nonatomic, nullable) NSURL *URL; 29 | @property (nonatomic) CGSize dimensions; 30 | @property (nonatomic) unsigned long long bytesUsed; 31 | @property (nonatomic) float progress; 32 | @property (nonatomic, nullable) UIImage *image; 33 | 34 | + (nullable instancetype)entryWithCacheEntry:(TIPImageCacheEntry *)cacheEntry 35 | class:(Class)class; 36 | 37 | @end 38 | 39 | @interface TIPImagePipelineInspectionResultRenderedEntry : TIPImagePipelineInspectionResultEntry 40 | @end 41 | 42 | @interface TIPImagePipelineInspectionResultCompleteMemoryEntry : TIPImagePipelineInspectionResultEntry 43 | @end 44 | 45 | @interface TIPImagePipelineInspectionResultCompleteDiskEntry : TIPImagePipelineInspectionResultEntry 46 | @end 47 | 48 | @interface TIPImagePipelineInspectionResultPartialMemoryEntry : TIPImagePipelineInspectionResultEntry 49 | @end 50 | 51 | @interface TIPImagePipelineInspectionResultPartialDiskEntry : TIPImagePipelineInspectionResultEntry 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageRenderedCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageRenderedCache.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 4/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "TIPInspectableCache.h" 13 | 14 | @class TIPImageCacheEntry; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @interface TIPImageRenderedCache : NSObject 19 | 20 | - (nullable TIPImageCacheEntry *)imageEntryWithIdentifier:(NSString *)identifier 21 | transformerIdentifier:(nullable NSString *)transformerIdentifier 22 | targetDimensions:(CGSize)size 23 | targetContentMode:(UIViewContentMode)mode 24 | sourceImageDimensions:(out CGSize * __nullable)sourceDimsOut; // main thread only 25 | - (void)storeImageEntry:(TIPImageCacheEntry *)entry 26 | transformerIdentifier:(nullable NSString *)transformerIdentifier 27 | sourceImageDimensions:(CGSize)sourceDims; 28 | - (void)clearImagesWithIdentifier:(NSString *)identifier; 29 | - (void)clearAllImages:(nullable void (^)(void))completion; 30 | 31 | @end 32 | 33 | NS_ASSUME_NONNULL_END 34 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPImageStoreAndMoveOperations.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageStoreAndMoveOperations.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 1/13/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TIPImagePipeline.h" 12 | #import "TIPImageStoreRequest.h" 13 | #import "TIPSafeOperation.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @class TIPImageStoreHydrationOperation; 18 | 19 | @interface TIPDisabledExternalMutabilityOperation : TIPSafeOperation 20 | @end 21 | 22 | @interface TIPImageStoreOperation : TIPDisabledExternalMutabilityOperation 23 | 24 | - (instancetype)initWithRequest:(id)request 25 | pipeline:(TIPImagePipeline *)pipeline 26 | completion:(nullable TIPImagePipelineOperationCompletionBlock)completion; 27 | 28 | - (void)setHydrationDependency:(nonnull TIPImageStoreHydrationOperation *)dependency; 29 | 30 | - (instancetype)init NS_UNAVAILABLE; 31 | + (instancetype)new NS_UNAVAILABLE; 32 | 33 | @end 34 | 35 | @interface TIPImageStoreHydrationOperation : TIPDisabledExternalMutabilityOperation 36 | 37 | @property (nonatomic, readonly, nullable) NSError *error; 38 | @property (nonatomic, readonly, nullable) id hydratedRequest; 39 | 40 | - (instancetype)initWithRequest:(id)request 41 | pipeline:(TIPImagePipeline *)pipeline 42 | hydrater:(id)hydrater; 43 | 44 | - (instancetype)init NS_UNAVAILABLE; 45 | + (instancetype)new NS_UNAVAILABLE; 46 | 47 | @end 48 | 49 | @interface TIPImageMoveOperation : TIPDisabledExternalMutabilityOperation 50 | 51 | @property (nonatomic, readonly, copy) NSString *originalIdentifier; 52 | @property (nonatomic, readonly, copy) NSString *updatedIdentifier; 53 | @property (nonatomic, readonly) TIPImagePipeline *pipeline; 54 | 55 | - (instancetype)initWithPipeline:(TIPImagePipeline *)pipeline 56 | originalIdentifier:(NSString *)oldIdentifier 57 | updatedIdentifier:(NSString *)newIdentifier 58 | completion:(nullable TIPImagePipelineOperationCompletionBlock)completion; 59 | 60 | - (instancetype)init NS_UNAVAILABLE; 61 | + (instancetype)new NS_UNAVAILABLE; 62 | 63 | @end 64 | 65 | NS_ASSUME_NONNULL_END 66 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPInspectableCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPInspectableCache.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/15/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TIPImagePipelineInspectionResult; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | typedef void(^TIPInspectableCacheCallback)(NSArray * __nullable completedEntries, NSArray * __nullable partialEntries); 16 | 17 | @protocol TIPInspectableCache 18 | 19 | @required 20 | - (void)inspect:(TIPInspectableCacheCallback)callback; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPLRUCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPLRUCache.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 10/27/15. 6 | // Copyright © 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol TIPLRUCacheDelegate; 12 | @protocol TIPLRUEntry; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface TIPLRUCache : NSObject 17 | 18 | @property (nonatomic, weak, nullable) id delegate; 19 | @property (nonatomic, readonly, nullable) id headEntry; 20 | @property (nonatomic, readonly, nullable) id tailEntry; 21 | 22 | - (NSUInteger)numberOfEntries; 23 | 24 | - (instancetype)initWithEntries:(nullable NSArray> *)arrayOfLRUEntries delegate:(nullable id)delegate NS_DESIGNATED_INITIALIZER; 25 | 26 | - (nullable id)entryWithIdentifier:(NSString *)identifier canMutate:(BOOL)moveToHead; 27 | 28 | - (nullable id)entryWithIdentifier:(NSString *)identifier; 29 | 30 | - (NSArray> *)allEntries; 31 | 32 | - (void)addEntry:(id)entry; 33 | 34 | - (void)appendEntry:(id)entry; 35 | 36 | - (void)removeEntry:(nullable id)entry; 37 | 38 | - (nullable id)removeTailEntry; 39 | 40 | - (void)clearAllEntries; 41 | 42 | @end 43 | 44 | @protocol TIPLRUCacheDelegate 45 | 46 | @optional 47 | - (void)tip_cache:(TIPLRUCache *)cache didEvictEntry:(id)entry; 48 | - (BOOL)tip_cache:(TIPLRUCache *)cache canEvictEntry:(id)entry; 49 | 50 | @end 51 | 52 | 53 | @protocol TIPLRUEntry 54 | 55 | @required 56 | 57 | - (NSString *)LRUEntryIdentifier; 58 | - (BOOL)shouldAccessMoveLRUEntryToHead; 59 | @property (nonatomic, nullable) id nextLRUEntry; 60 | @property (nonatomic, nullable, weak) id previousLRUEntry; 61 | 62 | @end 63 | 64 | NS_ASSUME_NONNULL_END 65 | 66 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPPartialImage.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPPartialImage.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/6/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageCodecs.h" 10 | #import "TIPImageContainer.h" 11 | #import "TIPImageUtils.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | typedef NS_ENUM(NSInteger, TIPPartialImageState) { 16 | TIPPartialImageStateNoData = 0, 17 | TIPPartialImageStateLoadingHeaders, 18 | TIPPartialImageStateLoadingImage, 19 | TIPPartialImageStateComplete, 20 | }; 21 | 22 | @interface TIPPartialImage : NSObject 23 | 24 | // State 25 | @property (atomic, readonly) TIPPartialImageState state; 26 | @property (nonatomic, readonly) NSUInteger expectedContentLength; 27 | 28 | // After headers are read 29 | @property (nonatomic, nullable, copy, readonly) NSString *type; 30 | @property (nonatomic, readonly) CGSize dimensions; 31 | @property (nonatomic, readonly) BOOL hasAlpha; 32 | @property (nonatomic, readonly, getter=isProgressive) BOOL progressive; // mutually exclusive from animated 33 | @property (nonatomic, readonly, getter=isAnimated) BOOL animated; // mutually exclusive from progressive 34 | @property (nonatomic, readonly) BOOL hasGPSInfo; // very bad! we should never have user GPS info in our served images! 35 | 36 | // Progress 37 | @property (nonatomic, readonly) NSUInteger frameCount; // for progressive or animated images 38 | @property (atomic, readonly, nullable) NSData *data; 39 | @property (nonatomic, readonly) NSUInteger byteCount; 40 | @property (nonatomic, readonly) float progress; 41 | 42 | - (instancetype)initWithExpectedContentLength:(NSUInteger)contentLength NS_DESIGNATED_INITIALIZER; 43 | - (instancetype)init NS_UNAVAILABLE; 44 | + (instancetype)new NS_UNAVAILABLE; 45 | 46 | - (void)updateDecoderConfigMap:(nullable NSDictionary *)configMap; 47 | - (TIPImageDecoderAppendResult)appendData:(nullable NSData *)data 48 | final:(BOOL)final; 49 | - (nullable TIPImageContainer *)renderImageWithMode:(TIPImageDecoderRenderMode)mode 50 | decoded:(BOOL)decode; 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPTiming.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTiming.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 5/12/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | FOUNDATION_EXTERN uint64_t TIPAbsoluteToNanoseconds(uint64_t absolute); 14 | FOUNDATION_EXTERN uint64_t TIPAbsoluteFromNanoseconds(uint64_t nano); 15 | 16 | FOUNDATION_EXTERN NSTimeInterval TIPAbsoluteToTimeInterval(uint64_t absolute); 17 | FOUNDATION_EXTERN uint64_t TIPAbsoluteFromTimeInterval(NSTimeInterval ti); 18 | 19 | static const NSTimeInterval kTIPTimeEpsilon = 0.0005; 20 | 21 | // If endTime is 0, mach_absolute_time() will be used in the calculation 22 | FOUNDATION_EXTERN NSTimeInterval TIPComputeDuration(uint64_t startTime, uint64_t endTime); 23 | 24 | NS_ASSUME_NONNULL_END 25 | 26 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPTiming.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTiming.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 5/12/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TIPTiming.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | static mach_timebase_info_data_t _TIPMachTimebaseInfo(void); 16 | static mach_timebase_info_data_t _TIPMachTimebaseInfo(void) 17 | { 18 | static mach_timebase_info_data_t sMachInfo; 19 | static dispatch_once_t sMachInfoOnceToken = 0; 20 | dispatch_once(&sMachInfoOnceToken, ^{ 21 | if (mach_timebase_info(&sMachInfo) != KERN_SUCCESS) { 22 | sMachInfo.numer = 0; 23 | sMachInfo.denom = 1; 24 | } 25 | }); 26 | return sMachInfo; 27 | } 28 | 29 | uint64_t TIPAbsoluteFromNanoseconds(uint64_t nano) 30 | { 31 | mach_timebase_info_data_t machInfo = _TIPMachTimebaseInfo(); 32 | if (0 == machInfo.numer) { 33 | // If we can't get a valid timebase, just convert to zero 34 | return 0; 35 | } 36 | 37 | // Don't sweat imprecision going from Nano to Absolute 38 | uint64_t absolute = nano * machInfo.denom; 39 | absolute /= machInfo.numer; 40 | return absolute; 41 | } 42 | 43 | uint64_t TIPAbsoluteToNanoseconds(uint64_t absolute) 44 | { 45 | mach_timebase_info_data_t machInfo = _TIPMachTimebaseInfo(); 46 | 47 | uint64_t nanoSeconds = absolute * machInfo.numer; 48 | if (nanoSeconds < absolute) { 49 | /* 50 | Either overflow or zero numer 51 | 52 | Overflow: 53 | proceed with loss of precision 54 | 55 | Zero numer: 56 | this will happen when there's an error returned by 57 | mach_timebase_info (I've never encountered this). 58 | In this case, it will be best to just have all values 59 | return as 0 instead of an inaccurate value. For 60 | simplicity, we'll just use the overflow logic. 61 | */ 62 | nanoSeconds = absolute / machInfo.denom; 63 | nanoSeconds *= machInfo.numer; 64 | } else { 65 | nanoSeconds /= machInfo.denom; 66 | } 67 | 68 | return nanoSeconds; 69 | } 70 | 71 | NSTimeInterval TIPAbsoluteToTimeInterval(uint64_t absolute) 72 | { 73 | uint64_t nanoSeconds = TIPAbsoluteToNanoseconds(absolute); 74 | double preciseValueInSeconds = (((double)nanoSeconds) / NSEC_PER_SEC); 75 | return preciseValueInSeconds; 76 | } 77 | 78 | uint64_t TIPAbsoluteFromTimeInterval(NSTimeInterval ti) 79 | { 80 | uint64_t nanoSeconds = (uint64_t)(ti * NSEC_PER_MSEC); 81 | uint64_t absolute = TIPAbsoluteFromNanoseconds(nanoSeconds); 82 | return absolute; 83 | } 84 | 85 | NSTimeInterval TIPComputeDuration(uint64_t startTime, uint64_t endTime) 86 | { 87 | if (!startTime) { 88 | return 0; 89 | } 90 | if (!endTime) { 91 | endTime = mach_absolute_time(); 92 | } 93 | if (endTime < startTime) { 94 | return -TIPAbsoluteToTimeInterval(startTime - endTime); 95 | } 96 | return TIPAbsoluteToTimeInterval(endTime - startTime); 97 | } 98 | 99 | NS_ASSUME_NONNULL_END 100 | 101 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPURLStringCoding.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPURLStringCoding.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/12/16. 6 | // Copyright (c) 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | FOUNDATION_EXTERN NSString * __nullable TIPURLEncodeString(NSString * __nullable string); 14 | FOUNDATION_EXTERN NSString * __nullable TIPURLDecodeString(NSString * __nullable string, BOOL replacePlussesWithSpaces); 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIPURLStringCoding.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPURLStringCoding.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/12/16. 6 | // Copyright (c) 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIP_Project.h" 10 | #import "TIPURLStringCoding.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | static const char kHexDigits[] = "0123456789ABCDEF"; 15 | 16 | NSString * __nullable TIPURLEncodeString(NSString * __nullable string) 17 | { 18 | if (0 == string.length) { 19 | return @""; 20 | } 21 | 22 | const char *stringAsUTF8 = [string UTF8String]; 23 | if (stringAsUTF8 == NULL) { 24 | NSMutableString *byteString = [[NSMutableString alloc] init]; 25 | for (NSUInteger i = 0; i < string.length; i++) { 26 | unichar uchar = [string characterAtIndex:i]; 27 | [byteString appendFormat:@"%02X ", uchar]; 28 | } 29 | TIPLogError(@"No UTF8String for NSString:\n%@", byteString); 30 | return nil; 31 | } 32 | 33 | NSUInteger encodedLength = 0; 34 | BOOL needsEncoding = NO; 35 | for (const char *c = stringAsUTF8; *c; c++) { 36 | switch (*c) { 37 | case '0' ... '9': 38 | case 'A' ... 'Z': 39 | case 'a' ... 'z': 40 | case '-': 41 | case '.': 42 | case '_': 43 | case '~': 44 | encodedLength++; 45 | break; 46 | default: 47 | encodedLength += 3; 48 | needsEncoding = YES; 49 | break; 50 | } 51 | } 52 | if (!needsEncoding) { 53 | return string; 54 | } 55 | 56 | char *encodedBytes = malloc(encodedLength); 57 | if (NULL == encodedBytes) { 58 | TIPLogError(@"Out of memory"); 59 | return nil; 60 | } 61 | 62 | char *outPtr = encodedBytes; 63 | for (const unsigned char *c = (const unsigned char *)stringAsUTF8; *c; c++) { 64 | switch (*c) { 65 | case '0' ... '9': 66 | case 'A' ... 'Z': 67 | case 'a' ... 'z': 68 | case '-': 69 | case '.': 70 | case '_': 71 | case '~': 72 | *outPtr++ = (char)*c; 73 | break; 74 | default: 75 | *outPtr++ = '%'; 76 | *outPtr++ = kHexDigits[(*c>>4)&0xf]; 77 | *outPtr++ = kHexDigits[*c&0xf]; 78 | break; 79 | } 80 | } 81 | 82 | NSString *encodedString = [[NSString alloc] initWithBytesNoCopy:encodedBytes length:encodedLength encoding:NSASCIIStringEncoding freeWhenDone:YES]; 83 | if (encodedString == nil) { 84 | TIPLogError(@"Can't create encodedString from '%@'", string); 85 | free(encodedBytes); 86 | } 87 | return encodedString; 88 | } 89 | 90 | NSString * __nullable TIPURLDecodeString(NSString * __nullable string, BOOL replacePlussesWithSpaces) 91 | { 92 | NSString *s = string; 93 | // replace the '+' first since if the '+' is encoded we want to preserve its value 94 | if (replacePlussesWithSpaces) { 95 | s = [s stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 96 | } 97 | 98 | // the deprecated [s stringByReplacingPercentExcapesUsingEncoding:NSUTF8StringEncoding] 99 | // used to return @"" for @"" . the replacement method returns nil. 100 | // 101 | // by checking length, we preserve the old behavior for this caller in case anything depended upon it. 102 | return s.length ? [s stringByRemovingPercentEncoding] : s; 103 | } 104 | 105 | NS_ASSUME_NONNULL_END 106 | 107 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIP_Project.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIP_Project.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/5/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | 12 | #import 13 | #import 14 | #import 15 | 16 | #import "TIP_ProjectCommon.h" 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | #pragma mark - Version 21 | 22 | FOUNDATION_EXTERN NSString *TIPVersion(void); 23 | 24 | #pragma mark - Helpers 25 | 26 | FOUNDATION_EXTERN BOOL TIPImageTypeSupportsLossyQuality(NSString * __nullable type); 27 | 28 | FOUNDATION_EXTERN void TIPSwizzle(Class cls, SEL originalSelector, SEL swizzledSelector); 29 | FOUNDATION_EXTERN void TIPClassSwizzle(Class cls, SEL originalSelector, SEL swizzledSelector); 30 | 31 | #define TIPSizeEqualToZero(targetSize) CGSizeEqualToSize((targetSize), CGSizeZero) 32 | NS_INLINE BOOL TIPSizeGreaterThanZero(CGSize targetSize) 33 | { 34 | return targetSize.width > 0 && targetSize.height > 0; 35 | } 36 | 37 | FOUNDATION_EXTERN NSString *TIPSafeFromRaw(NSString *raw); // URL encodes raw. If that encoded string is > the max length of a file name (less 4 characters for supporting a 3 character extension), it will be hashed. 38 | FOUNDATION_EXTERN NSString *TIPRawFromSafe(NSString *safe); // might not be the same as the input to "SafeFromRaw" since long "raw" strings will be hashed 39 | FOUNDATION_EXTERN NSString *TIPHash(NSString *string); 40 | 41 | FOUNDATION_EXTERN CGSize TIPScaleToFillKeepingAspectRatio(CGSize sourceSize, CGSize targetSize, CGFloat scale); 42 | 43 | FOUNDATION_EXTERN CGSize TIPScaleToFitKeepingAspectRatio(CGSize sourceSize, CGSize targetSize, CGFloat scale); 44 | 45 | #define TIP_UPDATE_BYTES(readWriteTotalVar, addValue, subValue, name) \ 46 | do { \ 47 | const SInt64 oldSize = readWriteTotalVar; \ 48 | const SInt64 newSize = oldSize + (SInt64)(addValue) - (SInt64)(subValue); \ 49 | TIPAssertMessage(newSize >= 0, name @" - Old: %lli, Add: %llu, Sub: %llu, New: %lli", oldSize, bytesAdded, bytesRemoved, newSize); \ 50 | readWriteTotalVar = newSize; \ 51 | } while (0) 52 | 53 | #pragma mark - Debugging Tools 54 | 55 | FOUNDATION_EXTERN BOOL TIPShouldAssertDuringPipelineRegistation(void); 56 | FOUNDATION_EXTERN void TIPSetShouldAssertDuringPipelineRegistation(BOOL shouldAssertDuringPipelineRegistration); 57 | 58 | #pragma mark - BG Task 59 | 60 | FOUNDATION_EXTERN dispatch_block_t __nullable TIPStartBackgroundTask(NSString * __nullable name); 61 | 62 | #pragma twitter startignorestylecheck 63 | 64 | #define TIPStartMethodScopedBackgroundTask(name) \ 65 | dispatch_block_t clearTaskBlock##__LINE__ = TIPStartBackgroundTask([NSString stringWithFormat:@"[%@ %@]", NSStringFromClass([self class]), @( #name )]); \ 66 | tip_defer(^{ if (clearTaskBlock##__LINE__) { clearTaskBlock##__LINE__(); } }); 67 | 68 | #pragma twitter stopignorestylecheck 69 | 70 | NS_ASSUME_NONNULL_END 71 | -------------------------------------------------------------------------------- /TwitterImagePipeline/Project/TIP_ProjectCommon.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIP_Project.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/5/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIP_ProjectCommon.h" 10 | 11 | #if DEBUG 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #endif 18 | 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | BOOL gTwitterImagePipelineAssertEnabled = YES; 23 | id __nullable gTIPLogger = nil; 24 | 25 | BOOL TIPIsExtension() 26 | { 27 | // Per Apple, an extension will have a top level NSExtension dictionary in the info.plist 28 | // https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/SystemExtensionKeys.html 29 | 30 | static BOOL sIsExtension; 31 | static dispatch_once_t onceToken; 32 | dispatch_once(&onceToken, ^{ 33 | NSBundle *mainBundle = [NSBundle mainBundle]; 34 | NSDictionary *infoDictionary = mainBundle.infoDictionary; 35 | NSDictionary *extensionDictionary = infoDictionary[@"NSExtension"]; 36 | sIsExtension = [extensionDictionary isKindOfClass:[NSDictionary class]]; 37 | }); 38 | return sIsExtension; 39 | } 40 | 41 | BOOL TIPAmIBeingUnitTested(void) 42 | { 43 | // Look for a "SenTest" (legacy) or "XCTest" class. If we switch unit test frameworks we need to update this. 44 | static BOOL sAmIBeingUnitTested = NO; 45 | static dispatch_once_t onceToken; 46 | dispatch_once(&onceToken, ^{ 47 | sAmIBeingUnitTested = (NSClassFromString(@"SenTest") != NULL || NSClassFromString(@"XCTest") != NULL); 48 | }); 49 | return sAmIBeingUnitTested; 50 | } 51 | 52 | #if DEBUG 53 | 54 | // https://developer.apple.com/library/mac/qa/qa1361/_index.html 55 | // Returns true if the current process is being debugged (either 56 | // running under the debugger or has a debugger attached post facto). 57 | BOOL TIPIsDebuggerAttached() 58 | { 59 | int junk; 60 | int mib[4]; 61 | struct kinfo_proc info; 62 | size_t size; 63 | 64 | // Initialize the flags so that, if sysctl fails for some bizarre 65 | // reason, we get a predictable result. 66 | 67 | info.kp_proc.p_flag = 0; 68 | 69 | // Initialize mib, which tells sysctl the info we want, in this case 70 | // we're looking for information about a specific process ID. 71 | 72 | mib[0] = CTL_KERN; 73 | mib[1] = KERN_PROC; 74 | mib[2] = KERN_PROC_PID; 75 | mib[3] = getpid(); 76 | 77 | // Call sysctl. 78 | 79 | size = sizeof(info); 80 | junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); 81 | assert(junk == 0); 82 | 83 | // We're being debugged if the P_TRACED flag is set. 84 | 85 | return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); 86 | } 87 | 88 | void TIPTriggerDebugSTOP() 89 | { 90 | kill(getpid(), SIGSTOP); 91 | } 92 | 93 | static volatile BOOL sIsDebugSTOPEnabled = YES; 94 | static dispatch_once_t sIsDebugSTOPEnabledOnceToken = 0; 95 | 96 | NS_INLINE void TIPPrepDebugSTOPOnAssert() 97 | { 98 | dispatch_once(&sIsDebugSTOPEnabledOnceToken, ^{ 99 | sIsDebugSTOPEnabled = !TIPAmIBeingUnitTested(); 100 | }); 101 | } 102 | 103 | BOOL TIPIsDebugSTOPOnAssertEnabled() 104 | { 105 | TIPPrepDebugSTOPOnAssert(); 106 | return sIsDebugSTOPEnabled; 107 | } 108 | 109 | void TIPSetDebugSTOPOnAssertEnabled(BOOL stopOnAssert) 110 | { 111 | TIPPrepDebugSTOPOnAssert(); 112 | sIsDebugSTOPEnabled = stopOnAssert; 113 | } 114 | 115 | void __TIPAssert(BOOL expression) 116 | { 117 | if (!expression) { 118 | if (TIPIsDebugSTOPOnAssertEnabled() && TIPIsDebuggerAttached()) { 119 | TIPTriggerDebugSTOP(); // trigger debug stop 120 | } 121 | } 122 | } 123 | 124 | #endif // DEBUG 125 | 126 | NS_ASSUME_NONNULL_END 127 | 128 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPDefinitions.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPDefinitions.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 2/18/15. 6 | // Copyright (c) 2015 Twitter, Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | The source that an image was loaded from 13 | */ 14 | typedef NS_ENUM(NSInteger, TIPImageLoadSource) { 15 | /** Unknown */ 16 | TIPImageLoadSourceUnknown = 0, 17 | /** The in memory cache */ 18 | TIPImageLoadSourceMemoryCache, 19 | /** The on disk cache */ 20 | TIPImageLoadSourceDiskCache, 21 | /** The additional cache */ 22 | TIPImageLoadSourceAdditionalCache, 23 | /** The _Network_ */ 24 | TIPImageLoadSourceNetwork, 25 | /** The _Network_, but was resumed from a disk cache entry */ 26 | TIPImageLoadSourceNetworkResumed, 27 | }; 28 | 29 | static const TIPImageLoadSource TIPImageLoadSourceMaxValue = TIPImageLoadSourceNetworkResumed; 30 | 31 | /** 32 | Target loading sources values for the loading sources mask 33 | */ 34 | typedef NS_OPTIONS(NSInteger, TIPImageFetchLoadingSources) { 35 | /** Load from Memory Cache(s) */ 36 | TIPImageFetchLoadingSourceMemoryCache = (1 << TIPImageLoadSourceMemoryCache), 37 | /** Load from Disk Cache */ 38 | TIPImageFetchLoadingSourceDiskCache = (1 << TIPImageLoadSourceDiskCache), 39 | /** Load from Additional Cache(s) */ 40 | TIPImageFetchLoadingSourceAdditionalCache = (1 << TIPImageLoadSourceAdditionalCache), 41 | /** Load from Network */ 42 | TIPImageFetchLoadingSourceNetwork = (1 << TIPImageLoadSourceNetwork), 43 | /** Load from Network Resumption */ 44 | TIPImageFetchLoadingSourceNetworkResumed = (1 << TIPImageLoadSourceNetworkResumed), 45 | }; 46 | 47 | static const TIPImageFetchLoadingSources TIPImageFetchLoadingSourcesNone = (TIPImageFetchLoadingSources)0; 48 | static const TIPImageFetchLoadingSources TIPImageFetchLoadingSourcesAll = (TIPImageFetchLoadingSources)0xFF; 49 | 50 | /** 51 | An interface for an operation-like object. 52 | 53 | Supports being a dependency of other `NSOperation` objects, `waitUntilFinished` and KVO observing 54 | of `isFinished` and `isExecuting` 55 | */ 56 | @protocol TIPDependencyOperation 57 | 58 | @required 59 | 60 | /** 61 | Same effect as `[op addDependency:self]`, if the `TIPDependencyOperation` was an `NSOperation` 62 | (which it is not...really...it is not an `NSOperation`...) 63 | */ 64 | - (void)makeDependencyOfTargetOperation:(nonnull NSOperation *)op; 65 | 66 | /** 67 | See `[NSOperation waitUntilFinished]` 68 | */ 69 | - (void)waitUntilFinished; 70 | 71 | /** 72 | See `[NSOperation isFinished]`. 73 | KVO Compliant. 74 | */ 75 | - (BOOL)isFinished; 76 | 77 | /** 78 | See `[NSOperation isExecuting]`. 79 | KVO Compliant. 80 | */ 81 | - (BOOL)isExecuting; 82 | 83 | @end 84 | 85 | 86 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPFileUtils.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPFileUtils.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 7/14/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | #pragma mark - File Helpers 14 | 15 | //! Return the files (and directories) within the directory at the given _path_ 16 | FOUNDATION_EXTERN NSArray * __nullable TIPContentsAtPath(NSString *dirPath, 17 | NSError * __nullable * __nullable outError); 18 | //! Return the size of the file at the given path 19 | FOUNDATION_EXTERN NSUInteger TIPFileSizeAtPath(NSString *filePath, 20 | NSError * __nullable * __nullable outError); 21 | 22 | //! Return the last modified date of the file at the given _path_ 23 | FOUNDATION_EXTERN NSDate * __nullable TIPLastModifiedDateAtPath(NSString *path); 24 | //! Return the last modified date of the file at the given _pathURL_ 25 | FOUNDATION_EXTERN NSDate * __nullable TIPLastModifiedDateAtPathURL(NSURL *pathURL); 26 | 27 | //! Set the last modified _date_ of the file at the given _path_ 28 | FOUNDATION_EXTERN void TIPSetLastModifiedDateAtPath(NSString *path, 29 | NSDate *date); 30 | //! Set the last modified _date_ of the file at the given _pathURL_ 31 | FOUNDATION_EXTERN void TIPSetLastModifiedDateAtPathURL(NSURL *pathURL, 32 | NSDate *date); 33 | 34 | #pragma mark - File Extended Attribute Helpers 35 | 36 | /** 37 | keys to pull out of xattrs and kind is the object to store it as. 38 | Example: `@{ @"timestamp" : [NSDate class] }`. 39 | Supports `NSNumber`, `NSString`, `NSDate` and `NSURL`. 40 | Anything unsupported will not be retrieved. 41 | */ 42 | FOUNDATION_EXTERN NSDictionary * __nullable TIPGetXAttributesForFile(NSString *filePath, 43 | NSDictionary *keyKindMap); 44 | 45 | /** 46 | Supports objects that are `NSNumber`, `NSString`, `NSDate` and `NSURL` 47 | Returns the number of entries that succeeded. 48 | If returned count != xattrs.count, you know there was an error. 49 | */ 50 | FOUNDATION_EXTERN NSUInteger TIPSetXAttributesForFile(NSDictionary *xattrs, 51 | NSString *filePath); 52 | 53 | //! Returns an array of xattr names for the file at the given _filePath_ 54 | FOUNDATION_EXTERN NSArray * __nullable TIPListXAttributesForFile(NSString *filePath); 55 | 56 | /** Below functions return `0` on success, otherwise `errno` will be set with the error */ 57 | 58 | //! Set _number_ as the attribute with the given _name_ for _filePath_ 59 | FOUNDATION_EXTERN int TIPSetXAttributeNumberForFile(const char *name, 60 | NSNumber *number, 61 | const char *filePath); 62 | //! Set _string_ as the attribute with the given _name_ for _filePath_ 63 | FOUNDATION_EXTERN int TIPSetXAttributeStringForFile(const char *name, 64 | NSString *string, 65 | const char *filePath); 66 | //! Set _date_ as the attribute with the given _name_ for _filePath_ 67 | FOUNDATION_EXTERN int TIPSetXAttributeDateForFile(const char *name, 68 | NSDate *date, 69 | const char *filePath); 70 | //! Set _url_ as the attribute with the given _name_ for _filePath_ 71 | FOUNDATION_EXTERN int TIPSetXAttributeURLForFile(const char *name, 72 | NSURL *url, 73 | const char *filePath); 74 | 75 | //! Return the `NSString` for the attribute with the given _name_ for _filePath_ (or `nil`) 76 | FOUNDATION_EXTERN NSString * __nullable TIPGetXAttributeStringFromFile(const char *name, 77 | const char *filePath); 78 | //! Return the `NSNumber` for the attribute with the given _name_ for _filePath_ (or `nil`) 79 | FOUNDATION_EXTERN NSNumber * __nullable TIPGetXAttributeNumberFromFile(const char *name, 80 | const char *filePath); 81 | //! Return the `NSDate` for the attribute with the given _name_ for _filePath_ (or `nil`) 82 | FOUNDATION_EXTERN NSDate * __nullable TIPGetXAttributeDateFromFile(const char *name, 83 | const char *filePath); 84 | //! Return the `NSURL` for the attribute with the given _name_ for _filePath_ (or `nil`) 85 | FOUNDATION_EXTERN NSURL * __nullable TIPGetXAttributeURLFromFile(const char *name, 86 | const char *filePath); 87 | 88 | NS_ASSUME_NONNULL_END 89 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImageCodecs.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageCodecs.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 11/7/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIP_Project.h" 10 | #import "TIPError.h" 11 | #import "TIPImageCodecs.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | TIPImageContainer * __nullable TIPDecodeImageFromData(id codec, 16 | id __nullable config, 17 | NSData *imageData) __attribute__((overloadable)) 18 | { 19 | return TIPDecodeImageFromData(codec, config, imageData, nil); 20 | } 21 | 22 | TIPImageContainer * __nullable TIPDecodeImageFromData(id codec, 23 | id __nullable config, 24 | NSData *imageData, 25 | NSString * __nullable earlyGuessImageType) __attribute__((overloadable)) 26 | { 27 | TIPImageContainer *container = nil; 28 | id decoder = codec.tip_decoder; 29 | if ([decoder respondsToSelector:@selector(tip_decodeImageWithData:config:)]) { 30 | container = [decoder tip_decodeImageWithData:imageData config:config]; 31 | } else { 32 | const TIPImageDecoderDetectionResult result = [decoder tip_detectDecodableData:imageData 33 | earlyGuessImageType:earlyGuessImageType]; 34 | if (TIPImageDecoderDetectionResultMatch == result) { 35 | id context = [decoder tip_initiateDecoding:config 36 | expectedDataLength:imageData.length 37 | buffer:nil]; 38 | [decoder tip_append:context data:imageData]; 39 | if (TIPImageDecoderAppendResultDidCompleteLoading == [decoder tip_finalizeDecoding:context]) { 40 | container = [decoder tip_renderImage:context 41 | mode:TIPImageDecoderRenderModeCompleteImage]; 42 | } 43 | } 44 | } 45 | return container; 46 | } 47 | 48 | BOOL TIPEncodeImageToFile(id codec, 49 | TIPImageContainer *imageContainer, 50 | NSString *filePath, 51 | TIPImageEncodingOptions options, 52 | float quality, 53 | BOOL atomic, 54 | NSError * __autoreleasing __nullable * __nullable error) 55 | { 56 | BOOL success = NO; 57 | id encoder = codec.tip_encoder; 58 | if (!encoder) { 59 | if (error) { 60 | *error = [NSError errorWithDomain:TIPErrorDomain 61 | code:TIPErrorCodeEncodingUnsupported 62 | userInfo:@{ @"codec" : codec }]; 63 | } 64 | } else { 65 | if ([encoder respondsToSelector:@selector(tip_writeToFile:withImage:encodingOptions:suggestedQuality:atomically:error:)]) { 66 | success = [encoder tip_writeToFile:filePath 67 | withImage:imageContainer 68 | encodingOptions:options 69 | suggestedQuality:quality 70 | atomically:atomic 71 | error:error]; 72 | } else { 73 | NSData *data = [encoder tip_writeDataWithImage:imageContainer 74 | encodingOptions:options 75 | suggestedQuality:quality 76 | error:error]; 77 | if (data) { 78 | success = [data writeToFile:filePath options:(atomic) ? NSDataWritingAtomic : 0 error:error]; 79 | } 80 | } 81 | } 82 | 83 | if (!success && error) { 84 | TIPAssert(*error != nil); 85 | if (!*error) { 86 | *error = [NSError errorWithDomain:TIPErrorDomain 87 | code:TIPErrorCodeUnknown 88 | userInfo:nil]; 89 | } 90 | } 91 | 92 | return success; 93 | } 94 | 95 | NS_ASSUME_NONNULL_END 96 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImageFetchMetrics.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchMetrics.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/19/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | #import 13 | 14 | @class TIPImageFetchMetricInfo; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 19 | Class that encapsulates all the metric information related to the `TIPImageFetchOperation` 20 | */ 21 | @interface TIPImageFetchMetrics : NSObject 22 | 23 | /** The duration it took for the entire `TIPImageFetchOperation` to complete (or fail) */ 24 | @property (nonatomic, readonly) NSTimeInterval totalDuration; 25 | /** 26 | The duration it took for the first presentable image to be loaded. 27 | This could be a preview image, a progressive frame or the final image. 28 | */ 29 | @property (nonatomic, readonly) NSTimeInterval firstImageLoadDuration; 30 | /** Whether the operation was cancelled */ 31 | @property (nonatomic, readonly) BOOL wasCancelled; 32 | 33 | /** 34 | Retrieve the breakdown of metric info for a given `TIPImageLoadSource`. 35 | @param source The source of interest. 36 | @return the metric info for the given _source_. If the _source_ was never accessed, `nil` will be 37 | returned. 38 | */ 39 | - (nullable TIPImageFetchMetricInfo *)metricInfoForSource:(TIPImageLoadSource)source; 40 | 41 | /** `NS_UNAVAILABLE` */ 42 | - (instancetype)init NS_UNAVAILABLE; 43 | /** `NS_UNAVAILABLE` */ 44 | - (instancetype)new NS_UNAVAILABLE; 45 | 46 | @end 47 | 48 | /** 49 | The result of a `TIPImageLoadSource`'s fetch 50 | */ 51 | typedef NS_ENUM(NSInteger, TIPImageFetchLoadResult){ 52 | /** The source never finished its fetch */ 53 | TIPImageFetchLoadResultNeverCompleted = -1, 54 | /** The source did not yield an image */ 55 | TIPImageFetchLoadResultMiss = 0, 56 | /** The source yielded a preview image */ 57 | TIPImageFetchLoadResultHitPreview, 58 | /** The source yielded a progress frame */ 59 | TIPImageFetchLoadResultHitProgressFrame, 60 | /** The source yielded the final image */ 61 | TIPImageFetchLoadResultHitFinal, 62 | }; 63 | 64 | /** Class that encapsulates the metric info for a specific `TIPImageLoadSource` */ 65 | @interface TIPImageFetchMetricInfo : NSObject 66 | 67 | /** The source for the metric info */ 68 | @property (nonatomic, readonly) TIPImageLoadSource source; 69 | /** The result of the fetch on this source */ 70 | @property (nonatomic, readonly) TIPImageFetchLoadResult result; 71 | /** Whether the operation cancelled while investigating this source */ 72 | @property (nonatomic, readonly) BOOL wasCancelled; 73 | 74 | /** The duration for this source to miss, hit or get interrupted */ 75 | @property (nonatomic, readonly) NSTimeInterval loadDuration; 76 | /** The fetch was synchronous (`TIPImageLoadSourceMemoryCache` == `source` only) */ 77 | @property (nonatomic, readonly) BOOL wasLoadedSynchronously; 78 | 79 | /** `NS_UNAVAILABLE` */ 80 | - (instancetype)init NS_UNAVAILABLE; 81 | /** `NS_UNAVAILABLE` */ 82 | - (instancetype)new NS_UNAVAILABLE; 83 | 84 | @end 85 | 86 | /** 87 | Info specifically for a `TIPImageFetchMetricInfo` that has a `[TIPImageFetchMetricInfo source]` 88 | equal to `TIPImageLoadSourceNetwork` or `TIPImageLoadSourceNetworkResumed`. 89 | */ 90 | @interface TIPImageFetchMetricInfo (NetworkSourceInfo) 91 | 92 | /** Opaque "metrics" object provided by the `TIPImageFetchDownload`. */ 93 | @property (nonatomic, readonly, nullable) id networkMetrics; 94 | /** The `NSURLRequest` for the network download */ 95 | @property (nonatomic, readonly, nullable) NSURLRequest *networkRequest; 96 | 97 | /** Time for the network load */ 98 | @property (nonatomic, readonly) NSTimeInterval totalNetworkLoadDuration; 99 | /** Time for the first progressive frame to load over the network */ 100 | @property (nonatomic, readonly) NSTimeInterval firstProgressiveFrameNetworkLoadDuration; 101 | /** Size of the image file (not necessarily bytes downloaded since resumed would be smaller) */ 102 | @property (nonatomic, readonly) NSUInteger networkImageSizeInBytes; 103 | /** Image type downloaded */ 104 | @property (nonatomic, copy, readonly, nullable) NSString *networkImageType; 105 | /** Image dimensions (in pixels) */ 106 | @property (nonatomic, readonly) CGSize networkImageDimensions; 107 | /** The pixels per byte ratio (larger indicates more compressed encoding) */ 108 | @property (nonatomic, readonly) float networkImagePixelsPerByte; 109 | 110 | @end 111 | 112 | NS_ASSUME_NONNULL_END 113 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImageFetchProgressiveLoadingPolicies.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchProgressiveLoadingPolicies.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 4/15/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | A policy that loads the first and last frames of a progressive image. 15 | */ 16 | @interface TIPFirstAndLastFrameProgressiveLoadingPolicy : NSObject 17 | 18 | /** Whether the policy should render the low quality frame. Default is `YES`. */ 19 | @property (nonatomic) BOOL shouldRenderLowQualityFrame; 20 | 21 | @end 22 | 23 | /** 24 | A policy that loads all frames (skipping the low quality frame if `shouldRenderLowQualityFrame` is 25 | `NO`). 26 | */ 27 | @interface TIPFullFrameProgressiveLoadingPolicy : NSObject 28 | 29 | /** Whether the policy should render the low quality frame. Default is `YES`. */ 30 | @property (nonatomic) BOOL shouldRenderLowQualityFrame; 31 | 32 | @end 33 | 34 | /** 35 | A policy that loads any progress encountered, even incomplete frames. 36 | */ 37 | @interface TIPGreedyProgressiveLoadingPolicy : NSObject 38 | 39 | /** 40 | The minimum amount of progress required before attempting to render a progressive image. 41 | Default == 0.0f 42 | */ 43 | @property (nonatomic) float minimumProgress; 44 | @end 45 | 46 | /** 47 | A policy that loads as much as possible after the `minimumProgress` has been completed (even 48 | incomplete frames) and the last final frame. 49 | */ 50 | @interface TIPFirstAndLastOpportunityProgressiveLoadingPolicy : NSObject 51 | 52 | /** 53 | The minimum amount of progress required before attempting to render a progressive image. 54 | Default == 0.0f 55 | */ 56 | @property (nonatomic) float minimumProgress; 57 | @end 58 | 59 | /** 60 | A policy that never loads any progress encountered, not even complete frames. 61 | */ 62 | @interface TIPDisabledProgressiveLoadingPolicy : NSObject 63 | @end 64 | 65 | /** 66 | A policy to wrap another policy that is weakly held to help avoid retain cycles if they would be 67 | possible. Behaves just like the `TIPDisabledProgressiveLoadingPolicy` if the wrapped policy has 68 | become `nil`. 69 | */ 70 | @interface TIPWrapperProgressiveLoadingPolicy : NSObject 71 | /** The wrapped policy or `nil` */ 72 | @property (nonatomic, readonly, weak, nullable) id wrappedPolicy; 73 | /** Designated initializer. Provide the _policy_ to wrap. */ 74 | - (instancetype)initWithProgressiveLoadingPolicy:(nullable id)policy NS_DESIGNATED_INITIALIZER; 75 | @end 76 | 77 | NS_ASSUME_NONNULL_END 78 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImageFetchProgressiveLoadingPolicy+StaticClass.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchProgressiveLoadingPolicy+StaticClass.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 4/15/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #if !APPLEDOC 10 | // Concrete interface to for `TIPImageFetchProgressiveLoadingPolicy` to have static methods. 11 | // See `TIPImageFetchProgressiveLoadingPolicy(ClassMethods)` 12 | NS_ROOT_CLASS 13 | @interface TIPImageFetchProgressiveLoadingPolicy 14 | @end 15 | #endif 16 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImageFetchTransformer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchTransformer.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 5/4/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class TIPImageFetchOperation; 13 | @class UIImage; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /** 18 | Protocol for transforming an image fetched by a `TIPImageFetchOperation`. 19 | Implementers can do whatever they like to the given image: scale, blur, colorize, etc. 20 | Target sizing hints are provided as a convenience. If scaling to the target sizing 21 | is viable for the transform, doing so can be an optimization. 22 | */ 23 | @protocol TIPImageFetchTransformer 24 | 25 | @required 26 | 27 | /** 28 | Transform the provided _image_ 29 | 30 | @param image the `UIImage` to transform 31 | @param progress the progress state of the image. `1.f` is final, lt `0.f` is preview, others are progressive scans 32 | @param targetDimensions hint `CGSize` for the target. `CGSizeZero` for no hint. 33 | @param targetContentMode hint `UIViewContentMode` for the target. 34 | @param op the `TIPImageFetchOperation` being targeted for transform. 35 | 36 | @return the desired transformed `UIImage`. `nil` to not transform. 37 | 38 | @note Transformed images are NOT cached. The cache will persist the raw source image. 39 | */ 40 | - (nullable UIImage *)tip_transformImage:(UIImage *)image 41 | withProgress:(float)progress 42 | hintTargetDimensions:(CGSize)targetDimensions 43 | hintTargetContentMode:(UIViewContentMode)targetContentMode 44 | forImageFetchOperation:(TIPImageFetchOperation *)op; 45 | 46 | @optional 47 | 48 | /** 49 | The unique identifier for this transformer. 50 | @warning If you do not specify an identifier, `TIPImageFetchRequest` instances with this transformer 51 | will not be able to take advantage of the synchronous rendered cache. 52 | */ 53 | - (NSString *)tip_transformerIdentifier; 54 | 55 | @end 56 | 57 | NS_ASSUME_NONNULL_END 58 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImageFetchable.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageFetchable.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 11/27/17. 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | A protocol for enabling a `UIView` to become compliant with `TIPImageViewFetchHelper` 15 | */ 16 | @protocol TIPImageFetchable 17 | 18 | @required 19 | 20 | /** 21 | This property is to set the underlying image of the view for display. 22 | For example, __TIP__ implements `TIPImageFetchable` on `UIImageView` such that 23 | the `tip_fetchedImage` just redirects to `UIImageView` class' `image` property. 24 | */ 25 | @property (nonatomic, readwrite, nullable) UIImage *tip_fetchedImage; 26 | 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPImagePipelineInspectionResult.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImagePipelineInspectionResult.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/15/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | @class TIPImagePipeline; 14 | @protocol TIPImagePipelineInspectionResultEntry; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 19 | Results for the inspection of a `TIPImagePipeline` 20 | */ 21 | @interface TIPImagePipelineInspectionResult : NSObject 22 | 23 | /** The inspected `TIPImagePipeline` */ 24 | @property (nonatomic, readonly) TIPImagePipeline *imagePipeline; 25 | 26 | /** The rendered cache entries that are complete */ 27 | @property (nonatomic, readonly) NSArray> *completeRenderedEntries; 28 | /** The memory cache entries that are complete */ 29 | @property (nonatomic, readonly) NSArray> *completeMemoryEntries; 30 | /** The disk cache entries that are complete */ 31 | @property (nonatomic, readonly) NSArray> *completeDiskEntries; 32 | 33 | /** The memory cache entries that are partial (incomplete) */ 34 | @property (nonatomic, readonly) NSArray> *partialMemoryEntries; 35 | /** The disk cache entries that are partial (incomplete) */ 36 | @property (nonatomic, readonly) NSArray> *partialDiskEntries; 37 | 38 | /** number of bytes used in memory caches */ 39 | @property (nonatomic, readonly) unsigned long long inMemoryBytesUsed; 40 | /** number of bytes used on disk */ 41 | @property (nonatomic, readonly) unsigned long long onDiskBytesUsed; 42 | 43 | /** `NS_UNAVAILABLE` */ 44 | - (instancetype)init NS_UNAVAILABLE; 45 | /** `NS_UNAVAILABLE` */ 46 | + (instancetype)new NS_UNAVAILABLE; 47 | 48 | @end 49 | 50 | /** 51 | An entry for a particular cache that was inspected 52 | */ 53 | @protocol TIPImagePipelineInspectionResultEntry 54 | 55 | @required 56 | 57 | /** the image identifier */ 58 | @property (nonatomic, readonly, copy, nullable) NSString *identifier; 59 | /** the image URL */ 60 | @property (nonatomic, readonly, nullable) NSURL *URL; 61 | /** the image dimensions (in pixels) */ 62 | @property (nonatomic, readonly) CGSize dimensions; 63 | /** the bytes used by the image (on disk size is encoded bytes, in memory is decoded bytes) */ 64 | @property (nonatomic, readonly) unsigned long long bytesUsed; 65 | 66 | /** progress (less than `1.f` for partial/incomplete entries) */ 67 | @property (nonatomic, readonly) float progress; 68 | /** the image itself */ 69 | @property (nonatomic, readonly, nullable) UIImage *image; 70 | 71 | @end 72 | 73 | NS_ASSUME_NONNULL_END 74 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPLogger.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 7/14/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | Syslog compatible log levels for use with *TIPLogger*. 15 | */ 16 | typedef NS_ENUM(NSInteger, TIPLogLevel) 17 | { 18 | /** Present for syslog compatability */ 19 | TIPLogLevelEmergency, 20 | /** Present for syslog compatability */ 21 | TIPLogLevelAlert, 22 | /** Present for syslog compatability */ 23 | TIPLogLevelCritical, 24 | /** The _ERROR_ log level */ 25 | TIPLogLevelError, 26 | /** The _WARNING_ log level */ 27 | TIPLogLevelWarning, 28 | /** Present for syslog compatability */ 29 | TIPLogLevelNotice, 30 | /** The _INFORMATION_ log level */ 31 | TIPLogLevelInformation, 32 | /** The _DEBUG_ log level */ 33 | TIPLogLevelDebug 34 | }; 35 | 36 | /** 37 | Protocol for supporting log statements from *TwitterImagePipeline* 38 | See `[TIPGlobalConfiguration logger]` 39 | */ 40 | @protocol TIPLogger 41 | 42 | @required 43 | 44 | /** 45 | Method called when logging a message from *TwitterImagePipeline* 46 | */ 47 | - (void)tip_logWithLevel:(TIPLogLevel)level file:(NSString *)file function:(NSString *)function line:(int)line message:(NSString *)message; 48 | 49 | @optional 50 | 51 | /** 52 | Optional method to determine if a message should be logged as an optimization to avoid argument 53 | execution of the log message. 54 | 55 | Default == `YES` 56 | */ 57 | - (BOOL)tip_canLogWithLevel:(TIPLogLevel)level; 58 | 59 | @end 60 | 61 | NS_ASSUME_NONNULL_END 62 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPProgressive.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPProgressive.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 7/14/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | @class TIPImageFetchOperation; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | #pragma mark - Constants 18 | 19 | /** 20 | The latest progress made in loading a progressive image. 21 | See `[TIPImageFetchProgressiveLoadingPolicy tip_imageFetchOperation:behaviorForProgress:frameCount:progress:type:dimensions:renderCount:]`. 22 | */ 23 | typedef NS_ENUM(NSInteger, TIPImageFetchProgress) 24 | { 25 | /** No progress was made */ 26 | TIPImageFetchProgressNone = 0, 27 | /** Data was loaded, but no frames were completed */ 28 | TIPImageFetchProgressPartialFrame, 29 | /** One or more full frames finished loading */ 30 | TIPImageFetchProgressFullFrame, 31 | }; 32 | 33 | /** 34 | The behavior for how to update the delegate with the progressive image that has been loaded thus 35 | far. 36 | See `[TIPImageFetchProgressiveLoadingPolicy tip_imageFetchOperation:behaviorForProgress:frameCount:progress: type:dimensions:renderCount:]`. 37 | */ 38 | typedef NS_ENUM(NSInteger, TIPImageFetchProgressUpdateBehavior) 39 | { 40 | /** Don't update the progressive image to the delegate */ 41 | TIPImageFetchProgressUpdateBehaviorNone = TIPImageFetchProgressNone, 42 | /** Update the progressive image to the delegate with as much of the image that is loaded */ 43 | TIPImageFetchProgressUpdateBehaviorUpdateWithAnyProgress = TIPImageFetchProgressPartialFrame, 44 | /** Update the progressive image to the delegate but only with the latest fully loaded frame */ 45 | TIPImageFetchProgressUpdateBehaviorUpdateWithFullFrameProgress = TIPImageFetchProgressFullFrame 46 | }; 47 | 48 | #pragma mark - Protocols 49 | 50 | /** 51 | `TIPImageFetchProgressiveLoadingPolicy` is a protocol for supporting custom progressive loading 52 | behavior. By providing a progressive loading policy to a `TIPImageFetchRequest`, the image can be 53 | progressively loaded in any desired fashion from loading as much as possible as bytes are loaded, 54 | to loading only full frames, to loading differently based on context. 55 | */ 56 | @protocol TIPImageFetchProgressiveLoadingPolicy 57 | 58 | @required 59 | /** 60 | The callback used to determine how a progressive image should be updated as it is loaded. 61 | Requires the image being requested support progressive loading AND the delegate must implement 62 | `imageFetchOperation:shouldLoadProgressivelyWithIdentifier:URL:imageType:originalDimensions:` to 63 | return `YES`. 64 | 65 | @param op The related image fetch operation 66 | @param frameProgress `TIPImageFetchProgress` since last full frame 67 | @param frameCount number of full frames loaded, doesn't include any partial frame progress 68 | @param progress total progress 69 | @param type the _TIPImageType_ of the image being progressively loaded 70 | @param dimensions the dimensions of the image being loaded 71 | @param renderCount the number of times the progress has been rendered thus far 72 | 73 | @return the `TIPImageFetchProgressUpdateBehavior` to perform. 74 | `TIPImageFetchProgressUpdateBehaviorNone` == don't update, 75 | `TIPImageFetchProgressUpdateBehaviorUpdateWithAnyProgress` == update no matter the progress, 76 | `TIPImageFetchProgressUpdateBehaviorUpdateWithFullFrameProgress` == only update to that last full 77 | frame loaded, nothing further. By default, if there is no policy, a strong default will be used. 78 | */ 79 | - (TIPImageFetchProgressUpdateBehavior)tip_imageFetchOperation:(TIPImageFetchOperation *)op behaviorForProgress:(TIPImageFetchProgress)frameProgress frameCount:(NSUInteger)frameCount progress:(float)progress type:(NSString *)type dimensions:(CGSize)dimensions renderCount:(NSUInteger)renderCount; 80 | 81 | @end 82 | 83 | NS_ASSUME_NONNULL_END 84 | 85 | #pragma mark - Class Methods 86 | 87 | #import 88 | 89 | NS_ASSUME_NONNULL_BEGIN 90 | 91 | /** 92 | A category on the `TIPImageFetchProgressiveLoadingPolicy` protocol for class methods. 93 | */ 94 | @interface TIPImageFetchProgressiveLoadingPolicy (ClassMethods) 95 | /** 96 | The default progressive loading policies if progressive loading is supported and enabled. 97 | The returned `NSDictionary` uses `NSNumber` wrapped `TIPImageType` values as keys. 98 | If a progressive loading policy is not given on a request that is loading an image type that is 99 | progressive, the default policy will be used. 100 | 101 | `TIPImageTypeJPEG`: 102 | - 64-bit: `TIPFullFrameProgressiveLoadingPolicy` w/ `shouldRenderLowQualityFrame` == `YES` 103 | - 32-bit: `TIPFirstAndLastFrameProgressiveLoadingPolicy` w/ `shouldRenderLowQualityFrame` == `YES` 104 | */ 105 | + (NSDictionary> *)defaultProgressiveLoadingPolicies; 106 | @end 107 | 108 | NS_ASSUME_NONNULL_END 109 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPSafeOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPSafeOperation.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/1/17 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | `TIPSafeOperation` works to encapsulate fixes for `NSOperation`. 15 | 16 | Specifically: 17 | - `NSOperation` is supposed to clear the `completionBlock` after it has been called. It does do this on macOS, but not on iOS. `TIPSafeOperation` fixes this. 18 | */ 19 | @interface TIPSafeOperation : NSOperation 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TIPSafeOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPSafeOperation.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 6/1/17 6 | // Copyright © 2017 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPSafeOperation.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | static BOOL _NSOperationHasCompletionBlockBug(void) 14 | { 15 | static BOOL sHasBug = NO; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | NSProcessInfo *processInfo = [NSProcessInfo processInfo]; 19 | if (![processInfo respondsToSelector:@selector(isOperatingSystemAtLeastVersion:)]) { 20 | // Technically, on iOS 7 and lower, it's not a bug but rather by design... 21 | // For our purposes though, we'll treat it as a bug so we can use our "safety" support 22 | sHasBug = YES; 23 | return; 24 | } 25 | #if TARGET_OS_WATCH 26 | // fixed watchOS 4 27 | sHasBug = ![processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 4, 0 , 0}]; 28 | #elif TARGET_OS_IOS || TARGET_OS_TV 29 | // fixed iOS/tvOS 11 30 | sHasBug = ![processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 11, 0 , 0}]; 31 | #else 32 | // macOS or newer OSes, no bug 33 | #endif 34 | }); 35 | return sHasBug; 36 | } 37 | 38 | @implementation TIPSafeOperation 39 | 40 | - (void)setCompletionBlock:(nullable void (^)(void))completionBlock 41 | { 42 | if (!_NSOperationHasCompletionBlockBug()) { 43 | [super setCompletionBlock:completionBlock]; 44 | return; 45 | } 46 | 47 | #pragma clang diagnostic push 48 | #pragma clang diagnostic ignored "-Warc-retain-cycles" 49 | [super setCompletionBlock:^{ 50 | if (completionBlock) { 51 | completionBlock(); 52 | } 53 | 54 | [self tip_clearCompletionBlock]; 55 | }]; 56 | #pragma clang diagnostic pop 57 | } 58 | 59 | - (void)tip_clearCompletionBlock 60 | { 61 | [super setCompletionBlock:NULL]; 62 | } 63 | 64 | @end 65 | 66 | NS_ASSUME_NONNULL_END 67 | -------------------------------------------------------------------------------- /TwitterImagePipeline/TwitterImagePipeline.h: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterImagePipeline.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/4/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #pragma mark TIP Headers 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | #import 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | 36 | #pragma mark TIP Categories 37 | 38 | #import 39 | #import 40 | -------------------------------------------------------------------------------- /TwitterImagePipeline/UIView+TIPImageFetchable.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+TIPImageFetchable.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 3/19/15. 6 | // Copyright (c) 2015 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class TIPImageViewFetchHelper; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | Convenience category for `UIView` for associating a `TIPImageViewFetchHelper` 18 | */ 19 | @interface UIView (TIPImageFetchable) 20 | 21 | /** 22 | An `TIPImageViewFetchHelper` for loading `TIPImageFetchRequests` into this view. 23 | 24 | Setting this value for the first time will add an invisible "observer" 25 | subview to this view for observing events that `TIPImageViewFetchHelper` needs 26 | in order to work properly. 27 | 28 | @warning the `tip_fetchHelper` may only be set on `UIView` instances that adopt 29 | `TIPImageFetchable`. To extend fetch helper support to a new `UIView` subclass, 30 | implement a category for that object that implements `TIPImageFetchable`. 31 | */ 32 | @property (nullable, nonatomic) TIPImageViewFetchHelper *tip_fetchHelper; 33 | 34 | @end 35 | 36 | /** 37 | Convenience category for `UIImageView` for adopting `TIPImageFetchable`. 38 | Just maps `tip_fetchedImage` to `image`, easy. 39 | */ 40 | @interface UIImageView (TIPImageFetchable) 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPImageViewTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPImageViewTests.m 3 | // TwitterImagePipeline 4 | // 5 | // Created by Brandon Carpenter on 4/3/17. 6 | // Copyright © 2017 Twitter, Inc. All rights reserved. 7 | // 8 | 9 | #import "TIPImageViewFetchHelper.h" 10 | #import "UIView+TIPImageFetchable.h" 11 | 12 | @import XCTest; 13 | 14 | #pragma mark - 15 | 16 | @interface TestEventRecordingFetchHelper : TIPImageViewFetchHelper 17 | @property (nonatomic, readonly) NSInteger triggerViewWillDisappearCount; 18 | @property (nonatomic, readonly) NSInteger triggerViewDidDisappearCount; 19 | @property (nonatomic, readonly) NSInteger triggerViewWillAppearCount; 20 | @property (nonatomic, readonly) NSInteger triggerViewDidAppearCount; 21 | @property (nonatomic, readonly) NSInteger triggerViewLayingOutSubviewsCount; 22 | @end 23 | 24 | @implementation TestEventRecordingFetchHelper 25 | 26 | - (void)triggerViewWillDisappear 27 | { 28 | _triggerViewWillDisappearCount++; 29 | [super triggerViewWillDisappear]; 30 | } 31 | 32 | - (void)triggerViewDidDisappear 33 | { 34 | _triggerViewDidDisappearCount++; 35 | [super triggerViewDidDisappear]; 36 | } 37 | 38 | - (void)triggerViewWillAppear 39 | { 40 | _triggerViewWillAppearCount++; 41 | [super triggerViewWillAppear]; 42 | } 43 | 44 | - (void)triggerViewDidAppear 45 | { 46 | _triggerViewDidAppearCount++; 47 | [super triggerViewDidAppear]; 48 | } 49 | 50 | - (void)triggerViewLayingOutSubviews 51 | { 52 | _triggerViewLayingOutSubviewsCount++; 53 | [super triggerViewLayingOutSubviews]; 54 | } 55 | 56 | @end 57 | 58 | #pragma mark - 59 | 60 | @interface UIImageView_TIPImageViewFetchHelperTest : XCTestCase 61 | @property (nonatomic) UIWindow *window; 62 | @property (nonatomic) UIImageView *imageView; 63 | @property (nonatomic) TestEventRecordingFetchHelper *fetchHelper; 64 | @end 65 | 66 | @implementation UIImageView_TIPImageViewFetchHelperTest 67 | 68 | - (void)setUp 69 | { 70 | [super setUp]; 71 | TestEventRecordingFetchHelper *fetchHelper = [[TestEventRecordingFetchHelper alloc] init]; 72 | UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 300.0, 300.0)]; 73 | UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(50.0, 50.0, 50.0, 50.0)]; 74 | imageView.tip_fetchHelper = fetchHelper; 75 | [window addSubview:imageView]; 76 | self.window = window; 77 | self.imageView = imageView; 78 | self.fetchHelper = fetchHelper; 79 | } 80 | 81 | - (void)tearDown 82 | { 83 | self.window = nil; 84 | self.imageView = nil; 85 | self.fetchHelper = nil; 86 | [super tearDown]; 87 | } 88 | 89 | - (void)testImageViewBecameVisible 90 | { 91 | // Image view became visible during setUp. 92 | XCTAssertEqual(self.fetchHelper.triggerViewWillAppearCount, 1); 93 | XCTAssertEqual(self.fetchHelper.triggerViewDidAppearCount, 1); 94 | } 95 | 96 | - (void)testImageViewBecameInvisible 97 | { 98 | [self.imageView removeFromSuperview]; 99 | XCTAssertEqual(self.fetchHelper.triggerViewWillDisappearCount, 1); 100 | XCTAssertEqual(self.fetchHelper.triggerViewDidDisappearCount, 1); 101 | } 102 | 103 | - (void)testImageViewResized 104 | { 105 | self.imageView.frame = CGRectMake(50.0, 50.0, 200.0, 200.0); 106 | [self.imageView layoutIfNeeded]; 107 | XCTAssertEqual(self.fetchHelper.triggerViewLayingOutSubviewsCount, 1); 108 | } 109 | 110 | - (void)testImageViewHidden 111 | { 112 | // Visible from set up 113 | XCTAssertEqual(self.fetchHelper.triggerViewWillAppearCount, 1); 114 | XCTAssertEqual(self.fetchHelper.triggerViewDidAppearCount, 1); 115 | XCTAssertEqual(self.fetchHelper.triggerViewWillDisappearCount, 0); 116 | XCTAssertEqual(self.fetchHelper.triggerViewDidDisappearCount, 0); 117 | 118 | // Hide 119 | self.imageView.hidden = YES; 120 | XCTAssertEqual(self.fetchHelper.triggerViewWillAppearCount, 1); 121 | XCTAssertEqual(self.fetchHelper.triggerViewDidAppearCount, 1); 122 | XCTAssertEqual(self.fetchHelper.triggerViewWillDisappearCount, 1); 123 | XCTAssertEqual(self.fetchHelper.triggerViewDidDisappearCount, 1); 124 | 125 | // Show 126 | self.imageView.hidden = NO; 127 | XCTAssertEqual(self.fetchHelper.triggerViewWillAppearCount, 2); 128 | XCTAssertEqual(self.fetchHelper.triggerViewDidAppearCount, 2); 129 | XCTAssertEqual(self.fetchHelper.triggerViewWillDisappearCount, 1); 130 | XCTAssertEqual(self.fetchHelper.triggerViewDidDisappearCount, 1); 131 | } 132 | 133 | @end 134 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPTestImageFetchDownloadInternalWithStubbing.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTestImageFetchDownloadInternalWithStubbing.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/28/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPImageFetchDownloadInternal.h" 10 | 11 | @interface TIPTestImageFetchDownloadInternalWithStubbing : TIPImageFetchDownloadInternal 12 | - (instancetype)initWithContext:(id)context NS_UNAVAILABLE; 13 | - (instancetype)initWithContext:(id)context stub:(BOOL)stub; 14 | @end 15 | 16 | @interface TIPTestImageFetchDownloadProviderInternalWithStubbing : NSObject 17 | @property (nonatomic, readwrite) BOOL downloadStubbingEnabled; 18 | @end 19 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPTestImageFetchDownloadInternalWithStubbing.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTestImageFetchDownloadInternalWithStubbing.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/28/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPTestImageFetchDownloadInternalWithStubbing.h" 10 | #import "TIPTestURLProtocol.h" 11 | 12 | static NSURLSession *sTIPTestImageFetchDownloadInternalURLSessionWithPseudo = nil; 13 | 14 | @interface TIPTestURLProtocol (TIPConvenience) 15 | 16 | + (void)tip_registerResponseData:(nullable NSData *)responseData responseMIMEType:(nullable NSString *)MIMEType shouldSupportResuming:(BOOL)shouldSupportResume suggestedBitrate:(uint64_t)suggestedBitrate withEndpoint:(nonnull NSURL *)endpointURL; 17 | 18 | @end 19 | 20 | @implementation TIPTestImageFetchDownloadProviderInternalWithStubbing 21 | 22 | - (id)imageFetchDownloadWithContext:(id)context 23 | { 24 | return [[TIPTestImageFetchDownloadInternalWithStubbing alloc] initWithContext:context stub:self.downloadStubbingEnabled]; 25 | } 26 | 27 | - (void)addDownloadStubForRequestURL:(NSURL *)requestURL responseData:(NSData *)responseData responseMIMEType:(NSString *)MIMEType shouldSupportResuming:(BOOL)shouldSupportResume suggestedBitrate:(uint64_t)suggestedBitrate 28 | { 29 | [TIPTestURLProtocol tip_registerResponseData:responseData responseMIMEType:MIMEType shouldSupportResuming:shouldSupportResume suggestedBitrate:suggestedBitrate withEndpoint:requestURL]; 30 | } 31 | 32 | - (void)removeDownloadStubForRequestURL:(NSURL *)requestURL 33 | { 34 | [TIPTestURLProtocol unregisterEndpoint:requestURL]; 35 | } 36 | 37 | - (void)removeAllDownloadStubs 38 | { 39 | [TIPTestURLProtocol unregisterAllEndpoints]; 40 | } 41 | 42 | @end 43 | 44 | @implementation TIPTestImageFetchDownloadInternalWithStubbing 45 | { 46 | BOOL _stub; 47 | } 48 | 49 | - (instancetype)initWithContext:(id)context stub:(BOOL)stub 50 | { 51 | if (self = [super initWithContext:context]) { 52 | _stub = stub; 53 | } 54 | return self; 55 | } 56 | 57 | - (NSURLSession *)URLSession 58 | { 59 | if (!_stub) { 60 | return [super URLSession]; 61 | } 62 | 63 | static dispatch_once_t onceToken; 64 | dispatch_once(&onceToken, ^{ 65 | NSURLSession *session = [super URLSession]; 66 | NSURLSessionConfiguration *config = [session.configuration copy]; 67 | id delegate = session.delegate; 68 | NSOperationQueue *queue = session.delegateQueue; 69 | 70 | NSMutableArray *protocols = [config.protocolClasses mutableCopy]; 71 | [protocols insertObject:[TIPTestURLProtocol class] atIndex:0]; 72 | config.protocolClasses = protocols; 73 | 74 | sTIPTestImageFetchDownloadInternalURLSessionWithPseudo = [NSURLSession sessionWithConfiguration:config delegate:delegate delegateQueue:queue]; 75 | }); 76 | 77 | return sTIPTestImageFetchDownloadInternalURLSessionWithPseudo; 78 | } 79 | 80 | @end 81 | 82 | @implementation TIPTestURLProtocol (TIPConvenience) 83 | 84 | + (void)tip_registerResponseData:(NSData *)responseData responseMIMEType:(NSString *)MIMEType shouldSupportResuming:(BOOL)shouldSupportResume suggestedBitrate:(uint64_t)suggestedBitrate withEndpoint:(NSURL *)endpointURL 85 | { 86 | NSHTTPURLResponse *response = [NSHTTPURLResponse tip_responseWithRequestURL:endpointURL dataLength:responseData.length responseMIMEType:MIMEType]; 87 | 88 | TIPTestURLProtocolResponseConfig *config = [[TIPTestURLProtocolResponseConfig alloc] init]; 89 | config.bps = suggestedBitrate; 90 | config.canProvideRange = shouldSupportResume; 91 | 92 | [TIPTestURLProtocol registerURLResponse:response body:responseData config:config withEndpoint:endpointURL]; 93 | } 94 | 95 | @end 96 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPTestURLProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTestURLProtocol.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 9/9/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | FOUNDATION_EXTERN NSString * const TIPTestURLProtocolErrorDomain; 14 | 15 | @class TIPTestURLProtocolResponseConfig; 16 | 17 | @interface TIPTestURLProtocol : NSURLProtocol 18 | 19 | + (void)registerURLResponse:(NSHTTPURLResponse *)response 20 | body:(nullable NSData *)body 21 | config:(nullable TIPTestURLProtocolResponseConfig *)config 22 | withEndpoint:(NSURL *)endpoint; 23 | + (void)registerURLResponse:(NSHTTPURLResponse *)response 24 | body:(nullable NSData *)body 25 | withEndpoint:(NSURL *)endpoint; 26 | 27 | + (void)unregisterEndpoint:(NSURL *)endpoint; 28 | + (void)unregisterAllEndpoints; 29 | 30 | + (BOOL)isEndpointRegistered:(NSURL *)endpoint; 31 | 32 | @end 33 | 34 | @interface TIPTestURLProtocolResponseConfig : NSObject 35 | 36 | @property (nonatomic) uint64_t bps; // bits per second, 0 == unlimited 37 | @property (nonatomic) uint64_t latency; // in milliseconds 38 | @property (nonatomic) uint64_t delay; // in milliseconds 39 | @property (nonatomic, nullable) NSError *failureError; // nil == no error 40 | @property (nonatomic) NSInteger statusCode; // 0 == don't override 41 | @property (nonatomic) BOOL canProvideRange; // default == YES 42 | @property (nonatomic, copy, nullable) NSString *stringForIfRange; // default == nil, nil == match any, @"" == match nothing 43 | 44 | @property (nonatomic, copy, nullable) NSDictionary *extraRequestHeaders; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPTests.Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTests.h 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/31/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #define kTIPTestsImageFetchDownloadProviderClassKey @"TIP_TESTS_IMAGE_FETCH_DOWNLOAD_PROVIDER_CLASS" 13 | 14 | FOUNDATION_EXTERN Class TIPTestsImageFetchDownloadProviderOverrideClass(void); 15 | FOUNDATION_EXTERN NSBundle *TIPTestsResourceBundle(void); 16 | 17 | // Need something to be compiled into the unit tests bundle or else there's no binary to run! 18 | // Put TIP_TESTS_IMPLEMENT_DUMMY in a code file for the consuming unit tests 19 | 20 | #define TIP_TESTS_IMPLEMENT_DUMMY \ 21 | void TwitterImagePipelineTestsDummyFunction() \ 22 | { \ 23 | NSLog(@"TwitterImagePipelineTestsDummyFunction"); \ 24 | } 25 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TIPTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TIPTests.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/31/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPTestImageFetchDownloadInternalWithStubbing.h" 10 | 11 | #import "TIPTests.h" 12 | 13 | Class TIPTestsImageFetchDownloadProviderOverrideClass() 14 | { 15 | static Class sImageFetchDownloadProviderClass = Nil; 16 | static dispatch_once_t onceToken; 17 | dispatch_once(&onceToken, ^{ 18 | NSString *imageFetchClassName = nil; 19 | for (NSBundle *innerBundle in [NSBundle allBundles]) { 20 | if ([innerBundle.bundlePath hasSuffix:@".xctest"]) { 21 | imageFetchClassName = [innerBundle objectForInfoDictionaryKey:kTIPTestsImageFetchDownloadProviderClassKey]; 22 | if (imageFetchClassName) { 23 | break; 24 | } 25 | } 26 | } 27 | if (imageFetchClassName) { 28 | sImageFetchDownloadProviderClass = NSClassFromString(imageFetchClassName); 29 | } 30 | if (!sImageFetchDownloadProviderClass) { 31 | sImageFetchDownloadProviderClass = [TIPTestImageFetchDownloadProviderInternalWithStubbing class]; 32 | } 33 | }); 34 | return sImageFetchDownloadProviderClass; 35 | } 36 | 37 | NSBundle *TIPTestsResourceBundle(void) 38 | { 39 | static NSBundle *sBundle = nil; 40 | static dispatch_once_t onceToken; 41 | dispatch_once(&onceToken, ^{ 42 | NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.twitter.TIPTestsResources"]; 43 | if (!bundle) { 44 | for (NSBundle *innerBundle in [NSBundle allBundles]) { 45 | if ([innerBundle.bundlePath hasSuffix:@".xctest"]) { 46 | bundle = [NSBundle bundleWithPath:[innerBundle.bundlePath stringByAppendingPathComponent:@"TIPTestsResources.bundle"]]; 47 | if (bundle) { 48 | break; 49 | } 50 | } 51 | } 52 | } 53 | sBundle = bundle; 54 | }); 55 | 56 | if (!sBundle) { 57 | @throw [NSException exceptionWithName:NSGenericException reason:@"Missing TIPTests.framework bundle!" userInfo:nil]; 58 | } 59 | return sBundle; 60 | } 61 | -------------------------------------------------------------------------------- /TwitterImagePipelineTests/TwitterImagePipelineTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TwitterImagePipelineTests.m 3 | // TwitterImagePipeline 4 | // 5 | // Created on 8/31/16. 6 | // Copyright © 2016 Twitter. All rights reserved. 7 | // 8 | 9 | #import "TIPTests.h" 10 | 11 | TIP_TESTS_IMPLEMENT_DUMMY; 12 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | function ci_lib() { 6 | NAME=$1 7 | xcodebuild -project TwitterImagePipeline.xcodeproj \ 8 | -scheme "TwitterImagePipeline.framework" \ 9 | -destination "platform=iOS Simulator,name=${NAME}" \ 10 | -sdk iphonesimulator \ 11 | build test 12 | } 13 | 14 | function ci_demo() { 15 | NAME=$1 16 | xcodebuild -project TwitterImagePipeline.xcodeproj \ 17 | -scheme "TIP Sample App" \ 18 | -destination "platform=iOS Simulator,name=${NAME}" \ 19 | -sdk iphonesimulator \ 20 | build 21 | } 22 | 23 | 24 | ci_lib "iPhone 6" && ci_demo "iPhone 6" 25 | ci_lib "iPhone 7" && ci_demo "iPhone 7" 26 | -------------------------------------------------------------------------------- /placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Naituw/ios-twitter-image-pipeline/81f8ad1171430dbcdfd545b9b3fbfc77fba31d40/placeholder.jpg --------------------------------------------------------------------------------