mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-11-03 04:49:28 -05:00
added files via upload
This commit is contained in:
442
Tweaks/YTABConfig/Tweak.xm
Normal file
442
Tweaks/YTABConfig/Tweak.xm
Normal file
@@ -0,0 +1,442 @@
|
||||
#import <rootless.h>
|
||||
#import "../YouTubeHeader/YTAlertView.h"
|
||||
#import "../YouTubeHeader/YTAppDelegate.h"
|
||||
#import "../YouTubeHeader/YTCommonUtils.h"
|
||||
#import "../YouTubeHeader/YTUIUtils.h"
|
||||
#import "../YouTubeHeader/YTVersionUtils.h"
|
||||
#import "../YouTubeHeader/YTGlobalConfig.h"
|
||||
#import "../YouTubeHeader/YTColdConfig.h"
|
||||
#import "../YouTubeHeader/YTHotConfig.h"
|
||||
#import "../YouTubeHeader/YTSettingsSectionItem.h"
|
||||
#import "../YouTubeHeader/YTSettingsSectionItemManager.h"
|
||||
#import "../YouTubeHeader/YTSettingsPickerViewController.h"
|
||||
#import "../YouTubeHeader/YTSettingsViewController.h"
|
||||
#import "../YouTubeHeader/YTSearchableSettingsViewController.h"
|
||||
#import "../YouTubeHeader/YTToastResponderEvent.h"
|
||||
|
||||
#define Prefix @"YTABC"
|
||||
#define EnabledKey @"EnabledYTABC"
|
||||
#define GroupedKey @"GroupedYTABC"
|
||||
#define INCLUDED_CLASSES @"Included classes: YTGlobalConfig, YTColdConfig, YTHotConfig"
|
||||
#define EXCLUDED_METHODS @"Excluded settings: android*, amsterdam*, musicClient* and unplugged*"
|
||||
|
||||
#define _LOC(b, x) [b localizedStringForKey:x value:nil table:nil]
|
||||
#define LOC(x) _LOC(tweakBundle, x)
|
||||
|
||||
static const NSInteger YTABCSection = 404;
|
||||
|
||||
@interface YTSettingsSectionItemManager (YTABConfig)
|
||||
- (void)updateYTABCSectionWithEntry:(id)entry;
|
||||
@end
|
||||
|
||||
NSMutableDictionary <NSString *, NSMutableDictionary <NSString *, NSNumber *> *> *cache;
|
||||
NSUserDefaults *defaults;
|
||||
NSArray <NSString *> *allKeys;
|
||||
|
||||
static BOOL tweakEnabled() {
|
||||
return [defaults boolForKey:EnabledKey];
|
||||
}
|
||||
|
||||
static BOOL groupedSettings() {
|
||||
return [defaults boolForKey:GroupedKey];
|
||||
}
|
||||
|
||||
static NSString *getKey(NSString *method, NSString *classKey) {
|
||||
return [NSString stringWithFormat:@"%@.%@.%@", Prefix, classKey, method];
|
||||
}
|
||||
|
||||
static NSString *getCacheKey(NSString *method, NSString *classKey) {
|
||||
return [NSString stringWithFormat:@"%@.%@", classKey, method];
|
||||
}
|
||||
|
||||
static BOOL getValue(NSString *methodKey) {
|
||||
if (![allKeys containsObject:methodKey])
|
||||
return [[cache valueForKeyPath:[methodKey substringFromIndex:Prefix.length + 1]] boolValue];
|
||||
return [defaults boolForKey:methodKey];
|
||||
}
|
||||
|
||||
static void setValue(NSString *method, NSString *classKey, BOOL value) {
|
||||
[cache setValue:@(value) forKeyPath:getCacheKey(method, classKey)];
|
||||
[defaults setBool:value forKey:getKey(method, classKey)];
|
||||
}
|
||||
|
||||
static void updateAllKeys() {
|
||||
allKeys = [defaults dictionaryRepresentation].allKeys;
|
||||
}
|
||||
|
||||
static BOOL returnFunction(id const self, SEL _cmd) {
|
||||
NSString *method = NSStringFromSelector(_cmd);
|
||||
NSString *methodKey = getKey(method, NSStringFromClass([self class]));
|
||||
return getValue(methodKey);
|
||||
}
|
||||
|
||||
static BOOL getValueFromInvocation(id target, SEL selector) {
|
||||
NSInvocationOperation *i = [[NSInvocationOperation alloc] initWithTarget:target selector:selector object:nil];
|
||||
[i start];
|
||||
BOOL result = NO;
|
||||
[i.result getValue:&result];
|
||||
return result;
|
||||
}
|
||||
|
||||
NSBundle *YTABCBundle() {
|
||||
static NSBundle *bundle = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *tweakBundlePath = [[NSBundle mainBundle] pathForResource:@"YTABC" ofType:@"bundle"];
|
||||
if (tweakBundlePath)
|
||||
bundle = [NSBundle bundleWithPath:tweakBundlePath];
|
||||
else
|
||||
bundle = [NSBundle bundleWithPath:ROOT_PATH_NS(@"/Library/Application Support/YTABC.bundle")];
|
||||
});
|
||||
return bundle;
|
||||
}
|
||||
|
||||
%group Search
|
||||
|
||||
%hook YTSettingsViewController
|
||||
|
||||
- (void)loadWithModel:(id)model fromView:(UIView *)view {
|
||||
%orig;
|
||||
if ([[self valueForKey:@"_detailsCategoryID"] integerValue] == YTABCSection)
|
||||
MSHookIvar<BOOL>(self, "_shouldShowSearchBar") = YES;
|
||||
}
|
||||
|
||||
- (void)setSectionControllers {
|
||||
%orig;
|
||||
if (MSHookIvar<BOOL>(self, "_shouldShowSearchBar")) {
|
||||
YTSettingsSectionController *settingsSectionController = [self settingsSectionControllers][[self valueForKey:@"_detailsCategoryID"]];
|
||||
if (settingsSectionController) {
|
||||
YTSearchableSettingsViewController *searchableVC = [self valueForKey:@"_searchableSettingsViewController"];
|
||||
[searchableVC storeCollectionViewSections:@[settingsSectionController]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
%end
|
||||
|
||||
%end
|
||||
|
||||
%hook YTSettingsSectionController
|
||||
|
||||
- (void)setSelectedItem:(NSUInteger)selectedItem {
|
||||
if (selectedItem != NSNotFound) %orig;
|
||||
}
|
||||
|
||||
%end
|
||||
|
||||
%hook YTAppSettingsPresentationData
|
||||
|
||||
+ (NSArray *)settingsCategoryOrder {
|
||||
NSArray *order = %orig;
|
||||
NSMutableArray *mutableOrder = [order mutableCopy];
|
||||
[mutableOrder insertObject:@(YTABCSection) atIndex:0];
|
||||
return mutableOrder;
|
||||
}
|
||||
|
||||
%end
|
||||
|
||||
static NSString *getCategory(char c, NSString *method) {
|
||||
if (c == 'e') {
|
||||
if ([method hasPrefix:@"elements"]) return @"elements";
|
||||
if ([method hasPrefix:@"enable"]) return @"enable";
|
||||
}
|
||||
if (c == 'i') {
|
||||
if ([method hasPrefix:@"ios"]) return @"ios";
|
||||
if ([method hasPrefix:@"is"]) return @"is";
|
||||
}
|
||||
if (c == 's') {
|
||||
if ([method hasPrefix:@"shorts"]) return @"shorts";
|
||||
if ([method hasPrefix:@"should"]) return @"should";
|
||||
}
|
||||
unichar uc = (unichar)c;
|
||||
return [NSString stringWithCharacters:&uc length:1];;
|
||||
}
|
||||
|
||||
%hook YTSettingsSectionItemManager
|
||||
|
||||
%new(v@:@)
|
||||
- (void)updateYTABCSectionWithEntry:(id)entry {
|
||||
NSMutableArray *sectionItems = [NSMutableArray array];
|
||||
int totalSettings = 0;
|
||||
NSBundle *tweakBundle = YTABCBundle();
|
||||
BOOL isPhone = ![%c(YTCommonUtils) isIPad];
|
||||
NSString *yesText = _LOC([NSBundle mainBundle], @"settings.yes");
|
||||
NSString *cancelText = _LOC([NSBundle mainBundle], @"confirm.cancel");
|
||||
NSString *deleteText = _LOC([NSBundle mainBundle], @"search.action.delete");
|
||||
Class YTSettingsSectionItemClass = %c(YTSettingsSectionItem);
|
||||
Class YTAlertViewClass = %c(YTAlertView);
|
||||
if (tweakEnabled()) {
|
||||
NSMutableDictionary <NSString *, NSMutableArray <YTSettingsSectionItem *> *> *properties = [NSMutableDictionary dictionary];
|
||||
for (NSString *classKey in cache) {
|
||||
for (NSString *method in cache[classKey]) {
|
||||
char c = tolower([method characterAtIndex:0]);
|
||||
NSString *category = getCategory(c, method);
|
||||
if (![properties objectForKey:category]) properties[category] = [NSMutableArray array];
|
||||
updateAllKeys();
|
||||
BOOL modified = [allKeys containsObject:getKey(method, classKey)];
|
||||
NSString *modifiedTitle = modified ? [NSString stringWithFormat:@"%@ *", method] : method;
|
||||
YTSettingsSectionItem *methodSwitch = [YTSettingsSectionItemClass switchItemWithTitle:modifiedTitle
|
||||
titleDescription:isPhone && method.length > 26 ? modifiedTitle : nil
|
||||
accessibilityIdentifier:nil
|
||||
switchOn:getValue(getKey(method, classKey))
|
||||
switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
|
||||
setValue(method, classKey, enabled);
|
||||
return YES;
|
||||
}
|
||||
selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
|
||||
NSString *content = [NSString stringWithFormat:@"%@.%@", classKey, method];
|
||||
YTAlertView *alertView = [YTAlertViewClass confirmationDialog];
|
||||
alertView.title = method;
|
||||
alertView.subtitle = content;
|
||||
[alertView addTitle:LOC(@"COPY_TO_CLIPBOARD") withAction:^{
|
||||
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||
pasteboard.string = content;
|
||||
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"COPIED_TO_CLIPBOARD") firstResponder:[self parentResponder]] send];
|
||||
}];
|
||||
updateAllKeys();
|
||||
NSString *key = getKey(method, classKey);
|
||||
if ([allKeys containsObject:key]) {
|
||||
[alertView addTitle:deleteText withAction:^{
|
||||
[defaults removeObjectForKey:key];
|
||||
updateAllKeys();
|
||||
}];
|
||||
}
|
||||
[alertView addCancelButton:NULL];
|
||||
[alertView show];
|
||||
return NO;
|
||||
}
|
||||
settingItemId:0];
|
||||
[properties[category] addObject:methodSwitch];
|
||||
}
|
||||
}
|
||||
YTSettingsViewController *settingsViewController = [self valueForKey:@"_settingsViewControllerDelegate"];
|
||||
BOOL grouped = groupedSettings();
|
||||
for (NSString *category in properties) {
|
||||
NSMutableArray <YTSettingsSectionItem *> *rows = properties[category];
|
||||
totalSettings += rows.count;
|
||||
if (grouped) {
|
||||
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES];
|
||||
[rows sortUsingDescriptors:@[sort]];
|
||||
NSString *shortTitle = [NSString stringWithFormat:@"\"%@\" (%ld)", category, rows.count];
|
||||
NSString *title = [NSString stringWithFormat:@"%@ %@", LOC(@"SETTINGS_START_WITH"), shortTitle];
|
||||
YTSettingsSectionItem *sectionItem = [YTSettingsSectionItemClass itemWithTitle:title accessibilityIdentifier:nil detailTextBlock:nil selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
|
||||
YTSettingsPickerViewController *picker = [[%c(YTSettingsPickerViewController) alloc] initWithNavTitle:shortTitle pickerSectionTitle:nil rows:rows selectedItemIndex:NSNotFound parentResponder:[self parentResponder]];
|
||||
[settingsViewController pushViewController:picker];
|
||||
return YES;
|
||||
}];
|
||||
[sectionItems addObject:sectionItem];
|
||||
} else {
|
||||
[sectionItems addObjectsFromArray:rows];
|
||||
}
|
||||
}
|
||||
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES];
|
||||
[sectionItems sortUsingDescriptors:@[sort]];
|
||||
YTSettingsSectionItem *copyAll = [YTSettingsSectionItemClass itemWithTitle:LOC(@"COPY_CURRENT_SETTINGS")
|
||||
titleDescription:LOC(@"COPY_CURRENT_SETTINGS_DESC")
|
||||
accessibilityIdentifier:nil
|
||||
detailTextBlock:nil
|
||||
selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
|
||||
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||
NSMutableArray *content = [NSMutableArray array];
|
||||
for (NSString *classKey in cache) {
|
||||
[cache[classKey] enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSNumber *value, BOOL* stop) {
|
||||
[content addObject:[NSString stringWithFormat:@"%@: %d", key, [value boolValue]]];
|
||||
}];
|
||||
}
|
||||
[content sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
[content insertObject:[NSString stringWithFormat:@"Device model: %@", [%c(YTCommonUtils) hardwareModel]] atIndex:0];
|
||||
[content insertObject:[NSString stringWithFormat:@"App version: %@", [%c(YTVersionUtils) appVersion]] atIndex:0];
|
||||
[content insertObject:EXCLUDED_METHODS atIndex:0];
|
||||
[content insertObject:INCLUDED_CLASSES atIndex:0];
|
||||
[content insertObject:[NSString stringWithFormat:@"YTABConfig version: %@", @(OS_STRINGIFY(TWEAK_VERSION))] atIndex:0];
|
||||
pasteboard.string = [content componentsJoinedByString:@"\n"];
|
||||
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"COPIED_TO_CLIPBOARD") firstResponder:[self parentResponder]] send];
|
||||
return YES;
|
||||
}];
|
||||
[sectionItems insertObject:copyAll atIndex:0];
|
||||
YTSettingsSectionItem *modified = [YTSettingsSectionItemClass itemWithTitle:LOC(@"VIEW_MODIFIED_SETTINGS")
|
||||
titleDescription:LOC(@"VIEW_MODIFIED_SETTINGS_DESC")
|
||||
accessibilityIdentifier:nil
|
||||
detailTextBlock:nil
|
||||
selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
|
||||
NSMutableArray *features = [NSMutableArray array];
|
||||
updateAllKeys();
|
||||
for (NSString *key in allKeys) {
|
||||
if ([key hasPrefix:Prefix]) {
|
||||
NSString *displayKey = [key substringFromIndex:Prefix.length + 1];
|
||||
[features addObject:[NSString stringWithFormat:@"%@: %d", displayKey, [defaults boolForKey:key]]];
|
||||
}
|
||||
}
|
||||
[features sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
[features insertObject:[NSString stringWithFormat:LOC(@"TOTAL_MODIFIED_SETTINGS"), features.count] atIndex:0];
|
||||
NSString *content = [features componentsJoinedByString:@"\n"];
|
||||
YTAlertView *alertView = [YTAlertViewClass confirmationDialogWithAction:^{
|
||||
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
|
||||
pasteboard.string = content;
|
||||
[[%c(YTToastResponderEvent) eventWithMessage:LOC(@"COPIED_TO_CLIPBOARD") firstResponder:[self parentResponder]] send];
|
||||
} actionTitle:LOC(@"COPY_TO_CLIPBOARD")];
|
||||
alertView.title = LOC(@"MODIFIED_SETTINGS_TITLE");
|
||||
alertView.subtitle = content;
|
||||
[alertView show];
|
||||
return YES;
|
||||
}];
|
||||
[sectionItems insertObject:modified atIndex:0];
|
||||
YTSettingsSectionItem *reset = [YTSettingsSectionItemClass itemWithTitle:LOC(@"RESET_KILL")
|
||||
titleDescription:LOC(@"RESET_KILL_DESC")
|
||||
accessibilityIdentifier:nil
|
||||
detailTextBlock:nil
|
||||
selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
|
||||
YTAlertView *alertView = [YTAlertViewClass confirmationDialogWithAction:^{
|
||||
updateAllKeys();
|
||||
for (NSString *key in allKeys) {
|
||||
if ([key hasPrefix:Prefix])
|
||||
[defaults removeObjectForKey:key];
|
||||
}
|
||||
exit(0);
|
||||
} actionTitle:yesText];
|
||||
alertView.title = LOC(@"WARNING");
|
||||
alertView.subtitle = LOC(@"APPLY_DESC");
|
||||
[alertView show];
|
||||
return YES;
|
||||
}];
|
||||
[sectionItems insertObject:reset atIndex:0];
|
||||
YTSettingsSectionItem *group = [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"GROUPED")
|
||||
titleDescription:nil
|
||||
accessibilityIdentifier:nil
|
||||
switchOn:groupedSettings()
|
||||
switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
|
||||
YTAlertView *alertView = [YTAlertViewClass confirmationDialogWithAction:^{
|
||||
[defaults setBool:enabled forKey:GroupedKey];
|
||||
exit(0);
|
||||
}
|
||||
actionTitle:yesText
|
||||
cancelAction:^{
|
||||
[cell setSwitchOn:!enabled animated:YES];
|
||||
}
|
||||
cancelTitle:cancelText];
|
||||
alertView.title = LOC(@"WARNING");
|
||||
alertView.subtitle = LOC(@"APPLY_DESC");
|
||||
[alertView show];
|
||||
return YES;
|
||||
}
|
||||
settingItemId:0];
|
||||
[sectionItems insertObject:group atIndex:0];
|
||||
}
|
||||
YTSettingsSectionItem *thread = [YTSettingsSectionItemClass itemWithTitle:LOC(@"OPEN_MEGATHREAD")
|
||||
titleDescription:LOC(@"OPEN_MEGATHREAD_DESC")
|
||||
accessibilityIdentifier:nil
|
||||
detailTextBlock:nil
|
||||
selectBlock:^BOOL (YTSettingsCell *cell, NSUInteger arg1) {
|
||||
return [%c(YTUIUtils) openURL:[NSURL URLWithString:@"https://github.com/PoomSmart/YTABConfig/discussions"]];
|
||||
}];
|
||||
[sectionItems insertObject:thread atIndex:0];
|
||||
YTSettingsSectionItem *master = [YTSettingsSectionItemClass switchItemWithTitle:LOC(@"ENABLED")
|
||||
titleDescription:LOC(@"ENABLED_DESC")
|
||||
accessibilityIdentifier:nil
|
||||
switchOn:tweakEnabled()
|
||||
switchBlock:^BOOL (YTSettingsCell *cell, BOOL enabled) {
|
||||
[defaults setBool:enabled forKey:EnabledKey];
|
||||
YTAlertView *alertView = [YTAlertViewClass confirmationDialogWithAction:^{ exit(0); }
|
||||
actionTitle:yesText
|
||||
cancelAction:^{
|
||||
[cell setSwitchOn:!enabled animated:YES];
|
||||
}
|
||||
cancelTitle:cancelText];
|
||||
alertView.title = LOC(@"WARNING");
|
||||
alertView.subtitle = LOC(@"APPLY_DESC");
|
||||
[alertView show];
|
||||
return YES;
|
||||
}
|
||||
settingItemId:0];
|
||||
[sectionItems insertObject:master atIndex:0];
|
||||
YTSettingsViewController *delegate = [self valueForKey:@"_dataDelegate"];
|
||||
[delegate setSectionItems:sectionItems
|
||||
forCategory:YTABCSection
|
||||
title:@"A/B"
|
||||
titleDescription:tweakEnabled() ? [NSString stringWithFormat:@"YTABConfig %@, %d feature flags.", @(OS_STRINGIFY(TWEAK_VERSION)), totalSettings] : nil
|
||||
headerHidden:NO];
|
||||
}
|
||||
|
||||
- (void)updateSectionForCategory:(NSUInteger)category withEntry:(id)entry {
|
||||
if (category == YTABCSection) {
|
||||
[self updateYTABCSectionWithEntry:entry];
|
||||
return;
|
||||
}
|
||||
%orig;
|
||||
}
|
||||
|
||||
%end
|
||||
|
||||
static NSMutableArray <NSString *> *getBooleanMethods(Class clz) {
|
||||
NSMutableArray *allMethods = [NSMutableArray array];
|
||||
unsigned int methodCount = 0;
|
||||
Method *methods = class_copyMethodList(clz, &methodCount);
|
||||
for (unsigned int i = 0; i < methodCount; ++i) {
|
||||
Method method = methods[i];
|
||||
const char *name = sel_getName(method_getName(method));
|
||||
if (strstr(name, "ndroid") || strstr(name, "musicClient") || strstr(name, "amsterdam") || strstr(name, "unplugged")) continue;
|
||||
const char *encoding = method_getTypeEncoding(method);
|
||||
if (strcmp(encoding, "B16@0:8")) continue;
|
||||
NSString *selector = [NSString stringWithUTF8String:name];
|
||||
if (![allMethods containsObject:selector])
|
||||
[allMethods addObject:selector];
|
||||
}
|
||||
free(methods);
|
||||
return allMethods;
|
||||
}
|
||||
|
||||
static void hookClass(NSObject *instance) {
|
||||
if (!instance) [NSException raise:@"hookClass Invalid argument exception" format:@"Hooking the class of a non-existing instance"];
|
||||
Class instanceClass = [instance class];
|
||||
NSMutableArray <NSString *> *methods = getBooleanMethods(instanceClass);
|
||||
NSString *classKey = NSStringFromClass(instanceClass);
|
||||
NSMutableDictionary *classCache = cache[classKey] = [NSMutableDictionary new];
|
||||
for (NSString *method in methods) {
|
||||
SEL selector = NSSelectorFromString(method);
|
||||
BOOL result = getValueFromInvocation(instance, selector);
|
||||
classCache[method] = @(result);
|
||||
MSHookMessageEx(instanceClass, selector, (IMP)returnFunction, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
%hook YTAppDelegate
|
||||
|
||||
- (BOOL)application:(id)arg1 didFinishLaunchingWithOptions:(id)arg2 {
|
||||
defaults = [NSUserDefaults standardUserDefaults];
|
||||
if (tweakEnabled()) {
|
||||
updateAllKeys();
|
||||
YTGlobalConfig *globalConfig;
|
||||
YTColdConfig *coldConfig;
|
||||
YTHotConfig *hotConfig;
|
||||
@try {
|
||||
globalConfig = [self valueForKey:@"_globalConfig"];
|
||||
coldConfig = [self valueForKey:@"_coldConfig"];
|
||||
hotConfig = [self valueForKey:@"_hotConfig"];
|
||||
} @catch (id ex) {
|
||||
id settings = [self valueForKey:@"_settings"];
|
||||
globalConfig = [settings valueForKey:@"_globalConfig"];
|
||||
coldConfig = [settings valueForKey:@"_coldConfig"];
|
||||
hotConfig = [settings valueForKey:@"_hotConfig"];
|
||||
}
|
||||
hookClass(globalConfig);
|
||||
hookClass(coldConfig);
|
||||
hookClass(hotConfig);
|
||||
if (!groupedSettings()) {
|
||||
%init(Search);
|
||||
}
|
||||
}
|
||||
return %orig;
|
||||
}
|
||||
|
||||
%end
|
||||
|
||||
%ctor {
|
||||
NSBundle *bundle = [NSBundle bundleWithPath:[NSString stringWithFormat:@"%@/Frameworks/Module_Framework.framework", [[NSBundle mainBundle] bundlePath]]];
|
||||
if (!bundle.loaded) [bundle load];
|
||||
cache = [NSMutableDictionary new];
|
||||
%init;
|
||||
}
|
||||
|
||||
%dtor {
|
||||
[cache removeAllObjects];
|
||||
}
|
||||
Reference in New Issue
Block a user