Flutter岗位(iOS方向)面试问题复盘

背景

面试的是一家做智能硬件的科技公司,技能要求除了音视屏、iOS/Flutter性能相关技术外,更多的还关注个人对常用开发技巧是否有探索性,下面列举几个在开发中我比较容易忽略的技能点。

面试题
  • 问题1:「同一个项目中如何跑多个域名」
    答:
    • 问题的本质:这个问题真正考量的是怎么让马甲包不被机审干掉。
    • 错误的回答:我的回答是:新增Target->不同的BundleID->按ID选择不同的baseUrl, 做文件和代码的混淆,这个回到方向是对的,但是没有讲透,我的回答是一套「一套代码跑多个包」的标准做法,项目混淆做马甲包,会被机审判别为马甲包而触发Guideline 4.3(重复应用)。且混淆会增加测试的工作量,如果在同一个工程里手动改代码混淆,每个包都要重新全量回归 → 成本爆炸。
    • 核心矛盾:审核4.3相似度检测主要看这些维度
      1.1、二进制代码相似度(Mach-o里的类名,方法名,函数调用)
      1.2、资源文件Hash(图片、xib、storyboard、json)
      1.3、符号表/字符串常量
      1.4、UI结构和页面跳转的关系
      1.5、账号、ip、上传设备指纹等关联信息。

多 Target 方案的代码是 100% 相同的,只是编译期换了 BundleID 和几个常量。机审一扫,代码指纹几乎一致 → 秒判马甲。「切域名」只是最表层需求,真正的难点是让每个包看起来像不同团队独立开发的 App。

正确回答:行业中最新的做法--用自动化的等价变换换掉代码指纹,而不是通过人肉去更改。面试官想知道的是自动化「同构异性」,也就是脚本生成内容相同,文件和函数不同的项目—— 这是马甲包工作室的核心资产。逻辑不变、指纹全换

