# iOS 组件化-路由解耦思想 JLRoutes 实战篇(一)App内控制器跳转

简介:

Router

前言

组件化, Router 这些概念可能在几年前还是比较新颖的概念, 至今相信绝大多数同学都对这些名词已耳熟能详, 笔者在真正接触到 Router 并在项目使用之前, 也有读过一些 组件化, Router 进行解耦的思想和框架的文章, 但是由于自己修行不够, 加上没能真正将其运用到项目进行实践。 导致每次读完文章之后, 所理解的知识没能真正转化为可以解决问题的技能, 笔者有幸在项目中接触并运用著名开源库 JLRouter 来解决 App 内外所有页面之间的跳转逻辑, 经过这几年的学习和使用, 将其记录一边巩固知识, 写出来跟大家一起学习, 加上看到网上分享关于使用组件化-Router 相关文章偏于理论, 很少有完整详细Demo, 具体在项目中使用还需进一步深入研究, 所以有了此篇文章, 有什么不对或需要补充的, 望大家多多指教。
此篇文章偏向实战, 想深入学习 Router 思想的推荐霜神写的 iOS 组件化 —— 路由设计思路分析

==Demo 在文章最👇==

为什么 Router

路由基础三问, 每次接触新颖思想框架时, 我都会不禁的问自己这几个问题, 希望通过下面几个简要的概括, 能很好的帮助大家理解 Router;

  • 路由是什么,解决了什么问题

    引用霜神Blog 图片
    上面一幅图很形象的展示了项目中各个控制器模块之间错综复杂的关系, 当我们在处理不当的情况下可能更加糟糕.
    使用 Router 之后大概是这样的;
    Router
    打个比方, Router 就是跟我们日常使用的路由器一样, App 内每个控制器可以想象成已经连接了这台路由器的不同设备, 当然连接路由器时, 一般需要输入密码, Router 同样的, 使用前需要每台设备进行一次注册, Router 在内部保存每台设备的 URL, 不同设备之间需要交互时, 将消息发送到路由器中统一处理;
    当控制器之间需要交互跳转时, 只需要将对应的 URL 地址发送到 Router 里, Router 根据其注册的 URL 来寻址到对方信息, 然后负责实例化对象, 并传参, 进行跳转等工作, 各个控制器之间不需要相互依赖对方, 完美解决不同模块之间耦合!

  • 为什么要用路由来实现 VC 跳转
    Router 能做的事情很多, 首先我们用它来解决棘手的控制器耦合关系,是一种非常有效的解决方案;
    在 App 中控制器跳转普遍分为 3 种, 模态跳转Modal(presented/dismiss), 导航控制器跳转(Push/pop), Storyboard 跳转(Segue), 还有 UITabBarVC 主控制器 Index 切换;
    除了常规的控制器之间跳转之外, 还会有 3D Touch 指定跳转到某个控制器中;
    App 之间跳转: URL Scheme, Universal Links方式;
    可想而知 App 内不管是页面切换, 外部调用, 都会涉及到控制器的跳转, 切换等等;
    下面引用常见场景来举个栗子:
    Router 前 伪代码:

    假如在没有引入 Router 之前, 实现 A Push B, B Modal C 的场景: 一般做法都是在 A 中引入B, B 中引入 C, 然后在每次跳转前都需要来一段硬编码,

    //A Push B   A 页面跳转至 B页面, 并且设置相应 @perpeoty, callback 等;
    #import "B"
    B* BVC = [B new]; 
    BVC.delegate = A;
    BVC.name = @"jersey";
    BVC.callback = ^ (id data) {
    };
    ... 
    ...
    ... 对 b 设置一些业务相关参数, delegate,  callback 等等;
    [A.nav pushVC: BVC animation: true]; 
    // B -> C 
    #import "C"
    C* CVC = [C new];
    [B presentVC: CVC];
    [B presentVC: CVC animation: true completion: nil];
    

    ==Router 后 伪代码:==
    在引用了 Router 之后, 相同的场景下, 我们的代码是这样的; 在需要做跳转的控制器引入我们封装好的 ==JSDVCRouter(是针对 JLRouter 进行的一层封装, 专门用于管理 App 跳转的类, 在文章后面会详细讲解)== 即可.

      // A Push B;
      #import "JSDVCRouter"
      [JSDVCRouter openURL: BVCPath info: @{@"delegate":self,@"name":@"jersey",@"callback":callback}];    
      // BVCPath: 表示我们对 B 控制器定义的路径, 一般保存在全局 Map 里面, 每个 Path 映射当前控制器 Map 包含相关 title, class, needLog, 等参数;
      // B Modal C
      [JSDVCRouter openURL: C info: {kJSDRouteSegue: @"Modal"}]; // 控制器之间跳转默认以 Push 实现, 当需要 Modal 时, 则传递一个参数;
    

    看到这里相信认真阅读的同学们已看出使用 Router 的好处:

    1. 耦合度降低: A 控制器不需要知道 B 控制器的存在, 只需要 import "JSDRouter", 由其去进行相应跳转逻辑, 以及赋值等等;
    2. 代码阅读性提高: 当然在刚刚接触时, 看着会不大不习惯, 等接触一段时间之后, 不仅减少了代码行数, 同时可读性还是很高的, 跟 push/pop, present/dismiss 说再见吧;
    3. 提高代码复用性: 每次控制器之间跳转和赋值等操作, 都需要重复性的 code 一次(严重违背了: 可复用性原则), 通过 JSDRouter 将跳转和赋值等逻辑封装起来, 一次 code, 终生受用;
    4. 易于维护: 写到这一点有点儿纠结, 当项目随着公司规模不断壮大时, 控制器数量, 跳转变得越加复杂, 跳转方法和逻辑很容易变得越来越混乱, 后期管理起来比较困难。 使用 JSDVCRouter 单一职责的原则来专门负责 App 内所有的跳转, 能非常有效的提高测试及后期维护, 当然成本是需要维护 RouterMap 同时完善 JSDVCRouter 内部逻辑;
    5. 动态化及灵活性: 使用 Router 时可以配合后台响应传递响应的 Key 来决定真正跳转的页面, 而不是硬编码的方式来进行跳转;
    6. 待补充:
  • 实现 Router 完成控制器跳转, 至少需要几个步骤?
    首次将控制器跳转转成 Router 方案
    很简单只有 3个步骤, 如何需求变动不大的话, 几乎一劳永逸;

  1. Map 表创建: 其是一个全局 Map, App 内相应的控制器定义好 Path, Router 可以根据 Path 映射相应控制器制定的 Map 内, Map 里面最少包含当前控制器的参数如: {@"Class": @"控制器类名"}。相当于调用这个路由时,得到一组其绑定的 Map 作为参数, 通过 Class 来初始化实例;
    代码结构如:
      + (NSDictionary *)configInfo
         return @{ JSDRouteHomeCenter: @{
                      @"class": @"JSDAHomeCenterVC",
                      @"name": @"首页",
                      @"parameter": @"",
                      @"needLogin": @"0", },
                   JSDRouteUserLogin: @{
                      @"class": @"JSDUserLoginVC",
                      @"name": @"登陆",
                      @"parameter": @"",
                      @"needLogin": @"0", },
         };
    
  2. 封装 JLRouter; 为了方便使用,管理,以及后期迁移等!类似使用 AFNetwork, SDWebImage, MJRefresh 等有名的开源库一样, 由于开源库提供功能非常丰富, 但是可能我们实际使用到的只是它一两个主要的功能来解决项目中存在的问题, 大家都会根据公司具体的业务场景或者使用习惯, 来对其进行一层甚至多层封装一样, 使其能更加适合实际要求;
    笔者对其进行了一层封装 + Category 的形式: JSDVCRouter,JSDVCRouter + Add;
    JSDVCRouter: 主要用于声明 Router 调用接口;
    JSDVCRouter + Handle: 主要用于实现 Router 注册, 处理控制器之间跳转和参数赋值代码;
  3. 根据约定 Path 进行跳转: 上面 1 2 都准备好之后, 即可轻松的进行控制器跳转 [JSDVCRouter openURL:BVC];

