Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 13:58:55 +01:00
parent 4af19165ec
commit 68073add76
12458 changed files with 12350765 additions and 2 deletions

View file

@ -0,0 +1,14 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol IMWMImageCache
- (void)imageForKey:(NSString *)imageKey
completion:(void (^)(UIImage * _Nullable image, NSError * _Nullable error))completion;
- (void)setImage:(UIImage *)image forKey:(NSString *)imageKey;
- (void)cleanup;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,12 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol IMWMImageCoder
- (UIImage * _Nullable)imageWithData:(NSData *)data;
- (NSData * _Nullable)dataFromImage:(UIImage *)image;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,21 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol IMWMImageTask <NSObject>
- (void)cancel;
@end
typedef void (^MWMWebImageCompletion)(UIImage * _Nullable image, NSError * _Nullable error);
@protocol IMWMWebImage
- (id<IMWMImageTask>)imageWithUrl:(NSURL *)url
completion:(MWMWebImageCompletion)completion;
- (void)cleanup;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,13 @@
#import "IMWMImageCache.h"
#import "IMWMImageCoder.h"
NS_ASSUME_NONNULL_BEGIN
@interface MWMImageCache : NSObject <IMWMImageCache>
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithImageCoder:(id<IMWMImageCoder>)imageCoder;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,92 @@
#import "MWMImageCache.h"
#import "NSString+MD5.h"
static NSTimeInterval kCleanupTimeInterval = 30 * 24 * 60 * 60;
@interface MWMImageCache ()
@property (nonatomic, strong) NSCache<NSString *, UIImage *> *cache;
@property (nonatomic, copy) NSString *cacheDirPath;
@property (nonatomic, strong) dispatch_queue_t diskQueue;
@property (nonatomic, strong) NSFileManager *fileManager;
@property (nonatomic, strong) id<IMWMImageCoder> imageCoder;
@end
@implementation MWMImageCache
- (instancetype)initWithImageCoder:(id<IMWMImageCoder>)imageCoder {
self = [super init];
if (self) {
_cache = [[NSCache alloc] init];
_cacheDirPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"images"];
_diskQueue = dispatch_queue_create("mapsme.imageCache.disk", DISPATCH_QUEUE_SERIAL);
_fileManager = [NSFileManager defaultManager];
_imageCoder = imageCoder;
[_fileManager createDirectoryAtPath:_cacheDirPath
withIntermediateDirectories:YES
attributes:nil
error:nil];
}
return self;
}
- (void)imageForKey:(NSString *)imageKey completion:(void (^)(UIImage *image, NSError *error))completion {
UIImage *image = [self.cache objectForKey:imageKey];
if (image) {
completion(image, nil);
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *path = [self.cacheDirPath stringByAppendingPathComponent:imageKey.md5String];
__block NSData *imageData = nil;
__block NSError *error = nil;
dispatch_sync(self.diskQueue, ^{
imageData = [NSData dataWithContentsOfFile:path options:0 error:&error];
});
UIImage *image = nil;
if (imageData) {
image = [self.imageCoder imageWithData:imageData];
if (image) {
[self.cache setObject:image forKey:imageKey];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
completion(image, error);
});
});
}
}
- (void)setImage:(UIImage *)image forKey:(NSString *)imageKey {
[self.cache setObject:image forKey:imageKey];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [self.imageCoder dataFromImage:image];
if (imageData) {
NSString *path = [self.cacheDirPath stringByAppendingPathComponent:imageKey.md5String];
dispatch_sync(self.diskQueue, ^{
[imageData writeToFile:path atomically:YES];
});
}
});
}
- (void)cleanup {
NSDirectoryEnumerator<NSString *> *enumerator = [self.fileManager enumeratorAtPath:self.cacheDirPath];
for (NSString *fileName in enumerator) {
NSString *path = [self.cacheDirPath stringByAppendingPathComponent:fileName];
NSError *error = nil;
NSDictionary *attributes = [self.fileManager attributesOfItemAtPath:path error:&error];
if (!error) {
NSDate *date = attributes[NSFileCreationDate];
if (fabs(date.timeIntervalSinceNow) > kCleanupTimeInterval) {
dispatch_sync(self.diskQueue, ^{
[self.fileManager removeItemAtPath:path error:nil];
});
}
}
}
}
@end

View file

@ -0,0 +1,9 @@
#import "IMWMImageCoder.h"
NS_ASSUME_NONNULL_BEGIN
@interface MWMImageCoder : NSObject <IMWMImageCoder>
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,51 @@
#import "MWMImageCoder.h"
@implementation MWMImageCoder
- (UIImage *)imageWithData:(NSData *)data {
UIImage *image = [UIImage imageWithData:data];
if (!image) {
return nil;
}
CGImageRef cgImage = image.CGImage;
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
int32_t flags;
if ([self imageHasAlpha:image]) {
flags = kCGImageAlphaPremultipliedLast;
} else {
flags = kCGImageAlphaNoneSkipLast;
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, flags);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
CGImageRef resultCgImage = CGBitmapContextCreateImage(context);
UIImage *resultImage = [UIImage imageWithCGImage:resultCgImage];
CGImageRelease(resultCgImage);
CGContextRelease(context);
return resultImage;
}
- (NSData *)dataFromImage:(UIImage *)image {
if ([self imageHasAlpha:image]) {
return UIImagePNGRepresentation(image);
} else {
return UIImageJPEGRepresentation(image, 0.9);
}
}
- (BOOL)imageHasAlpha:(UIImage *)image {
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(image.CGImage);
return (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst);
}
@end

