123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- //
- // YMRouter.m
- // MSYOUPAI
- //
- // Created by YoMi on 2023/11/8.
- //
- #import "YMRouter.h"
- #import <objc/runtime.h>
- static NSString * const YM_ROUTER_WILDCARD_CHARACTER = @"~";
- static NSString *specialCharacters = @"/?&.";
- NSString *const YMRouterParameterURL = @"YMRouterParameterURL";
- NSString *const YMRouterParameterCompletion = @"YMRouterParameterCompletion";
- NSString *const YMRouterParameterUserInfo = @"YMRouterParameterUserInfo";
- @interface YMRouter ()
- /**
- * 保存了所有已注册的 URL
- * 结构类似 @{@"beauty": @{@":id": {@"_", [block copy]}}}
- */
- @property (nonatomic) NSMutableDictionary *routes;
- @end
- @implementation YMRouter
- + (instancetype)sharedInstance
- {
- static YMRouter *instance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- instance = [[self alloc] init];
- });
- return instance;
- }
- + (void)registerURLPattern:(NSString *)URLPattern toHandler:(YMRouterHandler)handler
- {
- [[self sharedInstance] addURLPattern:URLPattern andHandler:handler];
- }
- + (void)deregisterURLPattern:(NSString *)URLPattern
- {
- [[self sharedInstance] removeURLPattern:URLPattern];
- }
- + (void)openURL:(NSString *)URL
- {
- [self openURL:URL completion:nil];
- }
- + (void)openURL:(NSString *)URL completion:(void (^)(id result))completion
- {
- [self openURL:URL withUserInfo:nil completion:completion];
- }
- + (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion
- {
- URL = [URL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
- NSMutableDictionary *parameters = [[self sharedInstance] extractParametersFromURL:URL matchExactly:NO];
-
- [parameters enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
- if ([obj isKindOfClass:[NSString class]]) {
- parameters[key] = [obj stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
- }
- }];
-
- if (parameters) {
- YMRouterHandler handler = parameters[@"block"];
- if (completion) {
- parameters[YMRouterParameterCompletion] = completion;
- }
- if (userInfo) {
- parameters[YMRouterParameterUserInfo] = userInfo;
- }
- if (handler) {
- [parameters removeObjectForKey:@"block"];
- handler(parameters);
- }
- }
- }
- + (BOOL)canOpenURL:(NSString *)URL
- {
- return [[self sharedInstance] extractParametersFromURL:URL matchExactly:NO] ? YES : NO;
- }
- + (BOOL)canOpenURL:(NSString *)URL matchExactly:(BOOL)exactly {
- return [[self sharedInstance] extractParametersFromURL:URL matchExactly:YES] ? YES : NO;
- }
- + (NSString *)generateURLWithPattern:(NSString *)pattern parameters:(NSArray *)parameters
- {
- NSInteger startIndexOfColon = 0;
-
- NSMutableArray *placeholders = [NSMutableArray array];
-
- for (int i = 0; i < pattern.length; i++) {
- NSString *character = [NSString stringWithFormat:@"%c", [pattern characterAtIndex:i]];
- if ([character isEqualToString:@":"]) {
- startIndexOfColon = i;
- }
- if ([specialCharacters rangeOfString:character].location != NSNotFound && i > (startIndexOfColon + 1) && startIndexOfColon) {
- NSRange range = NSMakeRange(startIndexOfColon, i - startIndexOfColon);
- NSString *placeholder = [pattern substringWithRange:range];
- if (![self checkIfContainsSpecialCharacter:placeholder]) {
- [placeholders addObject:placeholder];
- startIndexOfColon = 0;
- }
- }
- if (i == pattern.length - 1 && startIndexOfColon) {
- NSRange range = NSMakeRange(startIndexOfColon, i - startIndexOfColon + 1);
- NSString *placeholder = [pattern substringWithRange:range];
- if (![self checkIfContainsSpecialCharacter:placeholder]) {
- [placeholders addObject:placeholder];
- }
- }
- }
-
- __block NSString *parsedResult = pattern;
-
- [placeholders enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
- idx = parameters.count > idx ? idx : parameters.count - 1;
- parsedResult = [parsedResult stringByReplacingOccurrencesOfString:obj withString:parameters[idx]];
- }];
-
- return parsedResult;
- }
- + (id)objectForURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo
- {
- YMRouter *router = [YMRouter sharedInstance];
-
- URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
- NSMutableDictionary *parameters = [router extractParametersFromURL:URL matchExactly:NO];
- YMRouterObjectHandler handler = parameters[@"block"];
-
- if (handler) {
- if (userInfo) {
- parameters[YMRouterParameterUserInfo] = userInfo;
- }
- [parameters removeObjectForKey:@"block"];
- return handler(parameters);
- }
- return nil;
- }
- + (id)objectForURL:(NSString *)URL
- {
- return [self objectForURL:URL withUserInfo:nil];
- }
- + (void)registerURLPattern:(NSString *)URLPattern toObjectHandler:(YMRouterObjectHandler)handler
- {
- [[self sharedInstance] addURLPattern:URLPattern andObjectHandler:handler];
- }
- - (void)addURLPattern:(NSString *)URLPattern andHandler:(YMRouterHandler)handler
- {
- NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
- if (handler && subRoutes) {
- subRoutes[@"_"] = [handler copy];
- }
- }
- - (void)addURLPattern:(NSString *)URLPattern andObjectHandler:(YMRouterObjectHandler)handler
- {
- NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
- if (handler && subRoutes) {
- subRoutes[@"_"] = [handler copy];
- }
- }
- - (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern
- {
- NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];
- NSMutableDictionary* subRoutes = self.routes;
-
- for (NSString* pathComponent in pathComponents) {
- if (![subRoutes objectForKey:pathComponent]) {
- subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
- }
- subRoutes = subRoutes[pathComponent];
- }
- return subRoutes;
- }
- #pragma mark - Utils
- - (NSMutableDictionary *)extractParametersFromURL:(NSString *)url matchExactly:(BOOL)exactly
- {
- NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
-
- parameters[YMRouterParameterURL] = url;
-
- NSMutableDictionary* subRoutes = self.routes;
- NSArray* pathComponents = [self pathComponentsFromURL:url];
-
- BOOL found = NO;
- // borrowed from HHRouter(https://github.com/Huohua/HHRouter)
- for (NSString* pathComponent in pathComponents) {
-
- // 对 key 进行排序,这样可以把 ~ 放到最后
- NSArray *subRoutesKeys =[subRoutes.allKeys sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
- return [obj1 compare:obj2];
- }];
-
- for (NSString* key in subRoutesKeys) {
- if ([key isEqualToString:pathComponent] || [key isEqualToString:YM_ROUTER_WILDCARD_CHARACTER]) {
- found = YES;
- subRoutes = subRoutes[key];
- break;
- } else if ([key hasPrefix:@":"]) {
- found = YES;
- subRoutes = subRoutes[key];
- NSString *newKey = [key substringFromIndex:1];
- NSString *newPathComponent = pathComponent;
- // 再做一下特殊处理,比如 :id.html -> :id
- if ([self.class checkIfContainsSpecialCharacter:key]) {
- NSCharacterSet *specialCharacterSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];
- NSRange range = [key rangeOfCharacterFromSet:specialCharacterSet];
- if (range.location != NSNotFound) {
- // 把 pathComponent 后面的部分也去掉
- newKey = [newKey substringToIndex:range.location - 1];
- NSString *suffixToStrip = [key substringFromIndex:range.location];
- newPathComponent = [newPathComponent stringByReplacingOccurrencesOfString:suffixToStrip withString:@""];
- }
- }
- parameters[newKey] = newPathComponent;
- break;
- } else if (exactly) {
- found = NO;
- }
- }
-
- // 如果没有找到该 pathComponent 对应的 handler,则以上一层的 handler 作为 fallback
- if (!found && !subRoutes[@"_"]) {
- return nil;
- }
- }
-
- // Extract Params From Query.
- NSArray<NSURLQueryItem *> *queryItems = [[NSURLComponents alloc] initWithURL:[[NSURL alloc] initWithString:url] resolvingAgainstBaseURL:false].queryItems;
-
- for (NSURLQueryItem *item in queryItems) {
- parameters[item.name] = item.value;
- }
- if (subRoutes[@"_"]) {
- parameters[@"block"] = [subRoutes[@"_"] copy];
- }
-
- return parameters;
- }
- - (void)removeURLPattern:(NSString *)URLPattern
- {
- NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:[self pathComponentsFromURL:URLPattern]];
-
- // 只删除该 pattern 的最后一级
- if (pathComponents.count >= 1) {
- // 假如 URLPattern 为 a/b/c, components 就是 @"a.b.c" 正好可以作为 KVC 的 key
- NSString *components = [pathComponents componentsJoinedByString:@"."];
- NSMutableDictionary *route = [self.routes valueForKeyPath:components];
-
- if (route.count >= 1) {
- NSString *lastComponent = [pathComponents lastObject];
- [pathComponents removeLastObject];
-
- // 有可能是根 key,这样就是 self.routes 了
- route = self.routes;
- if (pathComponents.count) {
- NSString *componentsWithoutLast = [pathComponents componentsJoinedByString:@"."];
- route = [self.routes valueForKeyPath:componentsWithoutLast];
- }
- [route removeObjectForKey:lastComponent];
- }
- }
- }
- - (NSArray*)pathComponentsFromURL:(NSString*)URL
- {
- NSMutableArray *pathComponents = [NSMutableArray array];
- if ([URL rangeOfString:@"://"].location != NSNotFound) {
- NSArray *pathSegments = [URL componentsSeparatedByString:@"://"];
- // 如果 URL 包含协议,那么把协议作为第一个元素放进去
- [pathComponents addObject:pathSegments[0]];
-
- // 如果只有协议,那么放一个占位符
- URL = pathSegments.lastObject;
- if (!URL.length) {
- [pathComponents addObject:YM_ROUTER_WILDCARD_CHARACTER];
- }
- }
- for (NSString *pathComponent in [[NSURL URLWithString:URL] pathComponents]) {
- if ([pathComponent isEqualToString:@"/"]) continue;
- if ([[pathComponent substringToIndex:1] isEqualToString:@"?"]) break;
- [pathComponents addObject:pathComponent];
- }
- return [pathComponents copy];
- }
- - (NSMutableDictionary *)routes
- {
- if (!_routes) {
- _routes = [[NSMutableDictionary alloc] init];
- }
- return _routes;
- }
- #pragma mark - Utils
- + (BOOL)checkIfContainsSpecialCharacter:(NSString *)checkedString {
- NSCharacterSet *specialCharactersSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];
- return [checkedString rangeOfCharacterFromSet:specialCharactersSet].location != NSNotFound;
- }
- @end
|