业务变更后期维护

  1. Map 维护: 随着业务发展, 当有新的页面加入时, 对 Map 添加一个指定的 Path 和绑定的相应参数;
  2. JSDVCRouter 维护: 其包含着真正对控制器初始化跳转和赋值的代码这里一般很少进行修改; 比如后期需支持跳转到 H5, 处理 3D Touch, Universal Links 时来这里进行维护;

实战 Code!

写到这里, 笔者不知道上面讲的对 Router 实现控制器跳转的简要介绍, 是否起到帮助初步接触 Router 时的同学们, 希望下面通过 Code 的方式能让大家更好的理解和使用起来!
下面详细介绍笔者封装 JLRoutes 实现控制器跳转的三个类:

JSDVCRouterConfig

这个文件主要用于管理所有 Router 映射到指定控制器类名(class), 以及相关参数的配置文件(title,needLogin等), 具体配置根据实际项目需求进行即可;

  1. 为了编译期能更好的检查到错误, 使用 extern NSString const 声明, 配合 NSString const 实现指定 Router URL, 使用的时候直接通过外部声明的常量字符串来指定跳转即可;
  2. 这样管理 Router URL 能更加方便阅读和维护, 如果直接使用 @"/login" 的方式来进行绑定可读性差, 很容易出现粗心大意导致的错误;