View file

@ -0,0 +1,22 @@
#import "IMWMWebImage.h"
#import "IMWMImageCache.h"
#import "IMWMImageCoder.h"
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface MWMWebImage : NSObject <IMWMWebImage>
+ (MWMWebImage *)defaultWebImage;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithImageCahce:(id<IMWMImageCache>)imageCache
imageCoder:(id<IMWMImageCoder>)imageCoder;
- (id<IMWMImageTask>)imageWithUrl:(NSURL *)url
completion:(MWMWebImageCompletion)completion;
- (void)cleanup;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,108 @@
#import "MWMWebImage.h"
#import "MWMImageCache.h"
#import "MWMImageCoder.h"
@interface MWMWebImageTask : NSObject <IMWMImageTask>
@property (nonatomic) BOOL cancelled;
@property (nonatomic, strong) NSURLSessionTask *dataTask;
@end
@implementation MWMWebImageTask
- (void)cancel {
self.cancelled = YES;
[self.dataTask cancel];
}
@end
@interface MWMWebImage () <NSURLSessionDelegate>
@property (nonatomic, strong) NSURLSession *urlSession;
@property (nonatomic, strong) id<IMWMImageCache> imageCache;
@property (nonatomic, strong) id<IMWMImageCoder> imageCoder;
@end
@implementation MWMWebImage
+ (MWMWebImage *)defaultWebImage {
static MWMWebImage *instanse;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
MWMImageCoder *coder = [MWMImageCoder new];
instanse = [[self alloc] initWithImageCahce:[[MWMImageCache alloc] initWithImageCoder:coder]
imageCoder:coder];
});
return instanse;
}
- (instancetype)initWithImageCahce:(id<IMWMImageCache>)imageCache
imageCoder:(id<IMWMImageCoder>)imageCoder {
self = [super init];
if (self) {
_urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]
delegate:self
delegateQueue:nil];
_imageCache = imageCache;
_imageCoder = imageCoder;
}
return self;
}
- (id<IMWMImageTask>)imageWithUrl:(NSURL *)url
completion:(MWMWebImageCompletion)completion {
MWMWebImageTask *imageTask = [MWMWebImageTask new];
NSString *cacheKey = url.absoluteString;
[self.imageCache imageForKey:cacheKey completion:^(UIImage *image, NSError *error) {
if (imageTask.cancelled) {
return;
}
if (image) {
completion(image, nil);
} else {
NSURLSessionTask *dataTask = [self.urlSession dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
UIImage *image = nil;
if (data) {
image = [self.imageCoder imageWithData:data];
if (image) {
[self.imageCache setImage:image forKey:cacheKey];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
if (!imageTask.cancelled) {
completion(image, error);
}
});
}];
imageTask.dataTask = dataTask;
[dataTask resume];
}
}];
return imageTask;
}
- (void)cleanup {
[self.imageCache cleanup];
}
#pragma mark - NSURLSessionDelegate
#if DEBUG
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler
{
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
#endif
@end

View file

@ -0,0 +1,11 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSString (MD5)
- (NSString *)md5String;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,23 @@
#import "NSString+MD5.h"
#import <CommonCrypto/CommonDigest.h>
@implementation NSString (MD5)
- (NSString *)md5String {
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
if (data.length == 0) {
return @"";
}
unsigned char buf[CC_MD5_DIGEST_LENGTH];
CC_MD5(data.bytes, (CC_LONG)data.length, buf);
NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for (NSInteger i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) {
[result appendFormat:@"%02x", buf[i]];
}
return [result copy];
}
@end

View file

@ -0,0 +1,15 @@
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface UIImageView (WebImage)
- (void)wi_setImageWithUrl:(NSURL *)url;
- (void)wi_setImageWithUrl:(NSURL *)url
transitionDuration:(NSTimeInterval)duration
completion:(void (^ _Nullable)(UIImage * _Nullable image, NSError * _Nullable error))completion;
- (void)wi_cancelImageRequest;
@end
NS_ASSUME_NONNULL_END

View file

@ -0,0 +1,46 @@
#import "UIImageView+WebImage.h"
#import "MWMWebImage.h"
#import <objc/runtime.h>
static char kAssociatedObjectKey;
@implementation UIImageView (WebImage)
- (void)wi_setImageWithUrl:(NSURL *)url {
[self wi_setImageWithUrl:url transitionDuration:0 completion:nil];
}
- (void)wi_setImageWithUrl:(NSURL *)url
transitionDuration:(NSTimeInterval)duration
completion:(void (^)(UIImage *, NSError *))completion {
id<IMWMImageTask> task = [[MWMWebImage defaultWebImage] imageWithUrl:url
completion:^(UIImage *image, NSError *error) {
objc_setAssociatedObject(self, &kAssociatedObjectKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (!image) {
if (completion) { completion(nil, error); }
return;
}
if (duration > 0) {
[UIView transitionWithView:self
duration:duration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{ self.image = image; }
completion:nil];
} else {
self.image = image;
}
if (completion) { completion(image, nil); }
}];
objc_setAssociatedObject(self, &kAssociatedObjectKey, task, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)wi_cancelImageRequest {
id<IMWMImageTask> task = objc_getAssociatedObject(self, &kAssociatedObjectKey);
[task cancel];
objc_setAssociatedObject(self, &kAssociatedObjectKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end