Matrix

    Matrix.org 是什么?

    Matrix.org 是一个开放、去中心化的实时通讯协议。它的目标是打破现有通讯应用之间的壁垒,让用户可以在不同的平台之间自由、安全地交流,就像电子邮件协议(SMTP)使得不同邮件服务提供商之间的邮件互通成为可能一样。 核心特点:

    • 开放标准 (Open Standard): Matrix 协议是公开的,任何人都可以查看、使用和贡献。这促进了互操作性和创新。
    • 去中心化 (Decentralized): Matrix 没有中心服务器。用户可以选择加入由不同组织或个人运行的“家服务器 (homeserver)”。这些服务器可以相互连接(联邦),使得不同服务器上的用户可以进行通信。您甚至可以自己搭建一个家服务器,完全掌控自己的数据。
    • 端到端加密 (End-to-End Encryption): Matrix 支持可选的端到端加密,使用 Olm 和 Megolm 算法来保护用户的通信内容,确保只有参与对话的设备才能解密消息。
    • 桥接 (Bridging): Matrix 允许通过“桥 (bridge)”连接到其他流行的通讯平台(如 Slack、Discord、IRC、XMPP 等),使得用户可以在 Matrix 上与使用其他平台的朋友和同事交流,而无需让他们都切换到 Matrix。
    • 实时通讯 (Real-time Communication): Matrix 专为实时通讯设计,非常适合即时消息、语音通话、视频通话(通常通过集成 Jitsi 等服务实现)以及其他需要即时数据交换的应用。
    • 灵活可扩展 (Flexible and Extensible): Matrix 不仅限于聊天。它还可以用于构建各种实时协作应用,例如帮助台、社交媒体平台、物联网 (IoT) 通信等。
    • 客户端多样 (Diverse Clients): 有许多不同的 Matrix 客户端可供选择,包括官方的 Element(可在 Web、桌面和移动设备上使用),以及其他由社区开发的客户端,它们在用户界面和功能上各有特色。 Matrix.org 基金会 (The Matrix.org Foundation): Matrix.org 也是一个非营利性基金会的名称,该基金会负责维护 Matrix 协议的规范、开发核心组件(如 Synapse 家服务器和 Element 客户端),并推广 Matrix 生态系统。 您可以如何使用 Matrix?
    • 加入一个公共家服务器: 这是最简单的方式。您可以选择一个由 Matrix.org 或其他社区成员运营的公共服务器,并使用 Element 或其他 Matrix 客户端注册一个账号。
    • 搭建自己的家服务器: 如果您有技术能力,可以自己搭建并管理一个 Matrix 家服务器,完全掌控您的数据和通信。
    • 使用桥接连接到其他平台: 如果您的朋友或同事还在使用其他平台,您可以使用 Matrix 的桥接功能与他们进行交流。
    • 开发基于 Matrix 的应用: 由于 Matrix 是一个开放协议,开发者可以利用其 API 构建各种新的通讯和协作应用。 总而言之,Matrix.org 致力于构建一个开放、安全、互联的实时通讯网络,赋予用户更多的选择和控制权。它是一个正在不断发展壮大的生态系统,拥有强大的社区支持。

    iOS 开发者可以基于 Matrix 协议开发即时通讯应用,这主要涉及到使用 Matrix 提供的 iOS SDK (Software Development Kit)。以下是开发过程中的关键步骤和相关信息:

    1. 了解 Matrix iOS SDK:
    • 官方 SDK: Matrix.org 维护着一个官方的 iOS SDK,通常仓库名为 matrix-ios-sdk。您可以在 GitHub 上找到它 (https://github.com/matrix-org/matrix-ios-sdk).
    • Swift 封装: 社区也维护了一些对官方 SDK 的 Swift 封装,例如 MatrixSDK (https://github.com/niochat/MatrixSDK). 这些封装可能提供更符合 Swift 语言习惯的 API。
    • 功能: Matrix iOS SDK 封装了与 Matrix Client-Server API 的交互,提供了以下核心功能:
      • 连接到家服务器 (Homeserver): 用户注册、登录、登出等。
      • 管理会话 (Session): 处理用户会话数据。
      • 房间 (Room) 操作: 创建、加入、离开房间,获取房间信息。
      • 消息 (Message) 收发: 发送和接收文本、图片、视频等各种类型的消息。
      • 用户 (User) 管理: 获取用户信息、修改个人资料等。
      • 状态 (State) 同步: 监听和更新房间和用户的状态。
      • 端到端加密 (End-to-End Encryption): 集成了 Olm 和 Megolm 加密算法,用于实现安全的私密通信。
      • VoIP (Voice over IP) / 视频通话 (Video Call): 虽然核心 SDK 可能不直接包含 UI,但会提供底层 API 来集成 VoIP 和视频通话功能(通常会依赖 WebRTC 等技术)。
      • 桥接 (Bridging) 支持: 允许与桥接的第三方服务进行交互。
    1. 集成 SDK 到您的 iOS 项目:
    • 您可以使用 Swift Package Manager (SPM) 或 CocoaPods 将 Matrix iOS SDK 集成到您的 Xcode 项目中。
      • Swift Package Manager: 在 Xcode 中,选择 File -> Add Packages…,然后输入 SDK 的 GitHub 仓库 URL。
      • CocoaPods: 在您的 Podfile 中添加相应的 pod 依赖(例如 pod ‘MatrixSDK’),然后在终端运行 pod install。
    1. 实现用户界面 (UI):
    • Matrix SDK 本身通常不包含完整的 UI 组件。您需要使用 UIKit 或 SwiftUI 来构建您的应用程序界面,例如:
      • 登录/注册界面: 允许用户创建或登录 Matrix 账号。
      • 房间列表界面: 显示用户加入的房间列表。
      • 聊天界面: 显示房间内的消息,并允许用户发送消息。
      • 用户列表/联系人界面: 显示用户列表或联系人。
      • 设置界面: 允许用户管理账号、通知、加密等设置。
    1. 使用 SDK API 进行功能开发:
    • 在您的 UI 代码中,您将使用 SDK 提供的类和方法来实现即时通讯功能。这包括:
      • 初始化和配置 SDK: 创建 MXSession 实例并连接到指定的家服务器。
      • 处理用户认证: 调用 SDK 的登录和注册方法。
      • 监听房间和消息事件: 使用 SDK 提供的委托 (delegate) 或回调 (closure) 机制来接收新的消息、房间状态更新等事件,并更新您的 UI。
      • 发送消息: 调用 SDK 的方法来发送文本、图片、视频等消息到指定的房间。
      • 处理加密: 如果需要端到端加密,您需要使用 SDK 提供的加密相关 API 来设置和管理密钥,并确保消息在发送和接收时进行加密和解密。
      • 实现 VoIP/视频通话: 如果您的应用需要语音或视频通话功能,您可能需要集成额外的库(如 WebRTC)并结合 Matrix SDK 的 API 来建立和管理通话会话。
    1. 测试和调试:
    • 在开发过程中,您需要进行充分的测试,以确保您的应用能够正确地连接到 Matrix 网络,收发消息,处理加密等功能。
    • 可以使用 Matrix 客户端(如 Element)来验证您的应用是否能够与其他 Matrix 用户正常通信。 关键考虑因素:
    • 选择家服务器: 您的应用需要让用户选择一个 Matrix 家服务器进行连接。您可以提供一个默认的建议,或者允许用户自定义。
    • 用户体验 (UX): 设计直观易用的用户界面对于应用的成功至关重要。
    • 安全性: 正确地实现和管理端到端加密对于保护用户隐私至关重要。务必仔细阅读 SDK 的加密相关文档。
    • 性能: 考虑在用户数量多或消息量大的场景下的性能优化。
    • 推送通知: 为了在应用不在前台运行时接收新消息通知,您需要集成推送通知服务。Matrix 生态系统中有相关的解决方案,但 iOS 的推送通知依赖于苹果的 APNs 服务。
    • 桥接集成 (可选): 如果您的应用需要与其他平台互通,您可能需要研究 Matrix 的桥接技术,并在您的应用中进行相应的处理。 学习资源:
    • Matrix.org 开发者文档: https://matrix.org/docs/developers/
    • Matrix iOS SDK GitHub 仓库: https://github.com/matrix-org/matrix-ios-sdk
    • Element iOS 客户端源代码: Element 是官方的 Matrix 客户端,其源代码可以作为很好的参考 (https://github.com/element-hq/element-ios).
    • 社区支持: 加入 Matrix 的开发者社区,与其他开发者交流经验和寻求帮助。您可以在 Matrix 上找到 #matrix-dev:matrix.org 等房间。 基于 Matrix 开发即时通讯应用是一个涉及多个方面的任务,但 Matrix 协议的开放性和强大的 SDK 为 iOS 开发者提供了构建安全、互联的通讯应用的强大基础。

    iOS – 与Flutter互相通信

    在现代跨平台应用开发中,Flutter 已经成为一个非常受欢迎的解决方案。然而,很多时候我们需要在 Flutter 应用中调用原生功能,或者在原生应用中嵌入 Flutter 组件。本文将详细介绍 Flutter 与 iOS 原生代码的交互机制,并以 RayLink 项目为例进行说明。

    1. 交互方式概述

    Flutter 与 iOS 原生代码的交互主要有以下几种方式:

    1. Method Channel: 用于在 Flutter 与原生代码之间传递方法调用和结果
    2. Flutter Boost: 一个阿里巴巴开源的框架,用于管理混合栈的导航和页面生命周期
    3. Platform Views: 允许在 Flutter 界面中嵌入原生视图
    4. Pigeon: 一个代码生成工具,用于更类型安全的跨平台通信

    在 RayLink 项目中,我们主要使用了 Flutter BoostMethod Channel 来处理 Flutter 与 iOS 之间的通信。

    1.1 通信架构图

    上图展示了Flutter与iOS原生代码的主要通信层次:

    • 引擎层:Flutter Engine与iOS原生层通过FlutterBoost建立连接
    • 通信层:Method Channel用于方法调用和数据传递,使用JSON序列化或二进制格式
    • 视图层:Platform Views允许Flutter嵌入原生UIView组件

    通信流程大致如下:

    1. Flutter代码通过Method Channel发送调用请求
    2. iOS原生代码接收请求并处理
    3. iOS将处理结果通过Method Channel回传给Flutter
    4. 如需UI交互,通过Platform Views实现原生视图与Flutter的混合渲染

    2. Flutter Boost 设置与配置

    Flutter Boost 是一个功能强大的框架,它解决了 Flutter 页面与原生页面混合使用时的各种问题。在 RayLink 项目中,我们在 AppDelegate.swift 中进行了如下配置:

    import UIKit
    import Flutter
    import flutter_boost
    import raylink_sdk_ios

    @UIApplicationMain
    @objc class AppDelegate: FlutterAppDelegate {
     override func application(
       _ application: UIApplication,
       didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
     ) -> Bool {
         GeneratedPluginRegistrant.register(with: self)
         
         // 配置 Flutter Boost
         let options = FlutterBoostSetupOptions.createDefault()
         FlutterBoost.instance().setup(application, delegate: ConnectManager.shared, callback: { engine in
             
             // 注册平台视图工厂
             let factory = ImageFlutterViewFactory(messenger: engine!.binaryMessenger)
             engine?.registrar(forPlugin: "imageView")?.register(factory, withId: "imageView")
         }, options: options)
         
         // 创建 Flutter 容器视图并设置为根视图
         let fvc: FBFlutterViewContainer! = FBFlutterViewContainer();
         fvc.setName("app", uniqueId: nil, params: [:], opaque: true)
         
         // 管理容器视图的生命周期
         fvc.beginAppearanceTransition(true, animated: false)
         fvc.endAppearanceTransition()
         fvc.beginAppearanceTransition(false, animated: false)
         fvc.endAppearanceTransition()
         
         self.window.rootViewController = fvc
         
         return super.application(application, didFinishLaunchingWithOptions: launchOptions)
     }
    }

    2.1 主要组件说明

    1. FBFlutterViewContainer: 一个特殊的 UIViewController,用于承载 Flutter 页面
    2. ConnectManager: 自定义的 Flutter Boost 代理,处理页面跳转和生命周期事件
    3. FlutterBoostSetupOptions: 配置项,用于定制 Flutter Boost 的行为

    3. Method Channel 通信机制

    在 RayLink 项目中,Method Channel 是 Flutter 与 iOS 之间进行方法调用和数据传递的主要方式。通过分析 raylink_sdk_iosraylink_flutter 项目的代码,我们可以更详细地了解其实现方式。

    3.1 Method Channel 的基本原理

    Method Channel 基于消息传递机制,允许 Flutter 代码调用平台特定的 API,以及平台代码调用 Flutter 中的方法。数据通过 JSON 序列化或二进制格式进行传输。

    3.2 iOS 端的 Method Channel 实现

    在 iOS 端,Method Channel 的典型实现如下:

    // 在 iOS 端创建通道
    let methodChannel = FlutterMethodChannel(name: "raylink_sdk_flutter",
                                          binaryMessenger: controller.binaryMessenger)

    // 处理来自 Flutter 的方法调用
    methodChannel.setMethodCallHandler { (call, result) in
       if call.method == "methodName" {
           // 处理方法调用
           result("返回结果")
       } else {
           result(FlutterMethodNotImplemented)
       }
    }

    // 从 iOS 调用 Flutter 方法
    methodChannel.invokeMethod("flutterMethodName", arguments: ["key": "value"]) { (response) in
       // 处理返回结果
    }

    3.3 Flutter 端的 Method Channel 实现

    在 Flutter 端,对应的 Method Channel 代码如下:

    // 创建与 iOS 端相同名称的通道
    final methodChannel = MethodChannel('raylink_sdk_flutter');

    // 调用 iOS 端的方法
    try {
     final result = await methodChannel.invokeMethod('methodName', {'param': 'value'});
     // 处理返回结果
    } on PlatformException catch (e) {
     // 处理错误
    }

    // 处理来自 iOS 端的方法调用
    methodChannel.setMethodCallHandler((call) async {
     if (call.method == 'flutterMethodName') {
       // 处理方法调用
       return "返回结果";
    }
     throw PlatformException(code: 'notImplemented', message: 'Method not implemented');
    });

    3.4 RayLink 项目中的实际应用

    在 RayLink 项目中,Method Channel 主要用于以下场景:

    1. 设备信息传递:从 iOS 获取设备信息并传递给 Flutter
    2. 远程控制命令:Flutter 向 iOS 发送远程控制命令
    3. 用户认证:处理用户登录、注册等认证流程
    4. 设置同步:同步用户设置和偏好

    一个具体的例子是 RaylinkSdkFlutter 类的初始化和使用:

    // Flutter 端初始化 SDK
    RaylinkSdkFlutter.init();

    // 注册从 iOS 接收远程控制关闭事件
    RaylinkSdkFlutter.closeRemoteController.stream.listen((event) {
     // 处理远程控制关闭事件
    });

    对应的 iOS 端代码会在适当的时机调用相应的方法:

    // 通知 Flutter 远程控制已关闭
    methodChannel.invokeMethod("closeRemoteController", arguments: nil)

    4. Platform Views

    在 RayLink 项目中,Platform Views 是一项关键功能,允许在 Flutter 应用中嵌入原生 iOS 视图。这对于需要高性能渲染或特定原生功能的场景非常有用。

    4.1 Platform Views 的原理

    Platform Views 的工作原理是在 Flutter 应用中创建一个占位符,然后在该位置渲染原生视图。Flutter 引擎会处理这些视图之间的合成,使它们无缝集成。

    4.2 在 RayLink 项目中的实现

    在 RayLink 项目中,我们使用 Platform Views 来嵌入原生图像视图。在 AppDelegate 中,在 Flutter Boost 的设置过程中注册了 Platform View 工厂:

    // 在 AppDelegate.swift 的 didFinishLaunchingWithOptions 中
    FlutterBoost.instance().setup(application, delegate: ConnectManager.shared, callback: { engine in
       // 注册平台视图工厂
       let factory = ImageFlutterViewFactory(messenger: engine!.binaryMessenger)
       engine?.registrar(forPlugin: "imageView")?.register(factory, withId: "imageView")
    }, options: options)

    4.3 Platform View 工厂的实现

    在 raylink_sdk_ios 项目的 ImageFlutterView.swift 文件中,我们可以看到一个完整的 Platform View 实现:

    public class ImageFlutterViewFactory: NSObject, FlutterPlatformViewFactory {
       private var messenger: FlutterBinaryMessenger

       public init(messenger: FlutterBinaryMessenger) {
           self.messenger = messenger
           super.init()
       }
       
       public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
            return FlutterStandardMessageCodec.sharedInstance()
        }

       public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
           return ImageFlutterView(frame: frame, viewID: viewId, args: args, messenger: messenger)
       }
    }

    class ImageFlutterView: NSObject, FlutterPlatformView {
       private var _view: UIImageView
       
       // 初始化方法,接收 frame,view ID 和初始化参数
       init(frame: CGRect, viewID: Int64, args: Any?, messenger: FlutterBinaryMessenger) {
           _view = UIImageView(frame: frame)  // 使用自定义的 UIImageView
           
           // 处理来自 Flutter 的初始化参数
           if let params = args as? Dictionary<String, Any> {
               if let imageName = params["imageName"] as? String {
                   // 从资源包加载图片
                   _view.image = UIImage(named: imageName, in: Bundle(path: (Bundle(for: ImageFlutterView.classForCoder()).resourcePath ?? "") + "/raylink_sdk_ios.bundle"), compatibleWith: nil);
               }
           }
           super.init()
       }
       
       // 返回用于 Flutter 的 UIView
       func view() -> UIView {
           return _view
       }
    }

    4.4 Flutter 端的使用

    在 Flutter 端,使用 UiKitView(iOS)来嵌入原生视图:

    class NativeImageView extends StatelessWidget {
     final String imageName;
     
     const NativeImageView({Key? key, required this.imageName}) : super(key: key);
     
     @override
     Widget build(BuildContext context) {
       // 仅在 iOS 平台运行时创建 UiKitView
       if (Platform.isIOS) {
         return UiKitView(
           viewType: 'imageView',
           creationParams: <String, dynamic>{
             'imageName': imageName,
          },
           creationParamsCodec: const StandardMessageCodec(),
        );
      }
       
       // 在其他平台上显示占位符
       return Container(child: Text('仅支持在 iOS 平台'));
    }
    }

    4.5 性能考虑

    使用 Platform Views 时需要注意以下性能考虑:

    1. 渲染开销:Platform Views 的渲染需要额外的合成步骤,可能会影响性能
    2. 内存管理:需要正确管理原生视图的生命周期,避免内存泄漏
    3. 视图层级:过深的视图层级可能会影响性能,应保持扁平化
    4. 通信开销:Flutter 与原生视图之间的频繁通信会增加开销

    5. 路由管理与页面导航

    在 RayLink 项目中,Flutter Boost 提供了混合栈导航的关键能力,允许 Flutter 页面与原生 iOS 页面无缝切换。

    5.1 Flutter Boost 在 iOS 端的配置

    在 RayLink 项目的 AppDelegate 中,我们看到了关键的 Flutter Boost 配置代码:

    // 配置 Flutter Boost
    let options = FlutterBoostSetupOptions.createDefault()
    FlutterBoost.instance().setup(application, delegate: ConnectManager.shared, callback: { engine in
       // 注册平台视图工厂和其他初始化操作
    }, options: options)

    // 创建 Flutter 容器视图并设置为根视图
    let fvc: FBFlutterViewContainer! = FBFlutterViewContainer();
    fvc.setName("app", uniqueId: nil, params: [:], opaque: true)

    // 管理容器视图的生命周期
    fvc.beginAppearanceTransition(true, animated: false)
    fvc.endAppearanceTransition()
    fvc.beginAppearanceTransition(false, animated: false)
    fvc.endAppearanceTransition()

    self.window.rootViewController = fvc

    关键的部分是 ConnectManager.shared 作为 Flutter Boost 的代理类,负责处理路由和页面导航。

    5.2 ConnectManager 中的路由实现

    通过查看 ConnectManager.swift 文件,我们可以看到 RayLink 项目中路由处理的关键部分:

    // 在 ConnectManager 中打开 Flutter 页面
    func openFlutterPage(name: String, arguments: [AnyHashable : Any]!, onPageFinished: (([AnyHashable : Any]?) -> Void)?) {
       let options = FlutterBoostRouteOptions()
       options.pageName = name
       options.arguments = arguments
       
       if (name == FlutterPageName.showVipDialog.rawValue) {
           options.opaque = false
       } else {
           options.opaque = true
       }
       
       // 这个是push操作完成的回调,而不是页面关闭的回调
       options.completion = { completion in
           print("open operation is completed")
       }

       // 这个是页面关闭并且返回数据的回调
       if let onFinished = onPageFinished {
           options.onPageFinished = onFinished
       }

       FlutterBoost.instance().open(options)
    }

    // 在 ConnectManager 中实现的 pushFlutterRoute 方法
    @objc public func pushFlutterRoute(_ options: FlutterBoostRouteOptions!) {
       var fvc: FBFlutterViewContainer! = FBFlutterViewContainer()
       if options.pageName == FlutterPageName.showVipDialog.rawValue {
           fvc = DiaglogFlutterViewContainer()
           fvc.enableLeftPanBackGesture = false;
           fvc.surfaceUpdated(true)
       } else {
           fvc = FBFlutterViewContainer()
       }

       fvc.setName(options.pageName, uniqueId: options.uniqueId, params: options.arguments, opaque: options.opaque)
       fvc.enableLeftPanBackGesture = false;
       
       if let vc = fvc as? UIViewController {
           if let arguments = options.arguments {
               let animated = arguments["animated"] as? Bool ?? true
               let present = arguments["present"] as? Bool ?? true
               
               if (options.pageName == FlutterPageName.showVipDialog.rawValue) {
                   fvc.hero.modalAnimationType = .fade
                   fvc.hero.isEnabled = true
               }

               RGUIHelper.visibleViewController()?.present(vc, animated: animated, completion: {
                   options.completion(true)
               })
           } else {
               RGUIHelper.visibleViewController()?.present(vc, animated: true, completion: {
                   options.completion(true)
               })
           }
       }
    }

    // 在 ConnectManager 中实现的 popRoute 方法
    @objc public func popRoute(_ options: FlutterBoostRouteOptions!) {
       DispatchQueue.main.async {
           RGUIHelper.visibleViewController()?.dismiss(animated: true, completion: {
               options.completion(true)
           })
       }
    }

    5.3 Flutter 端的路由实现

    在 Flutter 端,我们可以看到 main.dart 文件中的 Flutter Boost 初始化和路由配置:

    // 在 main 方法中初始化 Flutter Boost
    runApp(
     FlutterBoostApp(
       routeFactory,
       appBuilder: (child) {
         return CupertinoApp(
           home: child,
           builder: (context, child) => FlutterEasyLoading(
             child: MediaQuery(
               data: MediaQuery.of(context).copyWith(textScaler: TextScaler.noScaling),
               child: child!,
            ),
          ),
        );
      },
    ),
    );

    // 在 Flutter 业务代码中使用 Flutter Boost 进行页面导航
    // 打开 Flutter 页面
    FlutterBoost.instance.push(
     "/flutter_page",
     arguments: {"key": "value"},
    );

    // 打开原生页面
    FlutterBoost.instance.push(
     "/native_page",
     arguments: {"key": "value"},
    );

    // 关闭当前页面
    FlutterBoost.instance.pop();

    5.4 特殊页面的处理

    在 RayLink 项目中,我们可以看到有一些特殊的页面处理,如 DiaglogFlutterViewContainer 类,它是 FBFlutterViewContainer 的子类,用于显示对话框页面:

    class DiaglogFlutterViewContainer: FBFlutterViewContainer {

       override func viewDidLoad() {
           super.viewDidLoad()
       }
       
       override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
           return [.landscapeLeft, .landscapeRight, .portrait]
       }
    }

    这个类主要处理屏幕方向的支持,确保对话框可以在不同的屏幕方向下正常显示。

    5.5 路由流程图

    下图展示了 RayLink 项目中 Flutter Boost 的路由流程:

    Flutter 页面FlutterBoostConnectManageriOS 原生页面FlutterBoost.instance.push("/page")pushFlutterRoute(options)创建 FBFlutterViewContainerpresent(视图控制器)openFlutterPage(name, args)FlutterBoost.instance().open(options)打开 Flutter 页面FlutterBoost.instance.pop()popRoute(options)dismiss(animated: true)Flutter 页面FlutterBoostConnectManageriOS 原生页面

    5.6 生命周期管理

    在 RayLink 项目中,Flutter Boost 的生命周期管理非常重要,特别是对于容器视图的生命周期。我们可以在 AppDelegate 中看到这些关键的生命周期调用:

    // 管理容器视图的生命周期
    fvc.beginAppearanceTransition(true, animated: false)
    fvc.endAppearanceTransition()
    fvc.beginAppearanceTransition(false, animated: false)
    fvc.endAppearanceTransition()

    这些调用确保了容器视图的正确初始化和生命周期管理,减少了页面切换时可能出现的问题。

    6. 最佳实践与性能考虑

    在 RayLink 项目中,我们采用了多种最佳实践来确保 Flutter 与 iOS 原生代码交互的高效性和稳定性。

    6.1 数据传输优化

    6.1.1 批量传输数据

    每次跨平台边界的数据传输都会带来性能开销。在 RayLink 项目中,我们采用了批量传输的策略:

    // 错误示例:多次小数据传输
    for item in items {
       methodChannel.invokeMethod("sendItem", arguments: item)
    }

    // 正确示例:一次性批量传输
    methodChannel.invokeMethod("sendItems", arguments: items)

    6.1.2 使用适当的序列化格式

    对于大数据量,我们使用二进制格式而非 JSON:

    // 使用 StandardMessageCodec 而非 JSON
    func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
       return FlutterStandardMessageCodec.sharedInstance()
    }

    6.2 线程处理

    在 Flutter 与 iOS 交互中,正确的线程处理至关重要。在 RayLink 项目中,我们确保所有 UI 操作在主线程执行:

    // iOS 端的线程处理
    methodChannel.setMethodCallHandler { (call, result) in
       if call.method == "updateUI" {
           // 确保 UI 更新在主线程执行
           DispatchQueue.main.async {
               // UI 更新操作
               result(true)
           }
       } else {
           // 非 UI 相关的计算密集型操作可以在后台线程执行
           DispatchQueue.global().async {
               // 处理耗时操作
               let processedResult = self.processData(call.arguments)
               // 结果返回需要切回主线程
               DispatchQueue.main.async {
                   result(processedResult)
               }
           }
       }
    }

    6.3 错误处理

    在 RayLink 项目中,我们实现了全面的错误处理机制,特别是对于 Method Channel 通信:

    // iOS 端的错误处理
    methodChannel.setMethodCallHandler { (call, result) in
       do {
           let response = try self.processMethodCall(call)
           result(response)
       } catch let error as NSError {
           result(FlutterError(code: String(error.code),
                               message: error.localizedDescription,
                               details: error.userInfo))
       }
    }

    在 Flutter 端的错误处理:

    // Flutter 端的错误处理
    try {
     final result = await methodChannel.invokeMethod('someMethod');
     // 处理成功结果
    } on PlatformException catch (e) {
     // 根据错误码分类处理
     switch (e.code) {
       case 'NETWORK_ERROR':
         // 处理网络错误
         break;
       case 'AUTHENTICATION_FAILED':
         // 处理认证错误
         break;
       default:
         // 其他错误处理
         break;
    }
    }

    6.4 内存管理

    在 RayLink 项目中,我们特别注意 Platform Views 的内存管理:

    // 正确处理 Platform View 的生命周期
    class ImageFlutterView: NSObject, FlutterPlatformView {
       private var _view: UIImageView
       private var messenger: FlutterBinaryMessenger
       private var registrar: FlutterPluginRegistrar?
       
       // 初始化时捕获强引用
       init(frame: CGRect, viewID: Int64, args: Any?, messenger: FlutterBinaryMessenger) {
           self._view = UIImageView(frame: frame)
           self.messenger = messenger
           super.init()
           // 设置视图
       }
       
       func view() -> UIView {
           return _view
       }
       
       // 处理资源释放
       deinit {
           // 清理相关资源
           NSLog("ImageFlutterView deinit")
       }
    }

    6.5 抽象层设计

    在 RayLink 项目中,我们创建了清晰的抽象层来管理平台特定代码。比如,ConnectManager 类封装了所有与 Flutter 相关的交互:

    @objc public class ConnectManager: NSObject, FlutterBoostDelegate {
       static let shared = ConnectManager()  // 单例模式
       
       // 抽象设备信息管理
       private var deviceInfo: DeviceInfo?
       
       // 抽象连接设置管理
       private var connectSettings = ConnectSettings()
       
       // Flutter 与原生交互的方法都封装在这里
       // ...
    }

    6.6 缓存策略

    对于频繁传输的数据,我们实现了缓存机制来减少跨平台通信:

    // iOS 端的数据缓存示例
    class DataCache {
       static let shared = DataCache()
       private var cache = [String: Any]()
       private let cacheQueue = DispatchQueue(label: "com.raylink.datacache")
       
       func set(key: String, value: Any) {
           cacheQueue.async {
               self.cache[key] = value
           }
       }
       
       func get(key: String) -> Any? {
           var result: Any?
           cacheQueue.sync {
               result = self.cache[key]
           }
           return result
       }
       
       func clear() {
           cacheQueue.async {
               self.cache.removeAll()
           }
       }
    }

    6.7 异步操作管理

    在 RayLink 项目中,我们使用 Swift 的 Completion Handler 和 Flutter 的 Future/async-await 来管理异步操作:

    // iOS 端的异步操作
    func performAsyncOperation(params: [String: Any], completion: @escaping (Result<[String: Any], Error>) -> Void) {
       // 异步操作
       DispatchQueue.global().async {
           // 执行耗时操作
           // ...
           
           // 完成后回调
           DispatchQueue.main.async {
               completion(.success(["result": "success"]))
           }
       }
    }

    // 在 Method Channel 中使用
    methodChannel.setMethodCallHandler { (call, result) in
       if call.method == "asyncOperation" {
           self.performAsyncOperation(params: call.arguments as? [String: Any] ?? [:]) { operationResult in
               switch operationResult {
               case .success(let data):
                   result(data)
               case .failure(let error):
                   result(FlutterError(code: "OPERATION_FAILED", message: error.localizedDescription, details: nil))
               }
           }
       }
    }

    6.8 应用启动优化

    在 RayLink 项目中,我们采用了懒加载策略,避免在启动时初始化所有组件:

    // AppDelegate中的懒加载策略
    override func application(
       _ application: UIApplication,
       didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
       // 只初始化必要的组件
       setupEssentialComponents()
       
       // 在主线程空闲时初始化非关键组件
       DispatchQueue.main.async {
           self.setupNonEssentialComponents()
       }
       
       return true
    }

    7. 调试技巧

    在 RayLink 项目中调试 Flutter 与 iOS 交互问题时,我们采用了一系列实用的调试技巧。

    7.1 打印日志

    在 RayLink 项目中,我们使用了 CocoaLumberjack 来实现更高级的日志记录:

    // 在 ConnectManager.swift 中的日志设置
    typealias NewNSLog = @convention(thin) (NSString, CVarArg...) -> Void

    func newNSLog(_ format: NSString, _ args: CVarArg...) -> Void {
       let message = NSString(format: format)
       DDLogInfo(message)  // 使用 CocoaLumberjack 记录
    }

    public func fishhookNSLog(newMethod: UnsafeMutableRawPointer) {
       var oldMethod: UnsafeMutableRawPointer?
       rebindSymbol("NSLog", replacement: newMethod, replaced: &oldMethod)
    }

    // 在初始化中设置日志文件
    let fileLogger = DDFileLogger()
    fileLogger.rollingFrequency = 60 * 60 * 24 // 24 小时滚动一次
    fileLogger.logFileManager.maximumNumberOfLogFiles = 7 // 保留 7 天的日志

    DDLog.add(fileLogger)

    // 替换标准 NSLog 以捕获所有系统日志
    fishhookNSLog(newMethod: unsafeBitCast(newNSLog as NewNSLog, to: UnsafeMutableRawPointer.self))

    Flutter 端的日志记录:

    // 定义日志级别
    enum LogLevel { debug, info, warning, error }

    // 打印结构化日志
    class Logger {
     static void log(String message, {LogLevel level = LogLevel.info, String? tag}) {
       final timestamp = DateTime.now().toIso8601String();
       final tagString = tag != null ? "[$tag]" : "";
       final levelString = level.toString().split('.').last.toUpperCase();
       
       print("$timestamp $tagString [$levelString] $message");
       
       // 对于重要日志,还可以存储到文件或发送到服务器
       if (level == LogLevel.error) {
         // 可以使用 Method Channel 将错误日志发送到 iOS 端记录
         MethodChannel('raylink_sdk_flutter').invokeMethod('logError', {
           'message': message,
           'timestamp': timestamp,
        });
      }
    }
    }

    // 使用示例
    Logger.log('Method channel communication started', level: LogLevel.debug, tag: 'MethodChannel');

    7.2 使用 Xcode 的调试工具

    在 RayLink 项目中,我们常在 Method Channel 的处理函数中设置断点:

    // 在 Method Channel 处理中设置的断点
    methodChannel.setMethodCallHandler { [weak self] (call, result) in
       guard let self = self else { return }
       
       // 在这里设置断点,检查 call.method 和 call.arguments
       print("Method called: \(call.method) with args: \(String(describing: call.arguments))")
       
       switch call.method {
       case "methodName":
           // 在方法开始处设置断点
           self.handleMethodName(args: call.arguments as? [String: Any]) { response in
               // 在回调处设置断点检查响应
               result(response)
           }
       default:
           result(FlutterMethodNotImplemented)
       }
    }

    此外,还可以使用 Xcode 的 LLDB 调试器来检查变量和状态:

    // 在 LLDB 控制台中使用的命令
    po call.method // 打印对象
    po call.arguments // 查看参数
    expression -l objc -- [[UIApplication sharedApplication] windows] // 查看当前窗口

    // 设置符号断点
    breakpoint set -n "setMethodCallHandler:" // 在特定方法上设置断点

    7.3 使用 Flutter DevTools

    在 RayLink 项目中,我们大量使用 Flutter DevTools 来调试 Flutter 端的问题:

    1. Widget Inspector: 检查 UI 渲染问题
    2. Timeline: 测量性能瓶颈,特别是与原生交互相关的帧速问题
    3. Memory: 监控内存泄漏,特别是与 Platform Views 相关的内存问题
    4. Network: 监控网络请求

    7.4 Method Channel 消息检查

    在 RayLink 项目中,我们实现了一个专用的 Method Channel 监控器:

    // iOS 端的 Method Channel 监控
    class MethodChannelMonitor {
       static let shared = MethodChannelMonitor()
       private var isMonitoring = false
       
       func startMonitoring() {
           guard !isMonitoring else { return }
           isMonitoring = true
           
           // 使用 Method Swizzling 技术来捕获所有 Method Channel 调用
           let originalSelector = #selector(FlutterMethodChannel.invokeMethod(_:arguments:result:))
           let swizzledSelector = #selector(FlutterMethodChannel.swizzled_invokeMethod(_:arguments:result:))
           
           let originalMethod = class_getInstanceMethod(FlutterMethodChannel.self, originalSelector)
           let swizzledMethod = class_getInstanceMethod(FlutterMethodChannel.self, swizzledSelector)
           
           method_exchangeImplementations(originalMethod!, swizzledMethod!)
       }
    }

    // 使用类别扩展来实现 swizzled 方法
    extension FlutterMethodChannel {
       @objc func swizzled_invokeMethod(
           _ method: String,
           arguments: Any?,
           result: @escaping FlutterResult
       ) {
           print("[MethodChannel] 调用方法: \(method) 参数: \(String(describing: arguments))")
           
           // 包装原始结果回调,以捕获响应
           let wrappedResult: FlutterResult = { response in
               print("[MethodChannel] 方法: \(method) 响应: \(String(describing: response))")
               result(response)
           }
           
           // 调用原始方法(由于方法交换,这实际上调用的是原始方法)
           self.swizzled_invokeMethod(method, arguments: arguments, result: wrappedResult)
       }
    }

    Flutter 端的 Method Channel 监控:

    // 创建带监控的 MethodChannel 包装器
    class MonitoredMethodChannel extends MethodChannel {
     MonitoredMethodChannel(String name) : super(name);
     
     @override
     Future<T?> invokeMethod<T>(String method, [dynamic arguments]) async {
       print('[Flutter MethodChannel] 调用: $method 参数: $arguments');
       try {
         final result = await super.invokeMethod<T>(method, arguments);
         print('[Flutter MethodChannel] 响应: $result');
         return result;
      } catch (e) {
         print('[Flutter MethodChannel] 错误: $e');
         rethrow;
      }
    }
    }

    // 使用监控通道
    final channel = MonitoredMethodChannel('raylink_sdk_flutter');

    7.5 实时监控和运行时调试

    RayLink 项目中,我们还实现了实时监控和运行时调试功能:

    // 添加通用的调试开关
    class DebugSettings {
       static let shared = DebugSettings()
       
       var isMethodChannelMonitorEnabled = false
       var isPlatformViewDebugEnabled = false
       var isPerformanceMonitorEnabled = false
       
       // 根据用户特定手势激活调试模式
       func activateDebugMode() {
           isMethodChannelMonitorEnabled = true
           isPlatformViewDebugEnabled = true
           isPerformanceMonitorEnabled = true
           
           // 启动监控
           MethodChannelMonitor.shared.startMonitoring()
           
           // 显示调试浮层
           showDebugOverlay()
       }
       
       private func showDebugOverlay() {
           // 创建浮层显示调试信息
       }
    }

    7.6 特定场景的调试技巧

    7.6.1 Platform Views 调试

    对于 Platform Views 的问题,我们有特定的调试技巧:

    // 在 Platform View 中添加可视化边界以调试布局
    extension UIView {
       func addDebugBorder(color: UIColor = .red, width: CGFloat = 1.0) {
           #if DEBUG
           self.layer.borderColor = color.cgColor
           self.layer.borderWidth = width
           #endif
       }
    }

    // 在 ImageFlutterView 中使用
    init(frame: CGRect, viewID: Int64, args: Any?, messenger: FlutterBinaryMessenger) {
       _view = UIImageView(frame: frame)
       // ...
       
       if DebugSettings.shared.isPlatformViewDebugEnabled {
           _view.addDebugBorder()
           
           // 添加标签以识别视图
           let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
           label.text = "ID: \(viewID)"
           label.textColor = .red
           label.font = UIFont.systemFont(ofSize: 10)
           _view.addSubview(label)
       }
    }

    8. 总结

    通过对 RayLink 项目中 Flutter 与 iOS 原生代码交互机制的综合分析,我们可以得出以下关键结论和最佳实践。

    8.1 交互机制总览

    在 RayLink 项目中,我们看到了 Flutter 与 iOS 交互的三种主要方式:

    1. Flutter Boost: 管理混合堆栈导航、页面生命周期和页面切换动画
    2. Method Channel: 实现 Flutter 与 iOS 原生代码之间的双向方法调用和数据传递
    3. Platform Views: 在 Flutter 应用中无缝嵌入原生 iOS 视图,如 ImageFlutterView

    这三种技术相互配合,共同构成了一个强大的混合应用架构。

    8.2 核心优势与价值

    这种混合架构为 RayLink 项目带来了以下显著优势:

    1. 开发效率: 采用 Flutter 进行 UI 开发,大大提高了多平台开发效率
    2. 原生体验: 在性能关键的场景可以无缝切换到原生实现
    3. 灵活集成: 允许在需要时利用原生 iOS 特有功能,如特殊硬件控制、AR 功能等
    4. 平滑迁移: 支持渐进式地将现有原生代码迁移到 Flutter

    8.3 实践经验总结

    基于 RayLink 项目的实践经验,我们总结出以下关键积累:

    8.3.1 架构设计

    1. 明确的层次划分: 在 RayLink 项目中,我们清晰地划分了业务逻辑层、通信层和平台特定层
    2. 统一的通信接口: 创建了标准化的 Method Channel 接口,确保跨平台交互的一致性
    3. 完善的错误处理: 实现了重试机制和优雅的错误回退策略

    8.3.2 性能优化

    1. 数据批量处理: 减少跨边界调用,合并相关操作
    2. 缓存策略: 对频繁访问的数据实现了惯性缓存
    3. 异步处理: 采用非阻塞和异步模式执行耗时操作

    8.3.3 测试与调试

    1. 实时监控: 实现了全方位日志记录和分析能力
    2. 模拟器: 搭建了特定交互场景的模拟环境
    3. 自动化测试: 开发了专门的跨平台集成测试套件

    8.4 未来发展路线

    随着 Flutter 与原生交互技术的不断成熟,我们看到了以下发展路线:

    1. Pigeon 等代码生成工具: 实现更类型安全的跨平台接口
    2. Flutter 2.0+ 的 PlatformView 改进: 发挥 Hybrid Composition 和 Virtual Display 模式的最大潜力
    3. Sound Null Safety: 利用 Dart 的空安全功能提高数据交换可靠性
    4. 动态特性加载: 部署支持动态交付的解决方案

    8.5 最终成果

    通过全面适配 Flutter 与 iOS 原生代码的交互机制,RayLink 项目成功实现了在保持高效开发的同时提供原生级别的用户体验。这种混合架构方案不仅澄清地展示了 Flutter 与 iOS 之间的通信机制,也为其他跨平台移动应用开发提供了实用的参考模型。

    iOS – 主线程卡顿监测方案

    背景

    iOS设备虽然在硬件和软件层面一直在优化,但还是有不少坑会导致UI线程的卡顿。对于程序员来说,除了增加自身知识储备和养成良好的编程习惯之外,如果能一套机制能自动预报“卡顿”并检测出导致该“卡顿”的代码位置自然更好。本文就可能的实现方案做一些探讨和分析。

    解决方案分析

    简单来说,主线程为了达到接近60fps的绘制效率,不能在UI线程有单个超过(1/60s≈16ms)的计算任务。通过Instrument设置16ms的采样率可以检测出大部分这种费时的任务,但有以下缺点:

    • Instrument profile一次重新编译,时间较长。
    • 只能针对特定的操作场景进行检测,要预先知道卡顿产生的场景。
    • 每次猜测,更改,再猜测再以此循环,需要重新profile。

    我们的目标方案是,检测能够自动发生,并不需要开发人员做任何预先配置或profile。运行时发现卡顿能即时通知开发人员导致卡顿的函数调用栈。

    基于上述前提,我暂时能想到两个方案大致可行。

    方案一:基于Runloop

    主线程绝大部分计算或者绘制任务都是以Runloop为单位发生。单次Runloop如果时长超过16ms,就会导致UI体验的卡顿。

    那如何检测单次Runloop的耗时呢?

    Runloop的生命周期及运行机制虽然不透明,但苹果提供了一些API去检测部分行为。我们可以通过如下代码监听Runloop每次进入的事件:

    - (void)setupRunloopObserver {
    	 static dispatch_once_t onceToken; 
    	 dispatch_once(&onceToken, ^{ 
    			 CFRunLoopRef runloop = CFRunLoopGetCurrent(); 
    			 CFRunLoopObserverRef enterObserver; 
    			 enterObserver = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopEntry | kCFRunLoopExit, true, -0x7FFFFFFF, BBRunloopObserverCallBack, NULL); 
    			 CFRunLoopAddObserver(runloop, enterObserver, kCFRunLoopCommonModes); 
    			 CFRelease(enterObserver); 
    		});
    }
    
    
    static void BBRunloopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
    		switch (activity) { 
    			case kCFRunLoopEntry: { 
    					NSLog(@"enter runloop..."); 
    			} break; 
    			case kCFRunLoopExit: {
    					NSLog(@"leave runloop..."); 
    			} break; 
    			default: break; 
    			}
    }

    看起来kCFRunLoopExit的时间,减去kCFRunLoopEntry的时间,即为一次Runloop所耗费的时间。这个方案我并没有继续深入思考更多的细节。因为虽然能找出大于16ms的runloop,但无法定位到具体的函数,只能起到预报的作用,不符合我们的目标方案。

    方案二:基于线程

    最理想的方案是让UI线程“主动汇报”当前耗时的任务,听起来简单做起来不轻松。

    我们可以假设这样一套机制:每隔16ms让UI线程来报道一次,如果16ms之后UI线程没来报道,那就一定是在执行某个耗时的任务。这种抽象的描述翻译成代码,可以用如下表述:

    我们启动一个worker线程,worker线程每隔一小段时间(delta)ping以下主线程(发送一个NSNotification),如果主线程此时有空,必然能接收到这个通知,并pong以下(发送另一个NSNotification),如果worker线程超过delta时间没有收到pong的回复,那么可以推测UI线程必然在处理其他任务了,此时我们执行第二步操作,暂停UI线程,并打印出当前UI线程的函数调用栈。

    难点在这第二步,如何暂停UI线程,同时获取到callstack

    iOS的多线程编程一般使用NSOperation或者GCD,这两者都无法暂停每个正在执行的线程。所谓的cancel调用也只能在目标线程空闲的时候,主动检测cancelled状态,然后主动sleep,这显然非我所欲。

    还剩下pthread一途,pthread系列api当中有个函数pthread_kill()看起来符合期望。

    The pthread_kill() function sends the signal sig to thread, a thread in the same process as the caller. The signal is asynchronously directed to thread.

    If sig is 0, then no signal is sent, but error checking is still performed.

    如果我们从worker线程给UI线程发送signal,UI线程会被即刻暂停,并进入接收signal的回调,再将callstack打印就接近目标了。

    iOS确实允许在主线程注册一个signal处理函数,类似这样:

    signal(CALLSTACK_SIG, thread_singal_handler);

    这里补充下signal相关的知识点。

    iOS系统的signal可以被归为两类:

    第一类内核signal

    这类signal由操作系统内核发出,比如当我们访问VM上不属于自己的内存地址时,会触发EXC_BAD_ACCESS异常,内核检测到该异常之后会发出第二类signal:BSD signal,传递给应用程序。

    第二类BSD signal

    这类signal需要被应用程序自己处理。通常当我们的App进程运行时遇到异常,比如NSArray越界访问。产生异常的线程会向当前进程发出signal,如果这个signal没有别处理,我们的app就会crash了。

    平常我们调试的时候很容易遇到第二类signal导致整个程序被中断的情况,gdb同时会将每个线程的调用栈呈现出来。

    pthread_kill允许我们向目标线程(UI线程)发送signal,目标线程被暂停,同时进入signal回调,将当前线程的callstack获取并处理,处理完signal之后UI线程继续运行。将callstack打印即可精确定位产生问题的函数调用栈。

    梳理下流程可以用如下示意图表示:

    代码流程

    理清思路之后实现起来就比较简单了。

    在主线程注册signal handler

    signal(CALLSTACK_SIG, thread_singal_handler);

    通过NSNotification完成pingpong流程

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectPingFromWorkerThread) name:Notification_PMainThreadWatcher_Worker_Ping object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(detectPongFromMainThread) name:Notification_PMainThreadWatcher_Main_Pong object:nil];

    如果ping超时,pthread_kill主线程。

    pthread_kill(mainThreadID, CALLSTACK_SIG);

    主线程被暂停,进入signal回调,通过[NSThread callStackSymbols]获取主线程当前callstack。

    static void thread_singal_handler(int sig)
    {
        NSLog(@"main thread catch signal: %d", sig);
    
        if (sig != CALLSTACK_SIG) {
            return;
        }
    
        NSArray* callStack = [NSThread callStackSymbols];
    
        id<PMainThreadWatcherDelegate> del = [PMainThreadWatcher sharedInstance].watchDelegate;
        if (del != nil && [del respondsToSelector:@selector(onMainThreadSlowStackDetected:)])  {
            [del onMainThreadSlowStackDetected:callStack];
        }
        else
        {
            NSLog(@"detect slow call stack on main thread! \n");
            for (NSString* call in callStack) {
                NSLog(@"%@\n", call);
            }
        }
    
        return;
    }

    至此基础流程结束。值得一提的是上述代码不能调试,因为调试时gdb会干扰signal的处理,导致signal handler无法进入。

    现阶段的实现,worker线程每隔1秒会ping一次UI线程,检测出运行超过16ms的调用栈。开发阶段可以将1s的间隔调至更短,可能会对app整体性能造成少许的负担,但能检测出更多的卡顿调用。这部分调优工作需要更多的思考。

    贴出最后方案的github地址

    9种常见的设计模式

    Creational Patterns

    • Factory Pattern
    • Singleton Pattern
    • Builder Pattern

    Structural Patterns

    • Adapter Pattern
    • Composite Pattern
    • Decorator Pattern
    • Proxy Pattern

    Behavioral Patterns

    • Strategy Pattern
    • Observer Pattern
    • Command Pattern

    Word List 1

    词根词缀预习表

    词根/词缀 含义 例词及释义
    act exact adj. 精确的;准确的
    reg 国王 regent n. 摄政者
    philo philosophy n. 哲学;哲理
    chron 时间 chronic adj. (疾病) 慢性的
    vari 变化 variety n. 品种;变化
    equi 相等 equity n. 公平,公正
    cav excavate v. 挖掘,开凿
    mut 改变 mutual adj. 相互的;共同的
    pan 面包 companion n. 共事者;同伴
    vas invasion n. 入侵,侵略
    • delve [delv] v. 钻研;探索,探究
      • 联想记忆:埋在书架 (shelves) 里整天钻研 (delve)
      • 例句:Thomas Hardy is delving into matters we all recognise because they are common to humanity. 托马斯·哈代正在探究的是我们都能辨识的事物,因为它们是人类所共有的。
    • exact [ig’zækt] adj. 精确的;准确的
      • 词根记忆:ex (出) + act (做) → 做出精确的结果 → 精确的;准确的
      • 例句:What is the exact amount left in your savings account? 你储蓄账户上的准确余额是多少?
      • 拓展:exactly (adv. 正确地;完全地)
    • elicit [I’lIsIt] v. 引出,诱出
      • 词根记忆:e (出) + lic (诱骗) + it → 诱骗到外面来 → 诱出,引出
      • 例句:elicit no response 没有得到回应
      • 例句:The students then were randomly assigned to watch a video clip eliciting either humour, contentment, or neutral feelings. 然后,学生们被随机分配观看一段视频,这些视频要么激发幽默感、满足感,要么不激发情感变化。
    • traditional [tra’dısənl] adj. 传统的,惯例的;口传的,传说的
      • 常见搭配:traditional view 传统观点; traditional belief 传统信条; traditional industry 传统工业; traditional method 传统方法
      • 例句:Rosewood is a pure example of a traditional country house of this part of England. 红木建筑是英格兰这个地区传统乡间住宅的一个典型代表。
    • lack [læk] n./v. 缺乏,不足,没有
      • 常见搭配:lack of 缺乏,没有; lack (for) nothing 什么都不缺
      • 例句:Martin can’t afford the book due to lack of money. 由于缺钱,马丁买不起这本书。
    • regent [ ‘ri:dzənt ] n. 摄政者 (代国王统治者)
      • 词根记忆:reg (国王) + ent (表人) → 摄政者
      • 例句:George acted as a regent when the present king was a child. 当现任国王还是孩子时,乔治担任摄政王。
    • burgeon [ ‘b3:dzən ] vi. 迅速成长;发展
      • 词根记忆:burg (=bud, 花蕾) + eon → 像花蕾一样成长 → 迅速成长
      • 例句:To serve the burgeoning tourist industry, an array of professionals has emerged. 为了满足日益繁荣的旅游业的发展需求,一大批专业人员已经出现了。
    • argue [ ‘a:gju: ] v. 争论;说服
      • 发音记忆:“阿Q” → 阿Q喜欢和人争论 → 争论
      • 常见搭配:argue sb. into/out of doing 说服某人做/不做某事;argue for 赞成,要求;argue about 议论……;argue over 辩论/争论……
      • 例句:The study argues that the type of action needed against passive smoking should be similar to that being taken against other diseases. 该研究认为人们需要采取类似防范其他疾病的行动来防范被动吸烟。
      • 拓展:arguably (adv. 可论证地,可辩解地)
    • barely [ ‘beǝli ] adv. 仅仅,几乎不;赤裸裸地,无遮蔽地
      • 例句:Rainfall barely penetrates the soil in this area. 在该地区,降雨很少渗入土壤。
        hierarchy [ ‘haiǝra:ki ] n. 领导层;层次,等级
      • 例句:It is likely that all uniforms make symbolic sense, namely, denoting a hierarchy. 很可能所有的制服都具有象征意义,即体现了穿衣人的等级。
    • guidance [ ‘gaıdns ] n. 指引,指导
      • 联想记忆:guid(e) (指引,指导) + ance (表名词) → 指引,指导
      • 常见搭配:guidance system 向导系统;moral guidance 精神指导
      • 例句:We will solicit guidance and aid from legal experts. 我们将向法律专家寻求指导和帮助。
    • easy-going [ ,i:zi ‘gǝvin ] adj. 脾气随和的,心平气和的;随便的**
      • 例句:The interviews don’t tell you if applicants are easy-going or hate smoking or whatever. 从面试中你看不出求职者是否随和或者讨厌吸烟之类的。
    • electrical [ I’lektrikl ] adj. 电的;电学的;有关电的
      • 常见搭配:electrical fault 电气故障
      • 例句:The small battery-powered gadget can deliver subliminal electrical pulses to the skin. 这种小的电池驱动装置可以向皮肤传达微小的电脉冲。
      • 拓展:electrically (adv. 电力地)
    • electronic [ ,lek’tronik ] adj. 电子的
      • 来自 electric (adj. 电的)
      • 例句:Computers make up of the so-called electronic media. 所谓的电子媒介是由计算机构成的。
    • philosophy [fə’lɒsəfi] n. 哲学;哲理
      • 词根记忆:philo (爱) + soph (智慧) + y (表名词) → 爱智慧的学问 → 哲学;哲理
      • 例句:Try your best to succeed – that’s my philosophy. 竭尽全力,取得成功——这就是我的人生哲学。
      • 拓展:philosopher (n. 哲学家,哲人)
    • chronic [ ‘krɒnɪk ] adj. (疾病) 慢性的;积习难改的
      • 词根记忆:chron (时间) + ic (……的) → 长时间的 → (疾病) 慢性的
      • 常见搭配:chronic problem 长期的问题;chronic smoker 积习难改的烟民;chronic disease 慢性疾病;chronic war 持久战
      • 例句:Jim suffers from chronic back pain after the car-wreck. 车祸后吉姆一直受背痛的折磨。
      • 拓展:inveterate (adj. 根深蒂固的;成癖的)
    • desirable [dɪ’zaɪərəbl] adj. 值得拥有的;合意的;可取的,有利的
      • 来自 desire (v. 渴望)
      • 例句:Selecting the right person for the job involves more than identifying the essential or desirable range of skills. 为一份工作选择合适的人选,不能只看此人是否拥有胜任这份工作的基本技能和相应专长。
    • consortium [kən’sɔ:tiəm] n. 集团;财团;社团,协会
      • 联想记忆:consort (陪伴,结交) + ium → 结交组成团体 → 社团,协会
      • 例句:The consortium that won the contract for the island opted for an aggressive approach. 赢得这座岛屿 (开发) 合同的财团采取了一种野蛮的开发方式。
    • buckle [ ‘bʌkl ] v. 扣紧;(使) 变形;弯曲 n. 皮带扣环
      • 联想记忆:扣紧 (buckle) 雄鹿 (buck),防止挣脱
      • 例句:Buildings, pipes and roads tend to buckle and crack because of the movement of the earth. 由于地壳的运动,建筑物、地下管道以及道路等往往会变形甚至破裂。
    • curry [ ‘kʌri ] n. 咖喱;咖喱饭菜 vt. 把 (肉、蔬菜等) 做成咖喱食品;梳刷 (马毛等)
      • 常见搭配:curry powder 咖喱粉
      • 例句:Which kind of curry do you like, mild or peppery? 你喜欢哪种咖喱,微辣的还是辛辣的?
    • subliminal [sʌb’lɪmɪnl] adj. 下意识的;潜意识的
      • 词根记忆:sub (下) + limin (=limen,最小限度的神经刺激) + al (……的) → 下意识的,潜意识的
      • 例句:How we look sends all sorts of subliminal messages to other people. 我们的外表向其他人传达了各种潜在的信息。
      • 拓展:subliminally (adv. 下意识地,潜意识地)
    • chamber [ ‘tʃeɪmbə(r) ] n. 室;洞穴;(枪) 膛
      • 例句:In the brood chamber below, the bees will stash honey to eat later. 在蜂巢的下部,蜜蜂会贮藏蜂蜜,以供日后食用。
    • frequent [ ‘fri:kwənt ] adj. 频繁的;常见的;常用的
      • 词根记忆:frequ (频繁,频率) + ent (具有……性质的) → 频繁的;常见的
      • 例句:Frequent failures did not affect his morale. 屡屡失败并未让他泄气。
      • 拓展:frequently (adv. 常常,频繁地)
    • prosperous [ ‘prɒspərəs ] adj. 繁荣的,兴旺的;成功的
      • 例句:a prosperous city 一座繁华的都市
      • 例句:My grandfather once was a prosperous Britain businessman. 我祖父曾是一名成功的英国商人。
    • purpose [ ‘p3:pəs ] n. 目的,意图;用途,效果 v. 打算,企图;决心
      • 常见搭配:on purpose 故意地,有意地;with the purpose of 为了;statement of purpose 目的陈述
      • 例句:Bob was absent from school on purpose. 鲍勃故意逃学了。
    • variety [ və’raɪəti ] n. 品种,种类;变化,多样化
      • 词根记忆:vari (变化) + ety (表状态) → 有很多变化 → 品种;变化
      • 常见搭配:a variety of 许多
      • 例句:The places we will visit offer a variety of shopping. 我们将参观的地方可提供各种购物活动。
    • immigration [ ,ɪmɪ’greɪʃn ] n. 外来的移民;移居 (入境)
      • 词根记忆:im (向内) + migr (迁移) + ation (表行为) → 向内迁移 → 移居 (入境)
      • 常见搭配:immigration law 移民法;immigration controls 移民管制;immigration application 移民申请
      • 例句:There will be stricter controls on immigration into the country. 以后要移民到这个国家会面临更严格的管制。
    • natural [ ‘nætʃrəl ] adj. 正常的;普通的;自然的;自然界的,天然的;天赋的;固有的
      • 常见搭配:natural disaster 自然灾害;natural scenery 自然景观
      • 例句:It’s natural for an old man of his age to be so calm. 处于他这般年纪的老人如此镇静是很自然的。
    • bet [ bet ] v. 赌,打赌 n. 打赌,赌注
      • 常见搭配:bet on 打赌;赌博;bet with sb. 和……打赌
      • 例句:Anyway I bet there are some things you didn’t know about bananas. 不管怎样,我敢说对于香蕉,你肯定有不知道的地方。
    • consumer [ kən’sju:mə(r) ] n. 消费者;用户
      • 例句:Many consumers feel that government and business have taken on the environmental agenda. 许多消费者认为政府和商家已经将环保提上了日程。
      • 拓展:consumerism (n. 消费主义)
    • physician [ fɪ’zɪʃn ] n. 内科医生,医师
      • 词根记忆:physic (医学) + ian (表人) → 医师
      • 例句:The physician said smoking was harmful to my health. 那位医生说吸烟对我的健康有害。
    • equal [ ‘i:kwəl ] adj. 相等的;能胜任的 vt. 比得上
      • 例句:These four firms had equal levels of productivity. 这四家公司的生产力水平不相上下。
      • 拓展:equality (n. 同等,平等);equally (adv. 平等地,相等地)
    • resort [ ri’zɔ:t ] n. 求助;诉诸;(度假)胜地 vi. 求助;诉诸
      • 联想记忆:报告(report)上级,紧急求助(resort)
      • 例句:The east coast resorts are only a short twenty-minute drive from here. 从这儿开车去东海岸的度假胜地只需短短20分钟。
    • leadership [ ‘li:dəʃɪp ] n. 领导;领导层;领导能力
      • 联想记忆:leader (领导者) + ship (表身份) → 领导,领导层
      • 例句:Last week we talked about the most effective ways of leading meetings, and the advantages and disadvantages of different leadership styles. 上周,我们讨论了主持会议最有效的方法以及不同主持风格的利弊。
    • equity [ ‘ekwəti ] n. 公平,公正
      • 词根记忆:equi (相等) + ty (表状态) → 等价交换 → 公平,公正
      • 例句:The equity of the committee’s decision was accepted by everyone. 委员会所做决定的公正性得到了大家的一致认可。
    • excavate [ ‘ekskəveɪt ] v. 挖掘,开凿;(科学家或考古学家)发掘,挖出(古物等)
      • 词根记忆:ex (出) + cav (洞) + ate (使…) → 挖出洞 → 挖掘,开凿
      • 例句:Some large species of dung beetles originating from France excavate tunnels to a depth of approximately 30 cm below the dung pat. 一些源自法国的大型蜣螂品种会在粪堆下挖一条大约30厘米深的隧道。
    • nuclear [ ‘nju:kliə(r) ] adj. 核能的,原子能的
      • 联想记忆:nu + clear (清除) → 清除核危机 → 核能的
      • 例句:France derives three quarters of its electricity from nuclear power. 法国四分之三的电来自核能。
    • mutual [ ‘mju:tʃuəl ] adj. 相互的;共同的
      • 词根记忆:mut (改变) + ual (有…性质的) → 穷则思变,共同发展 → 相互的;共同的
      • 常见搭配:mutual understanding 相互理解;the principle of equality and mutual benefit 平等互惠的原则
      • 例句:They soon discovered a mutual interest in music. 很快,他们发现他们对音乐有着共同的兴趣。
    • density [ ‘densəti ] n. 密集;浓度,密度
      • 常见搭配:population density 人口密度;the density of settlement 居住密度
      • 例句:The house has some high-density insulation materials in the roof. 房子的屋顶采用了一些高密度的绝缘材料。
    • massive [ ‘mæsɪv ] adj. 大而重的,厚实的,粗大的;大量的,大规模的
      • 例句:In Tokyo builders are planning a massive underground city to be begun in the next decade. 在东京,建筑者们正计划在接下来的十年里建造一个大型地下城。
    • congratulate [kən’grætʃəleɪt] vt. 祝贺
      • 词根记忆:con (共同) + grat (令人高兴的) + ulate → 同喜同贺 → 祝贺
      • 例句:I wanted to congratulate you all on having passed the exam. 大家都通过了考试,我向你们表示祝贺。
    • companion [kəm’pænjən] n. 共事者;同伴,伙伴
      • 词根记忆:com (共同) + pan (面包) + ion → 共享面包的人 → 共事者;同伴,伙伴
      • 例句:Tom was my only Chinese companion during my stay in Switzerland. 汤姆是我在瑞士期间唯一的中国伙伴。
    • rig [rɪg] vt. 操纵、垄断 n. 船桅 (或船帆等) 的装置;成套器械
      • 联想记忆:挖 (dig) 个洞把器械 (rig) 藏起来
      • 例句:The defeated candidate claimed that the election was rigged. 落选的候选者称选举被人操纵了。
    • input [‘ɪnpʊt] n. 投入,输入;输入的数据 vt. 把……输入计算机
      • 来自词组 put in (投入;输入)
      • 例句:The intensity of farming in the rich world should decline, and the use of chemical inputs will diminish. 富裕国家应该降低农业生产的密度,这样化学药品的使用将会减少。
    • merely [‘mɪəlɪ] adv. 仅仅,只不过
      • 例句:You don’t have to be present. Merely send a letter of explanation. 你不一定非出席不可,只要寄封信去说明一下就可以了。// I meant it merely as a joke. 我只不过是开个玩笑。
        impart [ɪm’pɑ:t] vt. 给予,赋予;传授;告知,透露
      • 常见搭配:impart knowledge 传授知识
      • 例句:He told me not to impart it to anyone without his own permission. 他告诉我,未经他人允许不可以把此事透露给任何人。
    • forfeit [‘fɔ:fɪt] v. (因犯规等而) 丧失,失去 n. 罚款;代价
      • 联想记忆:for (因为) + feit (看作词根 fect,做) → 因为做错事,所以被处罚 → 罚款
      • 例句:He forfeited his driving licence because he drove after drinking. 他因酒后驾车被没收了驾照。
    • counteract [ˌkaʊntər’ækt] v. 抵消;对抗;中和
      • 联想记忆:counter (相反) + act (动作) → 做相反的动作 → 抵消
      • 例句:Antacid will counteract the excess acid in your stomach. 解酸剂会中和胃里剩余的酸。
    • ventilation [ˌventɪ’leɪʃn] n. 空气流通;通风设备,通风方法
      • 来自 ventilate (vt. 使通风)
      • 例句:rooms with good ventilation 通风良好的房间;ventilation system 通风系统
      • 例句:In the noisiest areas mechanical ventilation will have to be installed in the exterior walls. 在噪音最大的区域,必须在外墙安装机械通风设备。
    • intermediate [ˌɪntɜː’miːdiət] adj. 中间的,中级的 n. 中间物
      • 词根记忆:inter (在……之间) + medi (中间) + ate (具有……的) → 中间的
      • 例句:Students who have reached an intermediate level benefit from learning general English skills. 那些英语水平达到中级的学生从英语综合技能学习中受益匪浅。
    • eternal [ɪ’tɜːnl] adj. 永恒的
      • 联想记忆:外部 (external) 世界是永恒的 (eternal) 诱惑
      • 例句:The supersession of the old by the new is a general, eternal and inviolable law of the universe. 新旧更迭是宇宙间普遍的、永恒的、不可违背的规律。
      • 拓展:endless (adj. 无止境的);everlasting (adj. 永恒的,持久的);permanent (adj. 永久的,持久的)
      • 反义词:momentary (adj. 瞬间的,刹那的);temporary (adj. 暂时的)
    • invasion [ɪn’veɪʒn] n. 入侵,侵略
      • 词根记忆:in (进入) + vas (走) + ion (表动作) → 走进来 → 入侵,侵略
      • 例句:The freedom from foreign invasion enables a country to develop its natural resources steadily. 不受外来侵略可以使一国的自然资源得到稳定的开发。
    • nevertheless [ˌnevədə’les] adv. 尽管如此;然而,不过
      • 例句:He was tired. Nevertheless he kept on working. 他累了,不过他仍然继续工作。
        celebrate* [‘selɪbreɪt] v. 赞扬,歌颂;庆祝
      • 例句:The architectural style celebrated scientific and engineering achievements by openly parading the sophisticated techniques used in construction. 该建筑风格通过公开展示建筑中使用 的先进技术来歌颂科学和工程方面的成就。
      • 拓展:celebration (n. 庆祝,庆典)
    • inspiring [ɪn’spaɪərɪŋ] adj. 鼓舞 (或激励) 人心的;启发灵感的
      • 词根记忆:in (使……) + spir (呼吸) + ing (令人……的) → 使呼吸的 → 鼓舞人心的
      • 例句:an inspiring story 一个激励人心的故事
      • 例句:King was an inspiring tutor. 金是一位激励人心的导师。
    • attendance [ə’tendəns] n. 到场,出席;出勤;伺候,照料
      • 来自 attend (v. 出席;照顾,护理)
      • 常见搭配:take attendance 点名;attendance record 考勤记录
      • 例句:I do not think there is a need to make class attendance every day at universities. 我认为在大学里没必要每天点名。
    • optional [‘ɒpʃənl] adj. 可选择的,非强制的,随意的;选修的
      • 联想记忆:option (选择) + al → 可选择的
      • 例句:an optional course 一门选修课
      • 例句:Is geography an optional course, or does everyone have to learn it? 地理是选修课吗,还是每个人都必须学习它?
      • 拓展:option (n. 选择;选择权)
        heal [hiːl] v. 治愈,康复;调停
      • 例句:The wound took months to heal. 这个伤口花了几个月才愈合。
    • enable [ɪ’neɪbl] vt. 使能够,使成为可能
      • 词根记忆:en (使……) + able (能够的) → 使能够,使成为可能
      • 常见搭配:enable sb. to do sth. 使……能够做……
      • 例句:The bank service enables you to withdraw cash from your account at any time. 这种银行服务使你能随时从账户中取钱。
    • dismantle [dɪs’mæntl] vt. 拆除;废除,取消
      • 联想记忆:dis (去掉) + mantle (覆盖) → 拆除;废除
      • 例句:Most people have been forced to dismantle their individualistic homes and return to more conventional lifestyles. 大多数人被迫拆除了他们极具个性的房子,回归更加传统的生活方式。
    • wage [weɪdʒ] n. 工资;[常 pl.] 报酬
      • 例句:In this company, wages are paid at the end of every month. 这家公司每到月末发工资。
        landscape [‘lændskeɪp] n. 风景,景色 vt. 对……作景观美化,美化 (自然环境等)
      • 例句:The landscape on the altiplano is the most beautiful I’ve ever seen. 高原上的风景是我所见过的最美的景色。
    • emotion [ɪ’moʊʃn] n. 感情;情绪
      • 词根记忆:e (出) + mot (动) + ion (表状态) → 心里释放出的东西 → 感情;情绪
      • 例句:Doing sports can be a good way to release suppressed emotion. 做运动是一个释放压抑情绪的好方法。
        commonwealth [‘kɒmənwelθ] n. 联邦;联合体;(the Commonwealth) 英联邦
      • 组合词:common (共同的) + wealth (财富) → 共创财富 → 联合体
      • 例句:George was a scientist at the Commonwealth Scientific and Industrial Research Organisation at that time. 那时乔治是 (澳大利亚) 联邦科学与工业研究组织的一名科学家。
    • newsletter [‘nju:zletər] n. 时事通讯,业务通讯
      • 组合词:news (消息,新闻) + letter (文字) → 时事通讯
      • 例句:There’s David West who has volunteered to be the club secretary, and one of the many jobs he will have is to send out newsletters to you regularly. 那位就是戴维·韦斯特,他自愿做俱乐部秘书,他有许多工作,其中之一就是定期给你们分发时事通讯。
    • periodical [ˌpɪəri’ɒdɪkl] n. 期刊,杂志 adj. 周期的,定期的
      • 来自 period (n. 时期,周期)
      • 例句:The professor’s paper has been issued in the latest periodical. 教授的论文已经发表在最新的一期期刊上。
      • 拓展:periodically (adv. 周期地;定期地,按时地)
      • 拓展:journal (n. 定期刊物,杂志);magazine (n. 杂志,期刊)
    • receptionist [rɪ’sepʃənɪst] n. 接待员
      • 例句:The patient goes to a receptionist and ask her about his disease. 那个病人走向接待员,向她询问他的病情。
    • security [sɪ’kjʊərəti] n. 安全,保障;抵押品;[pl.] 证券
      • 来自 secure (v. 保护,使安全;获得)
      • 常见搭配:security check 安全检查;job security 工作保障
      • 例句:Such insurance plan can offer your families financial security in the event of the loss of properties. 这种保险计划可以在你失去财产的情况下向你的家人提供经济保障。
    • clip [klɪp] n. (弹簧) 夹子;回形针;别针;弹夹;修剪;剪报;电影 (或电视) 片段 v. (用夹子、回形针等) 夹住,扣住;剪,修剪
      • 常见搭配:clip on 用夹子夹住
      • 例句:It is said that the U.S. produces 300 million aluminium paper clips each day. 据说美国每天生产三亿个铝制回形针。
      • 拓展:clutch (v. 抓住)
    • apace [ə’peɪs] adv. 快速地,急速地
      • 例句:The research went on apace. 研究进展迅速。
    • yield [jiːld] n. 产量 v. 出产 (作物);产生 (收益、效益等);放弃,屈服
      • 常见搭配:yield up 放纵;yield to 向……屈服;boost crop yield 提高作物产量
      • 例句:Higher yields have been achieved by increased irrigation and better crop breeding. 已通过增加灌溉和提高育种质量实现了产量的增长。
    • fair [feə(r)] adj. 公平的;合理的 adv. 公正地;公平合理地 n. 商品交易会;展销会
      • 例句:This part of the island is warmer than the southern half, but it wouldn’t be fair to say that the southern half is cold. 小岛的这部分区域比它的南部区域暖和,但是因此说南部冷是不合理的。
      • 拓展:unfair (adj. 不公平的)

    3. 无重复字符的最长子串

    题目

    3. 无重复字符的最长子串

    给定一个字符串 s ,请你找出其中不含有重复字符的 最长 

    子串

     的长度。

    示例 1:

    输入: s = "abcabcbb"
    输出: 3 
    解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
    

    示例 2:

    输入: s = "bbbbb"
    输出: 1
    解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
    

    示例 3:

    输入: s = "pwwkew"
    输出: 3
    解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
         请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
    

    提示:

    • 0 <= s.length <= 5 * 104
    • s 由英文字母、数字、符号和空格组成

    解答

    滑动窗口的思路

    class Solution {
        func lengthOfLongestSubstring(_ s: String) -> Int {
            var charMap: [Character: Int] = [:]
            var start = 0
            var maxLength = 0
    
            let count = s.count
            
            for i in 0..<count {
                // 1、取出当前字符
                let char = s[s.index(s.startIndex, offsetBy: i)]
    
                // 2、如果这个字符已经在字典里,取出他上次的位置,更新起点为上次出现的位置的后面
                if let lastIndex = charMap[char], start <= lastIndex {
                    start = lastIndex + 1
                }
    
                // 3、更新到字典里
                charMap[char] = i
    
                // 4、计算新的长度为当前index-起点,并更新长度
                maxLength = max(maxLength, i - start + 1)
    
            }
    
            return maxLength
        }
    }

    2. 两数相加

    题目

    2. 两数相加

    给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

    请你将两个数相加,并以相同形式返回一个表示和的链表。

    你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

    示例 1:

    输入:l1 = [2,4,3], l2 = [5,6,4]
    输出:[7,0,8]
    解释:342 + 465 = 807.
    

    示例 2:

    输入:l1 = [0], l2 = [0]
    输出:[0]
    

    示例 3:

    输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
    输出:[8,9,9,9,0,0,0,1]
    

    提示:

    • 每个链表中的节点数在范围 [1, 100] 内
    • 0 <= Node.val <= 9
    • 题目数据保证列表表示的数字不含前导零

    解答

    /**
     * Definition for singly-linked list.
     * public class ListNode {
     *     public var val: Int
     *     public var next: ListNode?
     *     public init() { self.val = 0; self.next = nil; }
     *     public init(_ val: Int) { self.val = val; self.next = nil; }
     *     public init(_ val: Int, _ next: ListNode?) { self.val = val; self.next = next; }
     * }
     */
    class Solution {
        func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
            /*
            哑:哑节点开头,哑节点固定,结果返回哑节点的后继
            标:标识两个遍历的节点和一个生成的新节点
            移:while循环向前走
            进:无法移动后再处理进位
            */
            let dummyHead = ListNode()
            var currentNode = dummyHead
            var carry = 0
    
            var p = l1
            var q = l2
    
            while p != nil || q != nil {
                /*
                只要不是都走到尽头,都要进入这里
                */
                let pValue = p?.val ?? 0
                let qValue = q?.val ?? 0
                var sum = pValue + qValue + carry
    
                // 1、是否要进位
                carry = sum / 10
                // 2、新节点的后继(value为当前相加取余)
                currentNode.next = ListNode(sum % 10)
    
                // 3、往后移动
                currentNode = currentNode.next!
                p = p?.next
                q = q?.next
            }
    
            /*
            两个单链表都走到尽头了,如果存在进位,要追加一个后继
            这个节点的value只能是1,实际是最高位
            */
            if carry > 0 {
                currentNode.next = ListNode(carry)
            }
    
            return dummyHead.next
            
        }
    }

    1. 两数之和

    题目

    1. 两数之和

    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

    你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

    你可以按任意顺序返回答案。

    示例 1:

    输入:nums = [2,7,11,15], target = 9
    输出:[0,1]
    解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
    

    示例 2:

    输入:nums = [3,2,4], target = 6
    输出:[1,2]
    

    示例 3:

    输入:nums = [3,3], target = 6
    输出:[0,1]
    

    提示:

    • 2 <= nums.length <= 104
    • -109 <= nums[i] <= 109
    • -109 <= target <= 109
    • 只会存在一个有效答案

    题解

    class Solution {
        func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
            /*
            从头开始遍历,看剩下的数里是否有相加等于目标数
            时间复杂度: O(n2)
            优化:
                遍历一次数组,生成一个字典[int: int],
                valueForKey来找index
            */
    
            let count = nums.count
            if count == 0 {
                return []
            }
            for index in 0..<count {
                let currentValue = nums[index]
                for targetIndex in 0..<count {
                    if index == targetIndex {
                        continue
                    }
                    let targetValue = nums[targetIndex]
                    if currentValue + targetValue == target {
                        return [index, targetIndex]
                    }
                }
            }
            return []
        }
    }

    腾讯云搭建网站

    刚刚完成一半,简单记录目前的流程,等后续备案完成后再梳理一遍

    一、本地开发静态站点

    • VS Code
    • 创建站点
    • 编辑以下:
      • html
      • css
      • js

    二、购买腾讯云轻量应用服务器(Lighthouse)

    • 四百多一年即可

    三、使用FTP客户端连接到云服务器

    • 公有IP
    • 端口号 21
    • user name
    • password
    • 很重要的是在连接过程中会使用到3000-4000这个范围内的端口,需要在云服务器的防火墙开启这些端口,不然本地电脑会连不上云服务器的FTP

    四、备案

    • 明天继续写