代码如下:

    //App 内所有控制器
    extern NSString* const JSDVCRouteWebview;
    extern NSString* const JSDVCRouteLogin;
    @interface JSDVCRouterConfig : NSObject

    + (NSDictionary *)configMapInfo;

    @end

    //App 内相关控制器
    NSString* const JSDVCRouteWebview = @"/webView";
    NSString* const JSDVCRouteLogin = @"/login";
    @implementation JSDVCRouterConfig

    + (NSDictionary *)configMapInfo {

    return @{
        JSDVCRouteWebview: @{@"class": @"JSDWebViewVC",
                             @"title": @"WebView",
                             @"flags": @"",
                             @"needLogin": @"",
        },
        JSDVCRouteLogin: @{@"class": @"JSDLoginVC",
                           @"title": @"登录",
                           @"flags": @"",
                           @"needLogin": @"",
        },
        };
    @end
JSDVCRouter

这个类内部实现的事情非常简单, 继承自 NSObject, 对外提供 注册和调用 Router 接口, 在内部调用 JLRoutes 提供的接口;

在项目中所有跳转均使用此类提供的接口来调用 Router;
一个是默认不带任何参数
另一个可以携带我们需要的参数(NSDictionary);

[JSDVCRouter openURL:JSDVCRouteAppear]; //push 到 AppearVC; 
[JSDVCRouter openURL:JSDVCRouteAppear parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"name": @"jersey"}]; // Modal 到 Appear VC 并携带参数 name;

单独封装一个 JSDVCRouter 好处:

防止三方库入侵. 其继承自 NSObject 并不直接依赖于 JLRouter, 这样在后期如果考虑更换三方库, 或者自己封装一套类似 JLRouter 提供的功能时, 只需要对其修改即可, 其他地方均无需修改;
接口隔离保持统一, 可读性更高;

@interface JSDVCRouter : NSObject

    + (BOOL)openURL:(NSString *)url;//调用 Router;
    + (BOOL)openURL:(NSString *)url parameters:(NSDictionary *)parameters;

    + (void)addRoute:(NSString* )route handler:(BOOL (^)(NSDictionary *parameters))handlerBlock;//注册 Router,调用 Router 时会触发回调; 

    @end
    #define JSDRouterURL(string) [NSURL URLWithString:string]

    @implementation JSDVCRouter

    + (BOOL)openURL:(NSString *)url {

        return [self routeURL:url parameters:nil];
    }

    + (BOOL)openURL:(NSString *)url parameters:(NSDictionary *)parameters {

       return [self routeURL:url parameters:parameters];
    }

    + (void)addRoute:(NSString *)route handler:(BOOL (^)(NSDictionary * _Nonnull parameters))handlerBlock {

        [JLRoutes addRoute:route handler:handlerBlock];
    }

    #pragma mark - mark JLRouter

    + (BOOL)routeURL:(NSString*)url parameters:(NSDictionary *)parameters{

        return [JLRoutes routeURL:JSDRouterURL(url) withParameters:parameters];
    }

    @end

