mirror of
				https://github.com/SoPat712/YTLitePlus.git
				synced 2025-10-30 20:34:04 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			528 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			528 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| //
 | |
| //  FLEXUtility.m
 | |
| //  Flipboard
 | |
| //
 | |
| //  Created by Ryan Olson on 4/18/14.
 | |
| //  Copyright (c) 2020 FLEX Team. All rights reserved.
 | |
| //
 | |
| 
 | |
| #import "FLEXColor.h"
 | |
| #import "FLEXUtility.h"
 | |
| #import "FLEXResources.h"
 | |
| #import "FLEXWindow.h"
 | |
| #import <ImageIO/ImageIO.h>
 | |
| #import <objc/runtime.h>
 | |
| #import <zlib.h>
 | |
| 
 | |
| BOOL FLEXConstructorsShouldRun() {
 | |
|     #if FLEX_DISABLE_CTORS
 | |
|         return NO;
 | |
|     #else
 | |
|         static BOOL _FLEXConstructorsShouldRun_storage = YES;
 | |
|         
 | |
|         static dispatch_once_t onceToken;
 | |
|         dispatch_once(&onceToken, ^{
 | |
|             NSString *key = @"FLEX_SKIP_INIT";
 | |
|             if (getenv(key.UTF8String) || [NSUserDefaults.standardUserDefaults boolForKey:key]) {
 | |
|                 _FLEXConstructorsShouldRun_storage = NO;
 | |
|             }
 | |
|         });
 | |
|         
 | |
|         return _FLEXConstructorsShouldRun_storage;
 | |
|     #endif
 | |
| }
 | |
| 
 | |
| @implementation FLEXUtility
 | |
| 
 | |