// 自动化异构前
 class LoginViewController: UIViewController {

// 自动化异构后
 class AbcXyzWidget: UIViewController {
      // 逻辑完全一致,只换指纹
// 自动化异构前
     func handleLogin() {
// 自动化异构后
     func p9x_doZk() {   // + 插入垃圾函数、打乱顺序

// 自动化异构前
        let url = "https://api.a.com"

// 自动化异构后
         let url = decrypt(kX9)  // 字符串加密
      }
  }

所以问题的本质是两个层面:
1.1、多环境切换域名
单纯的多域名/多环境的切换可以用Target+xcconfig+编译宏来区分,运行时按照配置选择baseUrl,这是标准的多环境方案。
1.2、真正的马甲包(避免4.3审核指南)
但要上多个马甲包过审,多个Target共用一份二进制会被相似度检测为重复应用,当前行业的做法「用脚本对母工程做自动化 同构异形」,批量重命名类、方法、插入无用的代码、加密字符串、资源改哈希、参数化换壳,因为都是语义上的等价变换,只需要回归母包一次,批量生成马甲包的默认功能一致的,不需要额外增加测试量。

  • 问题2、「iOS和flutter项目中多语言配置阿拉伯语,是如何进行适配的?」
    答:
    iOS端原生适配阿拉伯语:

1、首先和其他地区的文本配置一样,创建ar.lproj本地化文件夹,再放置到 Localizable.strings中。
在iOS中Auto Layout 布局适配天然支持RTL(right-to-left),核心规则:1、不要用left/right,而是用leading/trailing替代,2、leading在LTR语言中表示left,但在RTL语言中等于right
第三方库SnapKit用法和原理是一样的。

总结:iOS对RTL布局提供了系统级的支持,开发者的工作就是正确使用API和布局方式,让系统自动完成翻转。
1、布局基础:使用Auto Layout 或SnapKit的语义约束,关键一步是用leading和trailing属性来替代left和rignt,当系统语言切换到阿拉伯语时,leading会从左边变为右边,trailing也会相应的变化,从而实现自动翻转。

Flutter端原生适配阿拉伯语:
Flutter对RTL的支持也同样出色,它的设计理念就是“适配框架”,开发者只需要遵循规范即可。
1、文本和布局方向:Flutter是通过Directionality组件来控制文本和布局方向的,它会根据当前的语言环境,自动设置textDirection.rtl,当设置是阿拉伯语言时,MaterialApp会自动实现布局的自动翻转。
2、在Row和Column组件中MainAxisAlignment.start/end 和 CrossAxisAlignment.start/end 来代替 left/right。

总结:无论是leading/trailing和left/right还是flutter中的start/end和left/right他们的区别都是坐标系的不同。

  • 问题3、如何处理Flutter项目集成到iOS端在启动时出现白屏或首次加载Flutter页面时出现白屏的问题
    答:
    这是一个景点的Flutter混合开发问题,白屏的本质是Flutter Engine初始化和首帧渲染之前的时间差造成的。

白屏的两个阶段:
1、Engine初始化时白屏:
从创建FlutterEngine或FlutterViewController到Engine就绪,Flutter还没有渲染的能力,Native侧也没有提供任何的占位试图,所以显示系统默认的白色背景。
2、首帧渲染前的闪白
Engine就绪后Flutter首帧上屏之间,如果Flutter侧runApp()之前有任何的耗时操作,会延长这段空白。

解决方式:
方案1、预初始化FlutterEngine,在AppDelegate中提前创建并预热Engine

// AppDelegate.swift
import Flutter

@main
class AppDelegate: FlutterAppDelegate {
    // 持有 Engine 防止被释放
    lazy var flutterEngine = FlutterEngine(name: "my_flutter_engine")
    
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // 启动时预热 Engine
        flutterEngine.run()
        // 注册插件(如果有)
        GeneratedPluginRegistrant.register(with: flutterEngine)
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

方案2:使用Flutter闪屏(Splash screen),在Eengine初始化期间显示一个和FlutterUI一致的占位图。首帧渲染完成后再移除闪屏。

问题4:动态库和静态库的区别
答:
1、链接的时机不一样:静态库在编译链接阶段合并到主程序的,动态库是在运行时动态加载的
2、静态库代码会被完整的赋值到每个使用到它的可执行文件中,而动态库只会存储一份,多进程共享内存中的同一份副本。
3、静态库会导致App体积增大,但启动无需额外的加载,动态库体积小,动态库会增加启动加载的开销,切节省运行时内存
4、静态库常见以.a.framewofk。动态库常见以.framewofk/.dylib/.tbd

问题5:iOS 中如何 防止第三方库重复导入冲突,比如一个项目手动导入AFN,我的SDK是pod集成AFN,版本不一致,如何解决这个冲突
答:
核心的矛盾点是两个相同的符号在链接阶段的冲突,同时运行可能加载的错误的版本导致API不兼容。
方案一:符号重命名,将SDK内部使用的AFN所有类名、函数名、重命名,从源头消除冲突。比如使用前缀重命名(脚本自动化批量重命名)。
方案二:framework隔离,将SDK打包成动态库Dynamic Framework,运行各自加载自己的版本,无不干扰。

问题6:iOS如何处理让build编译得更快
答:iOS中构建时的编译速度的系统性方法,涵盖了从配置项到架构优化的各个层面的优化。
1、build setting项调优
需要在Debug环境下添加以下配置项:

  • SWIFT_COMPILATION_MODE设置为Incremental(只重编译变更文件,发布时用 Whole Module 做全量优化)。
  • DEBUG_INFORMATION_FORMAT设置为dwarf(也就是不带.DSYM,跳过DSYM的生成,可以减少Debug的构建时间)
  • SWIFT_OPTIMIZATION_LEVEL设置为none(不做耗时的优化,编译就会变得很快)
    2、依赖和模块化
  • 1、二进制化第三方库:对稳定不变的库 可以使用:binary => true。
  • 2、模块化拆分,模块化解耦可最大化并行编译,不过要避免过深的依赖和循环依赖。
    3、 构建缓存和产物的复用
  • DerivedData的管理:不要频繁去清理DerivedData。
  • 预编译头文件:oc项目中合理去使用PCH,减少头文件的重复解析。
    4、构建阶段的优化
  • 清理 Build Phases 脚本:将脚本从 Run Script阶段迁移到CI/CD阶段,或者是手动触发。
  • 资源的处理:合并大量小图片,减少不必要的资源复制阶段。

问题6:IAP和ApplePay的实现步骤

  • IAP
// ============================================================================
//  【完整实现思路总览 —— 含非代码步骤】
// ============================================================================
//
//  一、App Store Connect / 账号侧(非代码,必须先做)
//  --------------------------------------------------------------------------
//  1. 在 Apple Developer 开通「Paid Applications Agreement」(付费应用协议),
//     并填好银行/税务/联系信息。否则商品会一直「缺少元数据/不可用」。
//  2. 在 App Store Connect 创建 App(Bundle ID 必须与 Xcode 工程一致,
//     本工程示例:com.yeahka.xTool)。
//  3. 进入 App →「功能 / App 内购买项目」创建商品:
//     - 消耗型:如 com.yeahka.xTool.coins.100
//     - 非消耗型:如 com.yeahka.xTool.removeads
//     - 自动续期订阅:先建「订阅群组」,再建订阅商品
//  4. 每个商品填写:参考名称、价格、本地化描述、审核截图等,并提交审核
//     (商品可随版本审,也可单独审,视后台状态而定)。
//  5. 创建 Sandbox 测试账号:用户与访问 → 沙盒 → 测试员。
//     真机测试时:设置 → App Store → 沙盒账户 登录(不要在系统 iCloud 里登沙盒号)。
//  6. 若做订阅:配置订阅群组、等级、免费试用/推介促销(可选)。
//  7. (强烈建议)配置 App Store Server Notifications V2 回调地址,
//     用于服务端感知退款、续订失败、宽限期等,避免只靠客户端。
//
//  二、Xcode 工程侧(非业务代码,但要配)
//  --------------------------------------------------------------------------
//  1. Signing & Capabilities → + In-App Purchase。
//  2. Bundle Identifier 与 Connect 一致。
//  3. 真机调试(模拟器对 IAP 支持不完整,建议真机 + Sandbox)。
//  4. 部署目标:本文件使用 StoreKit 1(SKPaymentQueue),兼容 iOS 11+。
//     若最低版本 ≥ iOS 15,可逐步迁移 StoreKit 2(Product / Transaction)。
//
//  三、客户端代码主流程(本类负责)
//  --------------------------------------------------------------------------
//  步骤 A:App 启动最早时机 addTransactionObserver
//          —— 这是防漏单的第一道防线。未 finish 的交易会在下次启动再次回调。
//  步骤 B:请求商品信息 SKProductsRequest(拿价格、本地化标题)
//  步骤 C:发起购买 SKPaymentQueue.add(SKPayment)
//  步骤 D:在 updatedTransactions 里根据 state 分支处理
//  步骤 E:purchased / restored → 落本地 pending → 服务端验票 → 发奖
//  步骤 F:发奖成功后才 finishTransaction,并删除本地 pending
//  步骤 G:启动/回前台扫描本地 pending + 系统未完成交易,做补单
//
//  四、「漏单」与「付了没发奖」分别怎么防
//  --------------------------------------------------------------------------
//  【系统漏单】钱扣了,交易还在 SKPaymentQueue,但 App 没处理完:
//    → 启动就监听队列;永不在发奖成功前 finish。
//  【业务漏发】验票/发奖失败,或 finish 后发奖失败:
//    → 本地 IAPPendingOrderStore 持久化 + 重试;
//    → 服务端以 transactionId 幂等,允许安全重试。
//
//  五、绝对不要做的事
//  --------------------------------------------------------------------------
//  - 不要在客户端用「自己算的」成功状态直接发高价值道具而不验票;
//  - 不要支付成功回调里立刻 finishTransaction;
//  - 不要忽略 restored / 启动时队列里已有的交易;
//  - 不要用生产环境收据打 Sandbox 校验地址(服务端要区分环境)。
//
// ============================================================================
  • ApplePay
// ============================================================================
//  【Apple Pay 实现思路 —— 含非代码步骤】
// ============================================================================
//
//  重要概念区分:
//  - IAP(StoreKit):卖「数字内容/权益」(金币、会员、去广告),苹果抽成。
//  - Apple Pay(PassKit):卖「实物/服务」等(电商、线下核销),钱走你的收单行,
//    苹果不按 IAP 抽成。二者不要混用,审核会拒。
//
//  一、非代码准备
//  --------------------------------------------------------------------------
//  1. Apple Developer → Certificates, Identifiers & Profiles → Identifiers
//     → 选中 App ID → 勾选 Apple Pay。
//  2. 创建 Merchant ID(例如 merchant.com.yeahka.xTool)。
//  3. 为 Merchant ID 创建 Payment Processing Certificate
//     (通常由支付服务商/收单行提供 CSR,或按其文档生成)。
//  4. Xcode → Signing & Capabilities → + Apple Pay,勾选对应 Merchant ID。
//  5. 与支付网关签约(Stripe / 银联 / Adyen / 自有收单等),拿到:
//     - merchantId
//     - 后端解密 paymentToken 的能力
//     - 支持的银行卡网络(Visa/MasterCard/UnionPay...)
//  6. 真机测试:设备需登录钱包并添加可用卡;模拟器能力有限。
//
//  二、代码主流程
//  --------------------------------------------------------------------------
//  1. PKPaymentAuthorizationController.canMakePayments 判断设备是否可用;
//  2. 组装 PKPaymentRequest(币种、国家、商户号、账单明细);
//  3. present 授权页,用户用 Face ID / 侧边按钮确认;
//  4. didAuthorizePayment 拿到 payment.token,发给你们服务端;
//  5. 服务端把 token 交给收单行扣款,返回成功/失败;
//  6. 用 completion(.success / .failure) 告诉系统结果,关闭授权页。
//
//  三、安全注意
//  --------------------------------------------------------------------------
//  - paymentToken 只能服务端处理,客户端不要尝试「自己解析扣款」;
//  - 金额以服务端二次校验为准,防止被篡改 request;
//  - 授权成功 ≠ 你的业务订单完成,要以收单行结果为准再发货/发物流。
//
// ============================================================================

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容