JSDVCRouter+Handle

真正注册和调用 Router 时处理回调控制器跳转和参数赋值逻辑实现放在这里。

注册 Router : 对控制器内所有 Router 一一进行注册以及 TabBarIndex 切换和 处理返回 Router, 将回调统一转发到定义的方法里头。

处理 Router: 也就是注册好 Router 之后, 调用相应 Router 时, 我们在注册时写得回调方法, 这里是执行控制器跳转和传参的逻辑。

关于控制器跳转: 在触发 Router 时, 我们能拿到 Router 映射到的 Map, 获取到其 Class, 在通过 Class 来进行初始初始化实例, 这里通过对 UIViewController Category 找到当前 visibleVC 来进行 Push 或 Modal, 我们也可以根据业务方传递过来的参数来决定进行 Push 或 Modal 以及是否需要执行动画等等;

关于传参: 传递过来的参数是字典的数据结构, 所以我们先检测实例 VC 是否包含这个属性, [vc respondsToSelector:NSSelectorFromString(key)], 如果 VC 有这个属性则直接使用 KVC 的方式来进行赋值, 为了防止在开发时, 传入的字典 Key 与 VC 属性不匹配导致一些 Bug, 添加一层 NSAssert,这样能在开发过程中更快找到问题!

笔者自行封装的控制器跳转逻辑可能有考虑不周的地方, 主要还得根据具体业务需求来做具体判断;

下面分别是注册 Router 和匹配到 Router 之后回调处理代码, 有点长请耐心阅读
Router 注册, 将三种类型回调处理统一

    @implementation JSDVCRouter (Handle)
    //注册 Router,  控制器的跳转 + UITabBarIndex 切换 + 页面返回
    + (void)load {
        [self performSelectorOnMainThread:@selector(registerRouter) withObject:nil waitUntilDone:false];
    }
    + (void)registerRouter {

    //获取全局 RouterMapInfo
    NSDictionary* routerMapInfo = [JSDVCRouterConfig configMapInfo];

    // router 对应控制器路径, 使用其来注册 Route, 当调用当前 Route 时会执行回调; 回调参数 parameters: 在执行 Route 时传入的参数;
    for (NSString* router in routerMapInfo.allKeys) {

        NSDictionary* routerMap = routerMapInfo[router];
        NSString* className = routerMap[kJSDVCRouteClassName];
        if (JSDIsString(className)) {
            /*注册所有控制器 Router, 使用 [JSDVCRouter openURL:JSDVCRouteAppear]; push 到 AppearVC;
            [JSDVCRouter openURL:JSDVCRouteAppear parameters:@{kJSDVCRouteSegue: kJSDVCRouteSegueModal, @"name": @"jersey"}];  Modal 到 Appear VC 并携带参数 name;
             */
            [self addRoute:router handler:^BOOL(NSDictionary * _Nonnull parameters) {
                //执行路由匹配成功之后,跳转逻辑回调;
                /*执行 Route 回调; 处理控制器跳转 + 传参;
                ** routerMap: 当前 route 映射的  routeMap; 我们在 RouterConfig 配置的 Map;
                ** parameters: 调用 route 时, 传入的参数;
                 */
                return [self executeRouterClassName:className routerMap:routerMap parameters:parameters];
            }];
        }
    }

    // 注册 Router 到指定TabBar Index; 使用 [JSDVCRouter openURL:JSDVCRouteCafeTab] 切换到 Cafe Index
    [self addRoute:@"/rootTab/:index" handler:^BOOL(NSDictionary * _Nonnull parameters) {
        NSInteger index = [parameters[@"index"] integerValue];
        // 处理 UITabBarControllerIndex 切换;
        UITabBarController* tabBarVC = (UITabBarController* )[UIViewController jsd_rootViewController];
        if ([tabBarVC isKindOfClass:[UITabBarController class]] && index >= 0 && tabBarVC.viewControllers.count >= index) {
            UIViewController* indexVC = tabBarVC.viewControllers[index];
            if ([indexVC isKindOfClass:[UINavigationController class]]) {
                indexVC = ((UINavigationController *)indexVC).topViewController;
            }
            //传参
            [self setupParameters:parameters forViewController:indexVC];
            tabBarVC.selectedIndex = index;
            return YES;
        } else {
            return NO;
        }
    }];
    // 注册返回上层页面 Router, 使用 [JSDVCRouter openURL:kJSDVCRouteSegueBack] 返回上一页 或 [JSDVCRouter openURL:kJSDVCRouteSegueBack parameters:@{kJSDVCRouteBackIndex: @(2)}]  返回前两页
    [self addRoute:kJSDVCRouteSegueBack handler:^BOOL(NSDictionary * _Nonnull parameters) {

        return [self executeBackRouterParameters:parameters];
    }];
    }