| + (UIWindow *)appKeyWindow {
 | |
|     // First, check UIApplication.keyWindow
 | |
|     FLEXWindow *window = (id)UIApplication.sharedApplication.keyWindow;
 | |
|     if (window) {
 | |
|         if ([window isKindOfClass:[FLEXWindow class]]) {
 | |
|             return window.previousKeyWindow;
 | |
|         }
 | |
|         
 | |
|         return window;
 | |
|     }
 | |
|     
 | |
|     // As of iOS 13, UIApplication.keyWindow does not return nil,
 | |
|     // so this is more of a safeguard against it returning nil in the future.
 | |
|     //
 | |
|     // Also, these are obviously not all FLEXWindows; FLEXWindow is used
 | |
|     // so we can call window.previousKeyWindow without an ugly cast
 | |
|     for (FLEXWindow *window in UIApplication.sharedApplication.windows) {
 | |
|         if (window.isKeyWindow) {
 | |
|             if ([window isKindOfClass:[FLEXWindow class]]) {
 | |
|                 return window.previousKeyWindow;
 | |
|             }
 | |
|             
 | |
|             return window;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return nil;
 | |
| }
 | |
| 
 | |
| + (UIWindowScene *)activeScene {
 | |
|     for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
 | |
|         // Look for an active UIWindowScene
 | |
|         if (scene.activationState == UISceneActivationStateForegroundActive &&
 | |
|             [scene isKindOfClass:[UIWindowScene class]]) {
 | |
|             return (UIWindowScene *)scene;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return nil;
 | |
| }
 | |
| 
 | |
| + (UIViewController *)topViewControllerInWindow:(UIWindow *)window {
 | |
|     UIViewController *topViewController = window.rootViewController;
 | |
|     while (topViewController.presentedViewController) {
 | |
|         topViewController = topViewController.presentedViewController;
 | |
|     }
 | |
|     return topViewController;
 | |
| }
 | |
| 
 | |
| + (UIColor *)consistentRandomColorForObject:(id)object {
 | |
|     CGFloat hue = (((NSUInteger)object >> 4) % 256) / 255.0;
 | |
|     return [UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0];
 | |
| }
 | |
| 
 | |
| + (NSString *)descriptionForView:(UIView *)view includingFrame:(BOOL)includeFrame {
 | |
|     NSString *description = [[view class] description];
 | |
|     
 | |
|     NSString *viewControllerDescription = [[[self viewControllerForView:view] class] description];
 | |
|     if (viewControllerDescription.length > 0) {
 | |
|         description = [description stringByAppendingFormat:@" (%@)", viewControllerDescription];
 | |
|     }
 | |
|     
 | |
|     if (includeFrame) {
 | |
|         description = [description stringByAppendingFormat:@" %@", [self stringForCGRect:view.frame]];
 | |
|     }
 | |
|     
 | |
|     if (view.accessibilityLabel.length > 0) {
 | |
|         description = [description stringByAppendingFormat:@" · %@", view.accessibilityLabel];
 | |
|     }
 | |
|     
 | |
|     return description;
 | |
| }
 | |
| 
 | |
| + (NSString *)stringForCGRect:(CGRect)rect {
 | |
|     return [NSString stringWithFormat:@"{(%g, %g), (%g, %g)}",
 | |
|         rect.origin.x, rect.origin.y, rect.size.width, rect.size.height
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (UIViewController *)viewControllerForView:(UIView *)view {
 | |
|     NSString *viewDelegate = @"_viewDelegate";
 | |
|     if ([view respondsToSelector:NSSelectorFromString(viewDelegate)]) {
 | |
|         return [view valueForKey:viewDelegate];
 | |
|     }
 | |
| 
 | |
|     return nil;
 | |
| }
 | |
| 
 | |
| + (UIViewController *)viewControllerForAncestralView:(UIView *)view {
 | |
|     NSString *_viewControllerForAncestor = @"_viewControllerForAncestor";
 | |
|     if ([view respondsToSelector:NSSelectorFromString(_viewControllerForAncestor)]) {
 | |
|         return [view valueForKey:_viewControllerForAncestor];
 | |
|     }
 | |
| 
 | |
|     return nil;
 | |
| }
 | |
| 
 | |
| + (UIImage *)previewImageForView:(UIView *)view {
 | |
|     if (CGRectIsEmpty(view.bounds)) {
 | |
|         return [UIImage new];
 | |
|     }
 | |
|     
 | |
|     CGSize viewSize = view.bounds.size;
 | |
|     UIGraphicsBeginImageContextWithOptions(viewSize, NO, 0.0);
 | |
|     [view drawViewHierarchyInRect:CGRectMake(0, 0, viewSize.width, viewSize.height) afterScreenUpdates:YES];
 | |
|     UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
 | |
|     UIGraphicsEndImageContext();
 | |
|     return previewImage;
 | |
| }
 | |
| 
 | |
| + (UIImage *)previewImageForLayer:(CALayer *)layer {
 | |
|     if (CGRectIsEmpty(layer.bounds)) {
 | |
|         return nil;
 | |
|     }
 | |
|     
 | |
|     UIGraphicsBeginImageContextWithOptions(layer.bounds.size, NO, 0.0);
 | |
|     CGContextRef imageContext = UIGraphicsGetCurrentContext();
 | |
|     [layer renderInContext:imageContext];
 | |
|     UIImage *previewImage = UIGraphicsGetImageFromCurrentImageContext();
 | |
|     UIGraphicsEndImageContext();
 | |
|     return previewImage;
 | |
| }
 | |
| 
 | |
| + (NSString *)detailDescriptionForView:(UIView *)view {
 | |
|     return [NSString stringWithFormat:@"frame %@", [self stringForCGRect:view.frame]];
 | |
| }
 | |
| 
 | |
| + (UIImage *)circularImageWithColor:(UIColor *)color radius:(CGFloat)radius {
 | |
|     CGFloat diameter = radius * 2.0;
 | |
|     UIGraphicsBeginImageContextWithOptions(CGSizeMake(diameter, diameter), NO, 0.0);
 | |
|     CGContextRef imageContext = UIGraphicsGetCurrentContext();
 | |
|     CGContextSetFillColorWithColor(imageContext, color.CGColor);
 | |
|     CGContextFillEllipseInRect(imageContext, CGRectMake(0, 0, diameter, diameter));
 | |
|     UIImage *circularImage = UIGraphicsGetImageFromCurrentImageContext();
 | |
|     UIGraphicsEndImageContext();
 | |
|     return circularImage;
 | |
| }
 | |
| 
 | |
| + (UIColor *)hierarchyIndentPatternColor {
 | |
|     static UIColor *patternColor = nil;
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         UIImage *indentationPatternImage = FLEXResources.hierarchyIndentPattern;
 | |
|         patternColor = [UIColor colorWithPatternImage:indentationPatternImage];
 | |
|         if (@available(iOS 13.0, *)) {
 | |
|             // Create a dark mode version
 | |
|             UIGraphicsBeginImageContextWithOptions(
 | |
|                 indentationPatternImage.size, NO, indentationPatternImage.scale
 | |
|             );
 | |
|             [FLEXColor.iconColor set];
 | |
|             [indentationPatternImage drawInRect:CGRectMake(
 | |
|                 0, 0, indentationPatternImage.size.width, indentationPatternImage.size.height
 | |
|             )];
 | |
|             UIImage *darkModePatternImage = UIGraphicsGetImageFromCurrentImageContext();
 | |
|             UIGraphicsEndImageContext();
 | |
| 
 | |
|             // Create dynamic color provider
 | |
|             patternColor = [UIColor colorWithDynamicProvider:^UIColor *(UITraitCollection *traitCollection) {
 | |
|                 return (traitCollection.userInterfaceStyle == UIUserInterfaceStyleLight
 | |
|                         ? [UIColor colorWithPatternImage:indentationPatternImage]
 | |
|                         : [UIColor colorWithPatternImage:darkModePatternImage]);
 | |
|             }];
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     return patternColor;
 | |
| }
 | |
| 
 | |
| + (NSString *)applicationImageName {
 | |
|     return NSBundle.mainBundle.executablePath;
 | |
| }
 | |
| 
 | |
| + (NSString *)applicationName {
 | |
|     return FLEXUtility.applicationImageName.lastPathComponent;
 | |
| }
 | |
| 
 | |
| + (NSString *)pointerToString:(void *)ptr {
 | |
|     return [NSString stringWithFormat:@"%p", ptr];
 | |
| }
 | |
| 
 | |
| + (NSString *)addressOfObject:(id)object {
 | |
|     return [NSString stringWithFormat:@"%p", object];
 | |
| }
 | |
| 
 | |
| + (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString {
 | |
|     static NSDictionary<NSString *, NSString *> *escapingDictionary = nil;
 | |
|     static NSRegularExpression *regex = nil;
 | |
|     static dispatch_once_t onceToken;
 | |
|     dispatch_once(&onceToken, ^{
 | |
|         escapingDictionary = @{ @" " : @" ",
 | |
|                                 @">" : @">",
 | |
|                                 @"<" : @"<",
 | |
|                                 @"&" : @"&",
 | |
|                                 @"'" : @"'",
 | |
|                                 @"\"" : @""",
 | |
|                                 @"«" : @"«",
 | |
|                                 @"»" : @"»"
 | |
|                                 };
 | |
|         regex = [NSRegularExpression regularExpressionWithPattern:@"(&|>|<|'|\"|«|»)" options:0 error:NULL];
 | |
|     });
 | |
|     
 | |
|     NSMutableString *mutableString = originalString.mutableCopy;
 | |
|     
 | |
|     NSArray<NSTextCheckingResult *> *matches = [regex
 | |
|         matchesInString:mutableString options:0 range:NSMakeRange(0, mutableString.length)
 | |
|     ];
 | |
|     for (NSTextCheckingResult *result in matches.reverseObjectEnumerator) {
 | |
|         NSString *foundString = [mutableString substringWithRange:result.range];
 | |
|         NSString *replacementString = escapingDictionary[foundString];
 | |
|         if (replacementString) {
 | |
|             [mutableString replaceCharactersInRange:result.range withString:replacementString];
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return [mutableString copy];
 | |
| }
 | |
| 
 | |
| + (UIInterfaceOrientationMask)infoPlistSupportedInterfaceOrientationsMask {
 | |
|     NSArray<NSString *> *supportedOrientations = NSBundle.mainBundle.infoDictionary[@"UISupportedInterfaceOrientations"];
 | |
|     UIInterfaceOrientationMask supportedOrientationsMask = 0;
 | |
|     if ([supportedOrientations containsObject:@"UIInterfaceOrientationPortrait"]) {
 | |
|         supportedOrientationsMask |= UIInterfaceOrientationMaskPortrait;
 | |
|     }
 | |
|     if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskLandscapeRight"]) {
 | |
|         supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeRight;
 | |
|     }
 | |
|     if ([supportedOrientations containsObject:@"UIInterfaceOrientationMaskPortraitUpsideDown"]) {
 | |
|         supportedOrientationsMask |= UIInterfaceOrientationMaskPortraitUpsideDown;
 | |
|     }
 | |
|     if ([supportedOrientations containsObject:@"UIInterfaceOrientationLandscapeLeft"]) {
 | |
|         supportedOrientationsMask |= UIInterfaceOrientationMaskLandscapeLeft;
 | |
|     }
 | |
|     return supportedOrientationsMask;
 | |
| }
 | |
| 
 | |
| + (UIImage *)thumbnailedImageWithMaxPixelDimension:(NSInteger)dimension fromImageData:(NSData *)data {
 | |
|     UIImage *thumbnail = nil;
 | |
|     CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, 0);
 | |
|     if (imageSource) {
 | |
|         NSDictionary<NSString *, id> *options = @{
 | |
|             (__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
 | |
|             (__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
 | |
|             (__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(dimension)
 | |
|         };
 | |
| 
 | |
|         CGImageRef scaledImageRef = CGImageSourceCreateThumbnailAtIndex(
 | |
|             imageSource, 0, (__bridge CFDictionaryRef)options
 | |
|         );
 | |
|         if (scaledImageRef) {
 | |
|             thumbnail = [UIImage imageWithCGImage:scaledImageRef];
 | |
|             CFRelease(scaledImageRef);
 | |
|         }
 | |
|         CFRelease(imageSource);
 | |
|     }
 | |
|     return thumbnail;
 | |
| }
 | |
| 
 | |
| + (NSString *)stringFromRequestDuration:(NSTimeInterval)duration {
 | |
|     NSString *string = @"0s";
 | |
|     if (duration > 0.0) {
 | |
|         if (duration < 1.0) {
 | |
|             string = [NSString stringWithFormat:@"%dms", (int)(duration * 1000)];
 | |
|         } else if (duration < 10.0) {
 | |
|             string = [NSString stringWithFormat:@"%.2fs", duration];
 | |
|         } else {
 | |
|             string = [NSString stringWithFormat:@"%.1fs", duration];
 | |
|         }
 | |
|     }
 | |
|     return string;
 | |
| }
 | |
| 
 | |
| + (NSString *)statusCodeStringFromURLResponse:(NSURLResponse *)response {
 | |
|     NSString *httpResponseString = nil;
 | |
|     if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
 | |
|         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
 | |
|         NSString *statusCodeDescription = nil;
 | |
|         if (httpResponse.statusCode == 200) {
 | |
|             // Prefer OK to the default "no error"
 | |
|             statusCodeDescription = @"OK";
 | |
|         } else {
 | |
|             statusCodeDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
 | |
|         }
 | |
|         httpResponseString = [NSString stringWithFormat:@"%ld %@", (long)httpResponse.statusCode, statusCodeDescription];
 | |
|     }
 | |
|     return httpResponseString;
 | |
| }
 | |
| 
 | |
| + (BOOL)isErrorStatusCodeFromURLResponse:(NSURLResponse *)response {
 | |
|     if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
 | |
|         NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
 | |
|         return httpResponse.statusCode >= 400;
 | |
|     }
 | |
|     
 | |
|     return NO;
 | |
| }
 | |
| 
 | |
| + (NSArray<NSURLQueryItem *> *)itemsFromQueryString:(NSString *)query {
 | |
|     NSMutableArray<NSURLQueryItem *> *items = [NSMutableArray new];
 | |
| 
 | |
|     // [a=1, b=2, c=3]
 | |
|     NSArray<NSString *> *queryComponents = [query componentsSeparatedByString:@"&"];
 | |
|     for (NSString *keyValueString in queryComponents) {
 | |
|         // [a, 1]
 | |
|         NSArray<NSString *> *components = [keyValueString componentsSeparatedByString:@"="];
 | |
|         if (components.count == 2) {
 | |
|             NSString *key = components.firstObject.stringByRemovingPercentEncoding;
 | |
|             NSString *value = components.lastObject.stringByRemovingPercentEncoding;
 | |
| 
 | |
|             [items addObject:[NSURLQueryItem queryItemWithName:key value:value]];
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return items.copy;
 | |
| }
 | |
| 
 | |
| + (NSString *)prettyJSONStringFromData:(NSData *)data {
 | |
|     NSString *prettyString = nil;
 | |
|     
 | |
|     id jsonObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
 | |
|     if ([NSJSONSerialization isValidJSONObject:jsonObject]) {
 | |
|         // Thanks RaziPour1993
 | |
|         prettyString = [[NSString alloc]
 | |
|             initWithData:[NSJSONSerialization
 | |
|                 dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:NULL
 | |
|             ]
 | |
|             encoding:NSUTF8StringEncoding
 | |
|         ];
 | |
|         // NSJSONSerialization escapes forward slashes.
 | |
|         // We want pretty json, so run through and unescape the slashes.
 | |
|         prettyString = [prettyString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"];
 | |
|     } else {
 | |
|         prettyString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
 | |
|     }
 | |
|     
 | |
|     return prettyString;
 | |
| }
 | |
| 
 | |
| + (BOOL)isValidJSONData:(NSData *)data {
 | |
|     return [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL] ? YES : NO;
 | |
| }
 | |
| 
 | |
| // Thanks to the following links for help with this method
 | |
| // https://www.cocoanetics.com/2012/02/decompressing-files-into-memory/
 | |
| // https://github.com/nicklockwood/GZIP
 | |
| + (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData {
 | |
|     NSData *inflatedData = nil;
 | |
|     NSUInteger compressedDataLength = compressedData.length;
 | |
|     if (compressedDataLength > 0) {
 | |
|         z_stream stream;
 | |
|         stream.zalloc = Z_NULL;
 | |
|         stream.zfree = Z_NULL;
 | |
|         stream.avail_in = (uInt)compressedDataLength;
 | |
|         stream.next_in = (void *)compressedData.bytes;
 | |
|         stream.total_out = 0;
 | |
|         stream.avail_out = 0;
 | |
| 
 | |
|         NSMutableData *mutableData = [NSMutableData dataWithLength:compressedDataLength * 1.5];
 | |
|         if (inflateInit2(&stream, 15 + 32) == Z_OK) {
 | |
|             int status = Z_OK;
 | |
|             while (status == Z_OK) {
 | |
|                 if (stream.total_out >= mutableData.length) {
 | |
|                     mutableData.length += compressedDataLength / 2;
 | |
|                 }
 | |
|                 stream.next_out = (uint8_t *)[mutableData mutableBytes] + stream.total_out;
 | |
|                 stream.avail_out = (uInt)(mutableData.length - stream.total_out);
 | |
|                 status = inflate(&stream, Z_SYNC_FLUSH);
 | |
|             }
 | |
|             if (inflateEnd(&stream) == Z_OK) {
 | |
|                 if (status == Z_STREAM_END) {
 | |
|                     mutableData.length = stream.total_out;
 | |
|                     inflatedData = [mutableData copy];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     return inflatedData;
 | |
| }
 | |
| 
 | |
| + (NSArray<UIWindow *> *)allWindows {
 | |
|     BOOL includeInternalWindows = YES;
 | |
|     BOOL onlyVisibleWindows = NO;
 | |
| 
 | |
|     // Obfuscating selector allWindowsIncludingInternalWindows:onlyVisibleWindows:
 | |
|     NSArray<NSString *> *allWindowsComponents = @[
 | |
|         @"al", @"lWindo", @"wsIncl", @"udingInt", @"ernalWin", @"dows:o", @"nlyVisi", @"bleWin", @"dows:"
 | |
|     ];
 | |
|     SEL allWindowsSelector = NSSelectorFromString([allWindowsComponents componentsJoinedByString:@""]);
 | |
| 
 | |
|     NSMethodSignature *methodSignature = [[UIWindow class] methodSignatureForSelector:allWindowsSelector];
 | |
|     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
 | |
| 
 | |
|     invocation.target = [UIWindow class];
 | |
|     invocation.selector = allWindowsSelector;
 | |
|     [invocation setArgument:&includeInternalWindows atIndex:2];
 | |
|     [invocation setArgument:&onlyVisibleWindows atIndex:3];
 | |
|     [invocation invoke];
 | |
| 
 | |
|     __unsafe_unretained NSArray<UIWindow *> *windows = nil;
 | |
|     [invocation getReturnValue:&windows];
 | |
|     return windows;
 | |
| }
 | |
| 
 | |
| + (UIAlertController *)alert:(NSString *)title message:(NSString *)message {
 | |
|     return [UIAlertController
 | |
|         alertControllerWithTitle:title
 | |
|         message:message
 | |
|         preferredStyle:UIAlertControllerStyleAlert
 | |
|     ];
 | |
| }
 | |
| 
 | |
| + (SEL)swizzledSelectorForSelector:(SEL)selector {
 | |
|     return NSSelectorFromString([NSString stringWithFormat:
 | |
|         @"_flex_swizzle_%x_%@", arc4random(), NSStringFromSelector(selector)
 | |
|     ]);
 | |
| }
 | |
| 
 | |
| + (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls {
 | |
|     if ([cls instancesRespondToSelector:selector]) {
 | |
|         unsigned int numMethods = 0;
 | |
|         Method *methods = class_copyMethodList(cls, &numMethods);
 | |
|         
 | |
|         BOOL implementsSelector = NO;
 | |
|         for (int index = 0; index < numMethods; index++) {
 | |
|             SEL methodSelector = method_getName(methods[index]);
 | |
|             if (selector == methodSelector) {
 | |
|                 implementsSelector = YES;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         free(methods);
 | |
|         
 | |
|         if (!implementsSelector) {
 | |
|             return YES;
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     return NO;
 | |
| }
 | |
| 
 | |
| + (void)replaceImplementationOfKnownSelector:(SEL)originalSelector
 | |
|                                      onClass:(Class)class
 | |
|                                    withBlock:(id)block
 | |
|                             swizzledSelector:(SEL)swizzledSelector {
 | |
|     // This method is only intended for swizzling methods that are know to exist on the class.
 | |
|     // Bail if that isn't the case.
 | |
|     Method originalMethod = class_getInstanceMethod(class, originalSelector);
 | |
|     if (!originalMethod) {
 | |
|         return;
 | |
|     }
 | |
|     
 | |
|     IMP implementation = imp_implementationWithBlock(block);
 | |
|     class_addMethod(class, swizzledSelector, implementation, method_getTypeEncoding(originalMethod));
 | |
|     Method newMethod = class_getInstanceMethod(class, swizzledSelector);
 | |
|     method_exchangeImplementations(originalMethod, newMethod);
 | |
| }
 | |
| 
 | |
| + (void)replaceImplementationOfSelector:(SEL)selector
 | |
|                            withSelector:(SEL)swizzledSelector
 | |
|                                forClass:(Class)cls
 | |
|                   withMethodDescription:(struct objc_method_description)methodDescription
 | |
|                     implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock {
 | |
|     if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
 | |
|         return;
 | |
|     }
 | |
|     
 | |
|     IMP implementation = imp_implementationWithBlock((id)(
 | |
|         [cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock)
 | |
|     );
 | |
|     
 | |
|     Method oldMethod = class_getInstanceMethod(cls, selector);
 | |
|     const char *types = methodDescription.types;
 | |
|     if (oldMethod) {
 | |
|         if (!types) {
 | |
|             types = method_getTypeEncoding(oldMethod);
 | |
|         }
 | |
| 
 | |
|         class_addMethod(cls, swizzledSelector, implementation, types);
 | |
|         Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
 | |
|         method_exchangeImplementations(oldMethod, newMethod);
 | |
|     } else {
 | |
|         if (!types) {
 | |
|             // Some protocol method descriptions don't have .types populated
 | |
|             // Set the return type to void and ignore arguments
 | |
|             types = "v@:";
 | |
|         }
 | |
|         class_addMethod(cls, selector, implementation, types);
 | |
|     }
 | |
| }
 | |
| 
 | |
| @end
 | 
