处理键盘崩溃

This commit is contained in:
2026-02-10 13:22:19 +08:00
parent 4c57f16058
commit 3c71797b7b
5 changed files with 459 additions and 99 deletions

View File

@@ -4,6 +4,7 @@
#import "KBSkinManager.h"
#import "KBConfig.h"
#import <ImageIO/ImageIO.h>
NSString * const KBSkinDidChangeNotification = @"KBSkinDidChangeNotification";
NSString * const KBDarwinSkinChanged = @"com.loveKey.nyx.skin.changed";
@@ -59,10 +60,45 @@ static NSString * const kKBSkinThemeStoreKey = @"KBSkinThemeCurrent";
@interface KBSkinManager ()
@property (atomic, strong, readwrite) KBSkinTheme *current;
@property (nonatomic, strong) NSCache<NSString *, UIImage *> *kb_fileImageCache;
@property (nonatomic, copy, nullable) NSString *kb_cachedBgSkinId;
@property (nonatomic, assign) BOOL kb_cachedBgResolved;
@property (nonatomic, strong, nullable) UIImage *kb_cachedBgImage;
@end
@implementation KBSkinManager
/// maxPixel
+ (nullable UIImage *)kb_imageAtPath:(NSString *)path maxPixel:(NSUInteger)maxPixel {
if (path.length == 0) return nil;
NSURL *url = [NSURL fileURLWithPath:path];
CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)url, NULL);
if (!source) return nil;
NSDictionary *opts = @{
(__bridge id)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(__bridge id)kCGImageSourceCreateThumbnailWithTransform : @YES,
(__bridge id)kCGImageSourceThumbnailMaxPixelSize : @(MAX(1, (NSInteger)maxPixel)),
};
CGImageRef cg = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef)opts);
CFRelease(source);
if (!cg) return nil;
UIImage *img = [UIImage imageWithCGImage:cg scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp];
CGImageRelease(cg);
return img;
}
static inline NSUInteger KBApproxImageCostBytes(UIImage *img) {
if (!img) return 0;
CGFloat scale = img.scale > 0 ? img.scale : [UIScreen mainScreen].scale;
CGSize s = img.size;
double px = (double)s.width * scale * (double)s.height * scale;
if (px <= 0) return 0;
// RGBA 4 bytes/pixel
double cost = px * 4.0;
if (cost > (double)NSUIntegerMax) return NSUIntegerMax;
return (NSUInteger)cost;
}
/// App Group Caches
+ (NSArray<NSString *> *)kb_candidateBaseRoots {
NSMutableArray<NSString *> *roots = [NSMutableArray array];
@@ -104,6 +140,14 @@ static NSString * const kKBSkinThemeStoreKey = @"KBSkinThemeCurrent";
- (instancetype)init {
if (self = [super init]) {
_kb_fileImageCache = [NSCache new];
// App
// iPad
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
_kb_fileImageCache.totalCostLimit = 24 * 1024 * 1024;
} else {
_kb_fileImageCache.totalCostLimit = 12 * 1024 * 1024;
}
KBSkinTheme *t = [self p_loadFromStore];
// App Group / 退
if (!t || ![self.class kb_hasAssetsForSkinId:t.skinId]) {
@@ -170,6 +214,7 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
- (BOOL)applyTheme:(KBSkinTheme *)theme {
if (!theme) return NO;
NSLog(@"🎨[SkinManager] apply theme id=%@ name=%@", theme.skinId, theme.name);
[self clearRuntimeImageCaches];
// App Group 使
[self p_saveToStore:theme];
// 广
@@ -187,6 +232,15 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
[self applyTheme:[self.class defaultTheme]];
}
- (void)clearRuntimeImageCaches {
@synchronized (self) {
[self.kb_fileImageCache removeAllObjects];
self.kb_cachedBgSkinId = nil;
self.kb_cachedBgResolved = NO;
self.kb_cachedBgImage = nil;
}
}
- (BOOL)applyImageSkinWithData:(NSData *)imageData skinId:(NSString *)skinId name:(NSString *)name {
// 使 App Group
// Skins/<skinId>/background.png Keychain
@@ -216,20 +270,52 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
NSString *skinId = self.current.skinId;
if (skinId.length == 0) return nil;
// skinId
@synchronized (self) {
if (self.kb_cachedBgResolved && [self.kb_cachedBgSkinId isEqualToString:skinId]) {
return self.kb_cachedBgImage;
}
}
NSArray<NSString *> *roots = [self.class kb_candidateBaseRoots];
NSFileManager *fm = [NSFileManager defaultManager];
NSString *relative = [NSString stringWithFormat:@"Skins/%@/background.png", skinId];
//
NSUInteger maxPixel = (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) ? 2048 : 1024;
for (NSString *base in roots) {
NSString *bgPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if (![fm fileExistsAtPath:bgPath isDirectory:&isDir] || isDir) {
continue;
}
NSData *data = [NSData dataWithContentsOfFile:bgPath];
if (data.length == 0) continue;
UIImage *img = [UIImage imageWithData:data scale:[UIScreen mainScreen].scale];
if (img) return img;
NSString *cacheKey = [NSString stringWithFormat:@"bg|%@", bgPath];
UIImage *cached = [self.kb_fileImageCache objectForKey:cacheKey];
if (cached) {
@synchronized (self) {
self.kb_cachedBgSkinId = skinId;
self.kb_cachedBgResolved = YES;
self.kb_cachedBgImage = cached;
}
return cached;
}
UIImage *img = [self.class kb_imageAtPath:bgPath maxPixel:maxPixel];
if (img) {
NSUInteger cost = KBApproxImageCostBytes(img);
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:cost];
@synchronized (self) {
self.kb_cachedBgSkinId = skinId;
self.kb_cachedBgResolved = YES;
self.kb_cachedBgImage = img;
}
return img;
}
}
@synchronized (self) {
self.kb_cachedBgSkinId = skinId;
self.kb_cachedBgResolved = YES;
self.kb_cachedBgImage = nil;
}
return nil;
}
@@ -314,7 +400,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
if (![fm fileExistsAtPath:fullPath isDirectory:&isDir] || isDir) {
continue;
}
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
NSString *cacheKey = [NSString stringWithFormat:@"icon|%@", fullPath];
UIImage *img = [self.kb_fileImageCache objectForKey:cacheKey];
if (img) return img;
img = [UIImage imageWithContentsOfFile:fullPath];
if (img) {
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:KBApproxImageCostBytes(img)];
}
if (img) return img;
}
#if DEBUG
@@ -351,7 +443,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
NSString *fullPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && !isDir) {
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
NSString *cacheKey = [NSString stringWithFormat:@"icon|%@", fullPath];
UIImage *img = [self.kb_fileImageCache objectForKey:cacheKey];
if (img) return img;
img = [UIImage imageWithContentsOfFile:fullPath];
if (img) {
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:KBApproxImageCostBytes(img)];
}
if (img) return img;
}
}
@@ -363,7 +461,13 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
NSString *fullPath = [[base stringByAppendingPathComponent:relative] stringByStandardizingPath];
BOOL isDir = NO;
if ([fm fileExistsAtPath:fullPath isDirectory:&isDir] && !isDir) {
UIImage *img = [UIImage imageWithContentsOfFile:fullPath];
NSString *cacheKey = [NSString stringWithFormat:@"icon|%@", fullPath];
UIImage *img = [self.kb_fileImageCache objectForKey:cacheKey];
if (img) return img;
img = [UIImage imageWithContentsOfFile:fullPath];
if (img) {
[self.kb_fileImageCache setObject:img forKey:cacheKey cost:KBApproxImageCostBytes(img)];
}
if (img) return img;
}
}
@@ -449,6 +553,7 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
if (!t || ![self.class kb_hasAssetsForSkinId:t.skinId]) {
t = [self.class defaultTheme];
}
[self clearRuntimeImageCaches];
self.current = t;
if (broadcast) {
[[NSNotificationCenter defaultCenter] postNotificationName:KBSkinDidChangeNotification object:nil];