Router 匹配到之后回调: 实例化控制器, 参数赋值, 页面跳转

#pragma mark - execute Router VC    
// 当查找到指定 Router 时, 触发路由回调逻辑; 找不到已注册 Router 则直接返回 NO; 如需要的话, 也可以在这里注册一个全局未匹配到 Router 执行的回调进行异常处理;
+ (BOOL)executeRouterClassName:(NSString *)className routerMap:(NSDictionary* )routerMap parameters:(NSDictionary* )parameters {
    // 拦截 Router 映射参数,是否需要登录才可跳转;
    BOOL needLogin = [routerMap[kJSDVCRouteClassNeedLogin] boolValue];
    if (needLogin && !userIsLogin) {
        [JSDVCRouter openURL:JSDVCRouteLogin];
        return NO;
    }
    //统一初始化控制器,传参和跳转;
    UIViewController* vc = [self viewControllerWithClassName:className routerMap:routerMap parameters: parameters];
    if (vc) {
        [self gotoViewController:vc parameters:parameters];
        return YES;
    } else {
        return NO;
    }
}
// 根据 Router 映射到的类名实例化控制器;
+ (UIViewController *)viewControllerWithClassName:(NSString *)className routerMap:(NSDictionary *)routerMap parameters:(NSDictionary* )parameters {

    id vc = [[NSClassFromString(className) alloc] init];
    if (![vc isKindOfClass:[UIViewController class]]) {
        vc = nil;
    }
#if DEBUG
    //vc不是UIViewController
    NSAssert(vc, @"%s: %@ is not kind of UIViewController class, routerMap: %@",__func__ ,className, routerMap);
#endif
    //参数赋值
    [self setupParameters:parameters forViewController:vc];

    return vc;
}
// 对 VC 参数赋值
+ (void)setupParameters:(NSDictionary *)params forViewController:(UIViewController* )vc {

    for (NSString *key in params.allKeys) {
        BOOL hasKey = [vc respondsToSelector:NSSelectorFromString(key)];
        BOOL notNil = params[key] != nil;
        if (hasKey && notNil) {
            [vc setValue:params[key] forKey:key];
        }

#if DEBUG
    //vc没有相应属性,但却传了值
        if ([key hasPrefix:@"JLRoute"]==NO &&
            [key hasPrefix:@"JSDVCRoute"]==NO && [params[@"JLRoutePattern"] rangeOfString:[NSString stringWithFormat:@":%@",key]].location==NSNotFound) {
            NSAssert(hasKey == YES, @"%s: %@ is not property for the key %@",__func__ ,vc,key);
        }
#endif
    };
}
// 跳转和参数设置;
+ (void)gotoViewController:(UIViewController *)vc parameters:(NSDictionary *)parameters {

    UIViewController* currentVC = [UIViewController jsd_findVisibleViewController];
    NSString *segue = parameters[kJSDVCRouteSegue] ? parameters[kJSDVCRouteSegue] : kJSDVCRouteSeguePush; //  决定 present 或者 Push; 默认值 Push
    BOOL animated = parameters[kJSDVCRouteAnimated] ? [parameters[kJSDVCRouteAnimated] boolValue] : YES;  // 转场动画;
    NSLog(@"%s 跳转: %@ %@ %@",__func__ ,currentVC, segue,vc);

    if ([segue isEqualToString:kJSDVCRouteSeguePush]) { //PUSH
        if (currentVC.navigationController) {
            NSString *backIndexString = [NSString stringWithFormat:@"%@",parameters[kJSDVCRouteBackIndex]];
            UINavigationController* nav = currentVC.navigationController;
            if ([backIndexString isEqualToString:kJSDVCRouteIndexRoot]) {
                NSMutableArray *vcs = [NSMutableArray arrayWithObject:nav.viewControllers.firstObject];
                [vcs addObject:vc];
                [nav setViewControllers:vcs animated:animated];

            } else if ([backIndexString integerValue] && [backIndexString integerValue] < nav.viewControllers.count) {
                //移除掉指定数量的 VC, 在Push;
                NSMutableArray *vcs = [nav.viewControllers mutableCopy];
                [vcs removeObjectsInRange:NSMakeRange(vcs.count - [backIndexString integerValue], [backIndexString integerValue])];
                nav.viewControllers = vcs;
                [nav pushViewController:vc animated:YES];
            } else {
                [nav pushViewController:vc animated:animated];
            }
        }
        else { //由于无导航栏, 直接执行 Modal
            BOOL needNavigation = parameters[kJSDVCRouteSegueNeedNavigation] ? NO : YES;
            if (needNavigation) {
                UINavigationController* navigationVC = [[UINavigationController alloc] initWithRootViewController:vc];
                //vc.modalPresentationStyle = UIModalPresentationFullScreen;
                [currentVC presentViewController:navigationVC animated:YES completion:nil];
            }
            else {
                //vc.modalPresentationStyle = UIModalPresentationFullScreen;
                [currentVC presentViewController:vc animated:animated completion:nil];
            }
        }
    }
    else { //Modal
        BOOL needNavigation = parameters[kJSDVCRouteSegueNeedNavigation] ? parameters[kJSDVCRouteSegueNeedNavigation] : NO;
        if (needNavigation) {
            UINavigationController* navigationVC = [[UINavigationController alloc] initWithRootViewController:vc];
            //vc.modalPresentationStyle = UIModalPresentationFullScreen;
            [currentVC presentViewController:navigationVC animated:animated completion:nil];
        }
        else {
            //vc.modalPresentationStyle = UIModalPresentationFullScreen;
            [currentVC presentViewController:vc animated:animated completion:nil];
        }
    }
}

