From 98a94e765e40f53eca4525a3fc79474f847771fe Mon Sep 17 00:00:00 2001 From: serverlele30 Date: Fri, 23 Oct 2026 15:46:07 +0200 Subject: [PATCH] v11: encode wiederhergestellt, Absturz-Melder, Methoden-Check --- BVGNearby.xcodeproj/project.pbxproj | 4 --- BVGNearby/AppDelegate.m | 32 +++++++++++++++++++++++ BVGNearby/BVGAPI.h | 2 +- BVGNearby/BVGAPI.m | 16 ++++++++++++ BVGNearby/check_methods.py | 40 +++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 BVGNearby/check_methods.py diff --git a/BVGNearby.xcodeproj/project.pbxproj b/BVGNearby.xcodeproj/project.pbxproj index 49a07c8..859f43f 100644 --- a/BVGNearby.xcodeproj/project.pbxproj +++ b/BVGNearby.xcodeproj/project.pbxproj @@ -28,7 +28,6 @@ 65AABC92308B81050050A7AC /* GuidanceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 65AABC90308B81050050A7AC /* GuidanceManager.m */; }; 65AABC98308B83BE0050A7AC /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 65AABC94308B83BE0050A7AC /* SettingsViewController.m */; }; 65AABC99308B83BE0050A7AC /* BVGSettings.m in Sources */ = {isa = PBXBuildFile; fileRef = 65AABC95308B83BE0050A7AC /* BVGSettings.m */; }; - 65AABC9C308B87630050A7AC /* AppIcon.appiconset in Resources */ = {isa = PBXBuildFile; fileRef = 65AABC9B308B87630050A7AC /* AppIcon.appiconset */; }; 65AABCA5308B87E00050A7AC /* CardCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 65AABC9E308B87E00050A7AC /* CardCell.m */; }; 65AABCA6308B87E00050A7AC /* ChipBar.m in Sources */ = {isa = PBXBuildFile; fileRef = 65AABCA1308B87E00050A7AC /* ChipBar.m */; }; 65AABCA7308B87E00050A7AC /* BVGTheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 65AABCA3308B87E00050A7AC /* BVGTheme.m */; }; @@ -78,7 +77,6 @@ 65AABC94308B83BE0050A7AC /* SettingsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = ""; }; 65AABC95308B83BE0050A7AC /* BVGSettings.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVGSettings.m; sourceTree = ""; }; 65AABC97308B83BE0050A7AC /* SettingsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = ""; }; - 65AABC9B308B87630050A7AC /* AppIcon.appiconset */ = {isa = PBXFileReference; lastKnownFileType = folder; path = AppIcon.appiconset; sourceTree = ""; }; 65AABC9D308B87E00050A7AC /* CardCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CardCell.h; sourceTree = ""; }; 65AABC9E308B87E00050A7AC /* CardCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CardCell.m; sourceTree = ""; }; 65AABC9F308B87E00050A7AC /* ChipBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ChipBar.h; sourceTree = ""; }; @@ -137,7 +135,6 @@ 65AABC74308B7A0E0050A7AC /* BVGAPI.h */, 65AABC93308B83BE0050A7AC /* BVGSettings.h */, 65AABC95308B83BE0050A7AC /* BVGSettings.m */, - 65AABC9B308B87630050A7AC /* AppIcon.appiconset */, 65AABC97308B83BE0050A7AC /* SettingsViewController.h */, 65AABC94308B83BE0050A7AC /* SettingsViewController.m */, 65AABC76308B7A0E0050A7AC /* BVGAPI.m */, @@ -235,7 +232,6 @@ buildActionMask = 2147483647; files = ( 65AABCAC308B91540050A7AC /* add_files.rb in Resources */, - 65AABC9C308B87630050A7AC /* AppIcon.appiconset in Resources */, 659F88E3308B5CFA00D46649 /* LaunchScreen.storyboard in Resources */, 659F88E0308B5CFA00D46649 /* Assets.xcassets in Resources */, 65AABC8B308B7D8C0050A7AC /* LIESMICH.txt in Resources */, diff --git a/BVGNearby/AppDelegate.m b/BVGNearby/AppDelegate.m index a2b796e..bb152da 100644 --- a/BVGNearby/AppDelegate.m +++ b/BVGNearby/AppDelegate.m @@ -16,9 +16,38 @@ @property (nonatomic, strong) UITabBarController *tabs; @end +// Absturz-Melder: speichert Grund + Stack einer nicht abgefangenen Exception, +// beim naechsten Start wird beides angezeigt (iOS 9 schreibt den Grund nicht in den Crash-Report). +static NSString * const kLastCrashKey = @"lastCrash"; + +static void BVGUncaughtException(NSException *e) { + NSArray *stack = e.callStackSymbols ?: @[]; + stack = [stack subarrayWithRange:NSMakeRange(0, MIN((NSUInteger)25, stack.count))]; + NSString *report = [NSString stringWithFormat:@"%@\n%@\n\n%@", e.name, e.reason ?: @"(kein Grund)", + [stack componentsJoinedByString:@"\n"]]; + [[NSUserDefaults standardUserDefaults] setObject:report forKey:kLastCrashKey]; + [[NSUserDefaults standardUserDefaults] synchronize]; +} + @implementation AppDelegate +- (void)showLastCrashIfAny { + NSString *report = [[NSUserDefaults standardUserDefaults] stringForKey:kLastCrashKey]; + if (!report) return; + [[NSUserDefaults standardUserDefaults] removeObjectForKey:kLastCrashKey]; + + UIAlertController *a = [UIAlertController alertControllerWithTitle:@"Letzter Absturz" + message:report + preferredStyle:UIAlertControllerStyleAlert]; + [a addAction:[UIAlertAction actionWithTitle:@"Kopieren" style:UIAlertActionStyleDefault handler:^(UIAlertAction *x) { + [UIPasteboard generalPasteboard].string = report; + }]]; + [a addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]]; + [self.window.rootViewController presentViewController:a animated:YES completion:nil]; +} + - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + NSSetUncaughtExceptionHandler(&BVGUncaughtException); [application registerUserNotificationSettings: [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge) categories:nil]]; @@ -48,6 +77,9 @@ self.window.tintColor = [BVGTheme textPrimary]; self.window.rootViewController = self.tabs; [self.window makeKeyAndVisible]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [self showLastCrashIfAny]; + }); // Favoriten in die iOS-Suche aufnehmen (falls frisch installiert) for (NSDictionary *f in [BVGAPI favorites]) [BVGAPI indexStopInSpotlight:f[@"id"] name:f[@"name"]]; diff --git a/BVGNearby/BVGAPI.h b/BVGNearby/BVGAPI.h index 10df00f..ba65011 100644 --- a/BVGNearby/BVGAPI.h +++ b/BVGNearby/BVGAPI.h @@ -10,7 +10,7 @@ extern NSString * const BVGAlarmsChangedNotification; // Netzwerk + (void)getJSON:(NSString *)path completion:(void (^)(id json, NSError *error))completion; -+ (NSString *)encode:(NSString *)s; ++ (NSString *)encode:(id)value; // NSString, NSNumber, nil/NSNull -> URL-kodiert + (NSString *)lastSource; // "serverlele" oder "transport.rest" - wer zuletzt geantwortet hat // Zeit diff --git a/BVGNearby/BVGAPI.m b/BVGNearby/BVGAPI.m index 8789a0f..0361dd6 100644 --- a/BVGNearby/BVGAPI.m +++ b/BVGNearby/BVGAPI.m @@ -87,6 +87,22 @@ NSString * const BVGAlarmsChangedNotification = @"BVGAlarmsChanged"; }] resume]; } +// Fuer URL-Pfade und -Parameter: nur ASCII-Buchstaben/Ziffern bleiben unkodiert +// (sonst liefert NSURL bei Umlauten nil). Nimmt auch NSNumber/NSNull entgegen. ++ (NSString *)encode:(id)value { + NSString *s = nil; + if ([value isKindOfClass:[NSString class]]) s = value; + else if (value && value != [NSNull null]) s = [value description]; + if (!s.length) return @""; + static NSCharacterSet *allowed; + static dispatch_once_t once; + dispatch_once(&once, ^{ + allowed = [NSCharacterSet characterSetWithCharactersInString: + @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"]; + }); + return [s stringByAddingPercentEncodingWithAllowedCharacters:allowed] ?: @""; +} + #pragma mark - Zeit + (NSDateFormatter *)isoFormatter { diff --git a/BVGNearby/check_methods.py b/BVGNearby/check_methods.py new file mode 100644 index 0000000..d823feb --- /dev/null +++ b/BVGNearby/check_methods.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Prueft, ob jede in einer .h deklarierte Methode in der passenden .m implementiert ist. +Hintergrund: Objective-C kompiliert auch dann, wenn eine Methode fehlt - die App stuerzt erst +zur Laufzeit mit "unrecognized selector" ab. + +Aufruf (im Projektordner): python3 BVGNearby/check_methods.py BVGNearby +Rueckgabe: 0 = alles da, 1 = etwas fehlt (bricht z.B. deploy.sh ab) +""" +import glob +import os +import re +import sys + +folder = sys.argv[1] if len(sys.argv) > 1 else "." +missing = [] + +for header in sorted(glob.glob(os.path.join(folder, "*.h"))): + impl_path = header[:-2] + ".m" + if not os.path.exists(impl_path): + continue + hdr = open(header, encoding="utf-8").read() + impl = open(impl_path, encoding="utf-8").read() + for decl in re.findall(r"^[+-]\s*\([^)]*\)\s*[^;]+;", hdr, re.M): + kind = decl[0] + parts = re.findall(r"(\w+)\s*:", decl) + if parts: + selector = "".join(p + ":" for p in parts) + pattern = (r"^" + re.escape(kind) + r"\s*\([^)]*\)\s*" + + r".*?".join(re.escape(p) + r"\s*:" for p in parts) + r"[^;{]*\{") + else: + selector = re.match(r"^[+-]\s*\([^)]*\)\s*(\w+)", decl).group(1) + pattern = r"^" + re.escape(kind) + r"\s*\([^)]*\)\s*" + re.escape(selector) + r"\s*\{" + if not re.search(pattern, impl, re.M | re.S): + missing.append(f"{os.path.basename(impl_path)}: {kind}{selector}") + +if missing: + print("FEHLENDE METHODEN (App würde abstürzen):") + print("\n".join(" " + m for m in missing)) + sys.exit(1) +print("Methoden-Check: alle deklarierten Methoden sind implementiert.")