YMRouter.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. //
  2. // YMRouter.m
  3. // MSYOUPAI
  4. //
  5. // Created by YoMi on 2023/11/8.
  6. //
  7. #import "YMRouter.h"
  8. #import <objc/runtime.h>
  9. static NSString * const YM_ROUTER_WILDCARD_CHARACTER = @"~";
  10. static NSString *specialCharacters = @"/?&.";
  11. NSString *const YMRouterParameterURL = @"YMRouterParameterURL";
  12. NSString *const YMRouterParameterCompletion = @"YMRouterParameterCompletion";
  13. NSString *const YMRouterParameterUserInfo = @"YMRouterParameterUserInfo";
  14. @interface YMRouter ()
  15. /**
  16. * 保存了所有已注册的 URL
  17. * 结构类似 @{@"beauty": @{@":id": {@"_", [block copy]}}}
  18. */
  19. @property (nonatomic) NSMutableDictionary *routes;
  20. @end
  21. @implementation YMRouter
  22. + (instancetype)sharedInstance
  23. {
  24. static YMRouter *instance = nil;
  25. static dispatch_once_t onceToken;
  26. dispatch_once(&onceToken, ^{
  27. instance = [[self alloc] init];
  28. });
  29. return instance;
  30. }
  31. + (void)registerURLPattern:(NSString *)URLPattern toHandler:(YMRouterHandler)handler
  32. {
  33. [[self sharedInstance] addURLPattern:URLPattern andHandler:handler];
  34. }
  35. + (void)deregisterURLPattern:(NSString *)URLPattern
  36. {
  37. [[self sharedInstance] removeURLPattern:URLPattern];
  38. }
  39. + (void)openURL:(NSString *)URL
  40. {
  41. [self openURL:URL completion:nil];
  42. }
  43. + (void)openURL:(NSString *)URL completion:(void (^)(id result))completion
  44. {
  45. [self openURL:URL withUserInfo:nil completion:completion];
  46. }
  47. + (void)openURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo completion:(void (^)(id result))completion
  48. {
  49. URL = [URL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  50. NSMutableDictionary *parameters = [[self sharedInstance] extractParametersFromURL:URL matchExactly:NO];
  51. [parameters enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
  52. if ([obj isKindOfClass:[NSString class]]) {
  53. parameters[key] = [obj stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  54. }
  55. }];
  56. if (parameters) {
  57. YMRouterHandler handler = parameters[@"block"];
  58. if (completion) {
  59. parameters[YMRouterParameterCompletion] = completion;
  60. }
  61. if (userInfo) {
  62. parameters[YMRouterParameterUserInfo] = userInfo;
  63. }
  64. if (handler) {
  65. [parameters removeObjectForKey:@"block"];
  66. handler(parameters);
  67. }
  68. }
  69. }
  70. + (BOOL)canOpenURL:(NSString *)URL
  71. {
  72. return [[self sharedInstance] extractParametersFromURL:URL matchExactly:NO] ? YES : NO;
  73. }
  74. + (BOOL)canOpenURL:(NSString *)URL matchExactly:(BOOL)exactly {
  75. return [[self sharedInstance] extractParametersFromURL:URL matchExactly:YES] ? YES : NO;
  76. }
  77. + (NSString *)generateURLWithPattern:(NSString *)pattern parameters:(NSArray *)parameters
  78. {
  79. NSInteger startIndexOfColon = 0;
  80. NSMutableArray *placeholders = [NSMutableArray array];
  81. for (int i = 0; i < pattern.length; i++) {
  82. NSString *character = [NSString stringWithFormat:@"%c", [pattern characterAtIndex:i]];
  83. if ([character isEqualToString:@":"]) {
  84. startIndexOfColon = i;
  85. }
  86. if ([specialCharacters rangeOfString:character].location != NSNotFound && i > (startIndexOfColon + 1) && startIndexOfColon) {
  87. NSRange range = NSMakeRange(startIndexOfColon, i - startIndexOfColon);
  88. NSString *placeholder = [pattern substringWithRange:range];
  89. if (![self checkIfContainsSpecialCharacter:placeholder]) {
  90. [placeholders addObject:placeholder];
  91. startIndexOfColon = 0;
  92. }
  93. }
  94. if (i == pattern.length - 1 && startIndexOfColon) {
  95. NSRange range = NSMakeRange(startIndexOfColon, i - startIndexOfColon + 1);
  96. NSString *placeholder = [pattern substringWithRange:range];
  97. if (![self checkIfContainsSpecialCharacter:placeholder]) {
  98. [placeholders addObject:placeholder];
  99. }
  100. }
  101. }
  102. __block NSString *parsedResult = pattern;
  103. [placeholders enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL * _Nonnull stop) {
  104. idx = parameters.count > idx ? idx : parameters.count - 1;
  105. parsedResult = [parsedResult stringByReplacingOccurrencesOfString:obj withString:parameters[idx]];
  106. }];
  107. return parsedResult;
  108. }
  109. + (id)objectForURL:(NSString *)URL withUserInfo:(NSDictionary *)userInfo
  110. {
  111. YMRouter *router = [YMRouter sharedInstance];
  112. URL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  113. NSMutableDictionary *parameters = [router extractParametersFromURL:URL matchExactly:NO];
  114. YMRouterObjectHandler handler = parameters[@"block"];
  115. if (handler) {
  116. if (userInfo) {
  117. parameters[YMRouterParameterUserInfo] = userInfo;
  118. }
  119. [parameters removeObjectForKey:@"block"];
  120. return handler(parameters);
  121. }
  122. return nil;
  123. }
  124. + (id)objectForURL:(NSString *)URL
  125. {
  126. return [self objectForURL:URL withUserInfo:nil];
  127. }
  128. + (void)registerURLPattern:(NSString *)URLPattern toObjectHandler:(YMRouterObjectHandler)handler
  129. {
  130. [[self sharedInstance] addURLPattern:URLPattern andObjectHandler:handler];
  131. }
  132. - (void)addURLPattern:(NSString *)URLPattern andHandler:(YMRouterHandler)handler
  133. {
  134. NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
  135. if (handler && subRoutes) {
  136. subRoutes[@"_"] = [handler copy];
  137. }
  138. }
  139. - (void)addURLPattern:(NSString *)URLPattern andObjectHandler:(YMRouterObjectHandler)handler
  140. {
  141. NSMutableDictionary *subRoutes = [self addURLPattern:URLPattern];
  142. if (handler && subRoutes) {
  143. subRoutes[@"_"] = [handler copy];
  144. }
  145. }
  146. - (NSMutableDictionary *)addURLPattern:(NSString *)URLPattern
  147. {
  148. NSArray *pathComponents = [self pathComponentsFromURL:URLPattern];
  149. NSMutableDictionary* subRoutes = self.routes;
  150. for (NSString* pathComponent in pathComponents) {
  151. if (![subRoutes objectForKey:pathComponent]) {
  152. subRoutes[pathComponent] = [[NSMutableDictionary alloc] init];
  153. }
  154. subRoutes = subRoutes[pathComponent];
  155. }
  156. return subRoutes;
  157. }
  158. #pragma mark - Utils
  159. - (NSMutableDictionary *)extractParametersFromURL:(NSString *)url matchExactly:(BOOL)exactly
  160. {
  161. NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
  162. parameters[YMRouterParameterURL] = url;
  163. NSMutableDictionary* subRoutes = self.routes;
  164. NSArray* pathComponents = [self pathComponentsFromURL:url];
  165. BOOL found = NO;
  166. // borrowed from HHRouter(https://github.com/Huohua/HHRouter)
  167. for (NSString* pathComponent in pathComponents) {
  168. // 对 key 进行排序,这样可以把 ~ 放到最后
  169. NSArray *subRoutesKeys =[subRoutes.allKeys sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
  170. return [obj1 compare:obj2];
  171. }];
  172. for (NSString* key in subRoutesKeys) {
  173. if ([key isEqualToString:pathComponent] || [key isEqualToString:YM_ROUTER_WILDCARD_CHARACTER]) {
  174. found = YES;
  175. subRoutes = subRoutes[key];
  176. break;
  177. } else if ([key hasPrefix:@":"]) {
  178. found = YES;
  179. subRoutes = subRoutes[key];
  180. NSString *newKey = [key substringFromIndex:1];
  181. NSString *newPathComponent = pathComponent;
  182. // 再做一下特殊处理,比如 :id.html -> :id
  183. if ([self.class checkIfContainsSpecialCharacter:key]) {
  184. NSCharacterSet *specialCharacterSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];
  185. NSRange range = [key rangeOfCharacterFromSet:specialCharacterSet];
  186. if (range.location != NSNotFound) {
  187. // 把 pathComponent 后面的部分也去掉
  188. newKey = [newKey substringToIndex:range.location - 1];
  189. NSString *suffixToStrip = [key substringFromIndex:range.location];
  190. newPathComponent = [newPathComponent stringByReplacingOccurrencesOfString:suffixToStrip withString:@""];
  191. }
  192. }
  193. parameters[newKey] = newPathComponent;
  194. break;
  195. } else if (exactly) {
  196. found = NO;
  197. }
  198. }
  199. // 如果没有找到该 pathComponent 对应的 handler,则以上一层的 handler 作为 fallback
  200. if (!found && !subRoutes[@"_"]) {
  201. return nil;
  202. }
  203. }
  204. // Extract Params From Query.
  205. NSArray<NSURLQueryItem *> *queryItems = [[NSURLComponents alloc] initWithURL:[[NSURL alloc] initWithString:url] resolvingAgainstBaseURL:false].queryItems;
  206. for (NSURLQueryItem *item in queryItems) {
  207. parameters[item.name] = item.value;
  208. }
  209. if (subRoutes[@"_"]) {
  210. parameters[@"block"] = [subRoutes[@"_"] copy];
  211. }
  212. return parameters;
  213. }
  214. - (void)removeURLPattern:(NSString *)URLPattern
  215. {
  216. NSMutableArray *pathComponents = [NSMutableArray arrayWithArray:[self pathComponentsFromURL:URLPattern]];
  217. // 只删除该 pattern 的最后一级
  218. if (pathComponents.count >= 1) {
  219. // 假如 URLPattern 为 a/b/c, components 就是 @"a.b.c" 正好可以作为 KVC 的 key
  220. NSString *components = [pathComponents componentsJoinedByString:@"."];
  221. NSMutableDictionary *route = [self.routes valueForKeyPath:components];
  222. if (route.count >= 1) {
  223. NSString *lastComponent = [pathComponents lastObject];
  224. [pathComponents removeLastObject];
  225. // 有可能是根 key,这样就是 self.routes 了
  226. route = self.routes;
  227. if (pathComponents.count) {
  228. NSString *componentsWithoutLast = [pathComponents componentsJoinedByString:@"."];
  229. route = [self.routes valueForKeyPath:componentsWithoutLast];
  230. }
  231. [route removeObjectForKey:lastComponent];
  232. }
  233. }
  234. }
  235. - (NSArray*)pathComponentsFromURL:(NSString*)URL
  236. {
  237. NSMutableArray *pathComponents = [NSMutableArray array];
  238. if ([URL rangeOfString:@"://"].location != NSNotFound) {
  239. NSArray *pathSegments = [URL componentsSeparatedByString:@"://"];
  240. // 如果 URL 包含协议,那么把协议作为第一个元素放进去
  241. [pathComponents addObject:pathSegments[0]];
  242. // 如果只有协议,那么放一个占位符
  243. URL = pathSegments.lastObject;
  244. if (!URL.length) {
  245. [pathComponents addObject:YM_ROUTER_WILDCARD_CHARACTER];
  246. }
  247. }
  248. for (NSString *pathComponent in [[NSURL URLWithString:URL] pathComponents]) {
  249. if ([pathComponent isEqualToString:@"/"]) continue;
  250. if ([[pathComponent substringToIndex:1] isEqualToString:@"?"]) break;
  251. [pathComponents addObject:pathComponent];
  252. }
  253. return [pathComponents copy];
  254. }
  255. - (NSMutableDictionary *)routes
  256. {
  257. if (!_routes) {
  258. _routes = [[NSMutableDictionary alloc] init];
  259. }
  260. return _routes;
  261. }
  262. #pragma mark - Utils
  263. + (BOOL)checkIfContainsSpecialCharacter:(NSString *)checkedString {
  264. NSCharacterSet *specialCharactersSet = [NSCharacterSet characterSetWithCharactersInString:specialCharacters];
  265. return [checkedString rangeOfCharacterFromSet:specialCharactersSet].location != NSNotFound;
  266. }
  267. @end