能坚持看到这里, 应该对 Router 进行控制器跳转已经有了个不错的理解!

待补充

App 内部跳转除了, 频繁的控制器之间切换外, 还有比如跳转到 H5, 或者跳转到 WebView 等;
App 外跳转则包含 Scheme 启动, 3D Touch, UniversalLink, 点击通知等都会触发;
这些包含跳转, 页面切换的我们均可以统一使用 Router 来进行有效的管理, 使 App 变得更加动态化, 模块之间耦合度更低;

  • [ ] 支持 H5 跳转
  • [ ] 外部 Scheme 启动 App
  • [ ] UniversalLink
  • [ ] 3D Touch Shortcut
  • [ ] 支持后台动态下发 RouterMap 配置

最后

希望此篇文章对您有所帮助,如有不对的地方,希望大家能留言指出纠正。
emmmmm,每次看到这么长一串代码, 要么是直接跳过, 要么就是认认真真看完之后马上启动 Xcode Coding 一遍, Commond + R 实践一遍, 为了方便大家理解献上
Demo 如果觉得对你有帮助, 麻烦大家给个 Star 谢谢😆!!!!!
学习的路上,与君共勉!!!

本文原创作者:Jersey. 欢迎转载,请注明出处和本文链接

参考-链接

iOS 组件化 —— 路由设计思路分析
iOS架构实践干货
iOS应用架构谈 组件化方案

实战Demo 如果觉得对你有帮助, 麻烦大家给个 Star 谢谢😆!!!!!

目录
相关文章
|
2月前
|
UED
|
25天前
|
传感器 iOS开发 UED
探索iOS生态系统:从App Store优化到用户体验提升
本文旨在深入探讨iOS生态系统的多个方面,特别是如何通过App Store优化(ASO)和改进用户体验来提升应用的市场表现。不同于常规摘要仅概述文章内容的方式,我们将直接进入主题,首先介绍ASO的重要性及其对开发者的意义;接着分析当前iOS平台上用户行为的变化趋势以及这些变化如何影响应用程序的设计思路;最后提出几点实用建议帮助开发者更好地适应市场环境,增强自身竞争力。
|
1月前
|
JSON JavaScript 前端开发
harmony-chatroom 自研纯血鸿蒙OS Next 5.0聊天APP实战案例
HarmonyOS-Chat是一个基于纯血鸿蒙OS Next5.0 API12实战开发的聊天应用程序。这个项目使用了ArkUI和ArkTS技术栈,实现了类似微信的消息UI布局、输入框光标处插入文字、emoji表情图片/GIF动图、图片预览、红包、语音/位置UI、长按语音面板等功能。
68 2
|
1月前
|
设计模式 Swift iOS开发
探索iOS开发:从基础到高级,打造你的第一款App
【10月更文挑战第40天】在这个数字时代,掌握移动应用开发已成为许多技术爱好者的梦想。本文将带你走进iOS开发的世界,从最基础的概念出发,逐步深入到高级功能实现,最终指导你完成自己的第一款App。无论你是编程新手还是有志于扩展技能的开发者,这篇文章都将为你提供一条清晰的学习路径。让我们一起开始这段旅程吧!
|
2月前
|
JavaScript 小程序 开发者
uni-app开发实战:利用Vue混入(mixin)实现微信小程序全局分享功能,一键发送给朋友、分享到朋友圈、复制链接
uni-app开发实战:利用Vue混入(mixin)实现微信小程序全局分享功能,一键发送给朋友、分享到朋友圈、复制链接
456 0
|
3月前
|
小程序 开发工具
app跳转微信小程序,使用明文scheme拉起
app跳转微信小程序,使用明文scheme拉起
718 4
|
4月前
|
消息中间件 Java
【实战揭秘】如何运用Java发布-订阅模式,打造高效响应式天气预报App?
【8月更文挑战第30天】发布-订阅模式是一种消息通信模型,发送者将消息发布到公共队列,接收者自行订阅并处理。此模式降低了对象间的耦合度,使系统更灵活、可扩展。例如,在天气预报应用中,`WeatherEventPublisher` 类作为发布者收集天气数据并通知订阅者(如 `TemperatureDisplay` 和 `HumidityDisplay`),实现组件间的解耦和动态更新。这种方式适用于事件驱动的应用,提高了系统的扩展性和可维护性。
81 2
|
4月前
|
iOS开发
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
232 0
App备案与iOS云管理式证书 ,公钥及证书SHA-1指纹的获取方法
|
4月前
|
Android开发 iOS开发 C#
Xamarin:用C#打造跨平台移动应用的终极利器——从零开始构建你的第一个iOS与Android通用App,体验前所未有的高效与便捷开发之旅
【8月更文挑战第31天】Xamarin 是一个强大的框架,允许开发者使用单一的 C# 代码库构建高性能的原生移动应用,支持 iOS、Android 和 Windows 平台。作为微软的一部分,Xamarin 充分利用了 .NET 框架的强大功能,提供了丰富的 API 和工具集,简化了跨平台移动应用开发。本文通过一个简单的示例应用介绍了如何使用 Xamarin.Forms 快速创建跨平台应用,包括设置开发环境、定义用户界面和实现按钮点击事件处理逻辑。这个示例展示了 Xamarin.Forms 的基本功能,帮助开发者提高开发效率并实现一致的用户体验。
175 0

热门文章

最新文章

下一篇
DataWorks