普通视图

发现新文章,点击刷新页面。
今天 — 2025年12月14日首页

老司机 iOS 周报 #360 | 2025-12-15

作者 ChengzhiHuang
2025年12月14日 20:53

老司机 iOS 周报 #360 | 2025-12-15

ios-weekly
老司机 iOS 周报,只为你呈现有价值的信息。

你也可以为这个项目出一份力,如果发现有价值的信息、文章、工具等可以到 Issues 里提给我们,我们会尽快处理。记得写上推荐的理由哦。有建议和意见也欢迎到 Issues 提出。

文章

🌟 Teaching AI to Read Xcode Builds

@zhangferry:Xcode 原始构建日志对人和 AI 都不够友好,仅提供扁平信息输出。作者通过截获 Xcode 与 SWBBuildService 的通信,挖掘出日志之外的结构化数据,包括构建依赖、详细耗时等核心信息,现在随着 swift-build 的开源,可以更系统的了解这些构建信息,利用它们可以实现这些功能:

  • 精准排错:链接错误源于 NetworkKit 模块构建失败,其依赖的 CoreUtilities 正常,问题仅集中在 NetworkKit 本身
  • 慢构建分析:47 秒构建中仅 12 秒是实际编译,其余时间用于等待代码签名;8 核 CPU 仅达成 3.2 倍并行,XX 模块是主要瓶颈
  • 主动提醒:近一个月构建时间上涨 40%,与 Analytics 模块新增 12 个 Swift 文件相关
  • 自然语言查询:支持 “上次构建最慢的模块是什么?”“上周比这周多多少警告?” 等直接提问

利用这项能力对 Wikipedia iOS 应用完成 “体检”,找到多个编译耗时瓶颈,AI 还结合结果给出了模块拆分、并行处理的优化建议。

未来潜力更值得期待:若 Apple 官方支持实时获取构建消息,AI 可在构建中途发现异常(比如 “某模块比平时慢 2 倍,是否暂停检查?”),还能实时监控 CI 构建进度,甚至自动修复问题。作者基于 swift-build 开发了 Argus,已经实现部分实时功能。

🐕 豆包手机为什么会被其他厂商抵制?它的工作原理是什么?

@EyreFree:豆包手机因采用底层系统权限实现 AI 自动化操作,遭微信、淘宝等厂商抵制。其核心工作原理为:通过 aikernel 进程与 autoaction APK 协同,利用 GPU 缓冲区读取屏幕数据、注入输入事件,借助独立虚拟屏幕后台运行,无需截屏或无障碍服务,还能绕过部分应用反截屏限制。AI 操作主要依赖云端推理,本地每 3-5 秒向字节服务器发送 250K 左右图片,接收 1K 左右操作指令。这种模式虽提升自动化效率,但存在隐私安全隐患,易被灰产利用,且冲击现有移动互联网商业逻辑,相关规范与监管仍需完善。感兴趣的同学可以结合视频 【老戴】豆包手机到底在看你什么?我抓到了它的真实工作流程 一起看看。

🐢 How we built the v0 iOS app

@含笑饮砒霜:Vercel 首款 iOS 应用 v0 的移动端负责人 Fernando Rojo 详细分享了应用的构建过程:团队以角逐苹果设计奖为目标,经多轮试验选定 React Native 和 Expo 技术栈,核心聚焦打造优质聊天体验,通过自定义钩子(如 useFirstMessageAnimation、useMessageBlankSize 等)、依赖 LegendList 等开源库实现消息动画、空白区域处理、键盘适配、漂浮作曲家等功能,解决了动态消息高度、滚动异常、原生交互适配等难题;同时在 Web 与原生应用间共享类型和辅助函数,通过自研 API 层保障跨端一致性,优先采用原生元素并针对 React Native 原生问题提交补丁优化;未来团队计划开源相关研究成果,持续改进 React Native 生态。

🐎 Opening up the Tuist Registry

@Kyle-Ye:Tuist Registry 宣布完全开放,无需认证或创建账户即可使用。作为 Swift 生态首个完全开放的 Package Registry,目前已托管近 10,000 个包和 160,000+ 个版本。使用 Registry 的团队可获得高达 91% 的磁盘空间节省(从 6.6 GB 降至 600 MB),CI 缓存恢复时间从 2 分钟缩短至 20 秒以内。开发者只需运行 tuist registry setupswift package-registry set 命令即可配置,支持标准 Xcode 项目、Tuist 生成项目和 Swift Package。未认证用户每分钟可发起 10,000 次请求,对于大多数项目已足够使用。

🐕 Initializing @Observable classes within the SwiftUI hierarchy

@AidenRao:本文探讨了在 SwiftUI 中正确初始化和管理 @Observable 对象的几种模式。作者通过清晰的代码示例,层层递进地讲解了使用 @State 的必要性、如何通过 .task 修饰符避免不必要的初始化开销,以及如何利用 environment 实现跨场景的状态共享。如果你对 @Observable 的生命周期管理还有疑惑,这篇文章会给你清晰的答案。

🐕 Demystifying the profraw format

@david-clang:本文深入剖析了 LLVM 用于代码覆盖率分析的 .profraw 二进制文件格式及其生成机制。其核心原理分为编译时插桩与运行时记录两步:

  1. 编译时,Clang 的 -fprofile-instr-generate 选项会在程序中插入计数器 (__profc_*) 和元数据 (__profd_*) 到特定的 ELF 节中,并在关键执行点插入 Load/Add/Store 指令进行实时计量。
  2. 运行时,LLVM 运行时库通过 atexit() 钩子,在程序终止时自动将内存中这些节的最终数据序列化到 .profraw 文件中。该文件依次包含 Header、Data 段(元数据)、Counters 段(执行次数)和 Names 段,完整记录了代码执行轨迹。

工具

🐕 CodeEdit

@JonyFang:CodeEdit 是一个面向 macOS 的开源代码编辑器项目,使用 Swift/SwiftUI 开发并由社区维护。README 将其定位为纯 macOS 原生编辑器,并以 TextEdit 与 Xcode 作为两端参照:在保持更简洁的使用体验的同时,按需扩展到更完整的编辑与开发能力,并遵循 Apple Human Interface Guidelines。项目目前处于开发中,官方提示暂不建议用于生产环境,可通过 pre-release 版本试用并在 Issues/Discussions 反馈。README 列出的功能包括语法高亮、代码补全、项目级查找替换、代码片段、内置终端、任务运行、调试、Git 集成、代码评审与扩展等。

代码

🐕 mlx-swift-lm

@Barney:MLX Swift LM 是一个面向 Apple 平台的 Swift 包 , 让开发者能够轻松构建 LLM 和 VLM 应用。通过一行命令即可从 Hugging Face Hub 加载数千个模型 , 支持 LoRA 微调和量化优化。提供 MLXLLM、MLXVLM、MLXLMCommon、MLXEmbedders 四个库 , 涵盖语言模型、视觉模型和嵌入模型的完整实现。开发者只需几行代码就能创建对话系统 , 所有功能通过 Swift Package Manager 便捷集成。

内推

重新开始更新「iOS 靠谱内推专题」,整理了最近明确在招人的岗位,供大家参考

具体信息请移步:https://www.yuque.com/iosalliance/article/bhutav 进行查看(如有招聘需求请联系 iTDriverr)

关注我们

我们是「老司机技术周报」,一个持续追求精品 iOS 内容的技术公众号,欢迎关注。

关注有礼,关注【老司机技术周报】,回复「2024」,领取 2024 及往年内参

同时也支持了 RSS 订阅:https://github.com/SwiftOldDriver/iOS-Weekly/releases.atom

说明

🚧 表示需某工具,🌟 表示编辑推荐

预计阅读时间:🐎 很快就能读完(1 - 10 mins);🐕 中等 (10 - 20 mins);🐢 慢(20+ mins)

Swift 6.2 列传(第十一篇):梅若华的执念与“浪子回头”的异步函数

2025年12月14日 20:18
0. 🐼楔子:量子纠缠与发际线的危机 在这个万物互联、元宇宙崩塌又重建的赛博纪元,大熊猫侯佩正面临着熊生最大的危机——不是由于长期熬夜写代码导致的黑眼圈(反正原本就是黑的),而是他引以为傲的头顶毛发密

粉笔与华图达成战略合作,双方将互相参股、成立合资公司

2025年12月14日 20:08
36氪获悉,12月14日,粉笔与华图山鼎联合宣布达成深度战略合作,将在服务深度、成本结构与业务拓展等方面推动资源整合。股权层面,双方合作包括但不限于战略投资或控股、互相参股、共同成立合资公司等措施,同时还将相互派驻董事,搭建常态化沟通桥梁。

安博通:筹划发行H股并在香港联交所上市

2025年12月14日 19:00
12月14日,安博通(688168.SH)公告称,公司拟在境外发行股份(H股)并在香港联合交易所有限公司上市。公司董事会同意授权公司管理层启动本次H股上市的前期筹备工作,授权期限为自董事会审议通过之日起12个月内。(每经网)

将flutter打成aar包嵌入到安卓

2025年12月14日 17:44

Flutter Module 打包成 AAR 并集成到 Android 项目

一、创建 Flutter Module

如果你 还没有 Flutter Module

flutter create -t module flutter_module

二、构建 Flutter AAR

执行 AAR 构建命令

flutter build aar

构建产物位置

flutter_module/build/host/outputs/repo/

构建控制台输出

在下面的配置中会用到

  1. Open <host>\app\build.gradle
  2. Ensure you have the repositories configured, otherwise add them:

      String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.googleapis.com"
      repositories {
        maven {
            url 'E:\lumosproject\module\lumos\build\host\outputs\repo'
        }
        maven {
            url "$storageUrl/download.flutter.io"
        }
      }

  3. Make the host app depend on the Flutter module:

    dependencies {
      debugImplementation 'com.example.lumos:flutter_debug:1.0'
      profileImplementation 'com.example.lumos:flutter_profile:1.0'
      releaseImplementation 'com.example.lumos:flutter_release:1.0'
    }


  4. Add the `profile` build type:

    android {
      buildTypes {
        profile {
          initWith debug
        }
      }
    }


三、Android 宿主项目集成 AAR

1. 修改MyApp/app/build.gradle

你目前现有的 Android 项目可能支持 mips 或 x86 之类的架构,然而,Flutter 当前仅支持 为 x86_64armeabi-v7a 和 arm64-v8a 构建预编(AOT)的库。

可以考虑使用 abiFilters 这个 Android Gradle 插件 API 来指定 APK 中支持的架构,从而避免 libflutter.so 无法生成而导致应用运行时崩溃,具体操作如下:

Groovy 版
android {
    defaultConfig {
        ndk {
            // Filter for architectures supported by Flutter
            abiFilters "armeabi-v7a", "arm64-v8a", "x86_64"
        }
    }
}
Kotlin DSL
android {
    defaultConfig {
        ndk {
            // Filter for architectures supported by Flutter
            abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86_64")
        }
    }
}

2. 配置 settings.gradle(.kts)

在国内,需要使用镜像站点代替 storage.googleapis.com。有关镜像的详细信息,参见 在中国网络环境下使用 Flutter 页面。

Groovy 版
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    String storageUrl = System.env.FLUTTER_STORAGE_BASE_URL ?: "https://storage.flutter-io.cn"
    repositories {

        google()
        mavenCentral()
        maven {
            url 'E:/lumosproject/module/lumos/build/host/outputs/repo'
        }
        maven {
            url "$storageUrl/download.flutter.io"
        }
    }
}
Kotlin DSL
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
        maven("https://storage.googleapis.com/download.flutter.io")
    }
}

3. 添加依赖(app/build.gradle)

这里添加的依赖都是build aar 控制台输出的内容

groovy版
dependencies {
   debugImplementation 'com.example.untitled:flutter_debug:1.0'
   profileImplementation 'com.example.untitled:flutter_profile:1.0'
   releaseImplementation 'com.example.untitled:flutter_release:1.0'
}
Kotlin DSL
dependencies {
debugImplementation("com.example.flutter_module:flutter_debug:1.0")    
releaseImplementation("com.example.flutter_module:flutter_release:1.0")   
add("profileImplementation", "com.example.flutter_module:flutter_profile:1.0") 
}

4. 添加profile build type(app/build.gradle)

buildTypes中添加

groovy版
profile {
    initWith debug
}
Kotlin DSL
create("profile") { initWith(getByName("debug")) }

四、Android 启动 Flutter 页面

1.在 AndroidManifest.xml 中添加 FlutterActivity

Flutter 提供了 FlutterActivity,用于在 Android 应用内部展示一个 Flutter 的交互界面。和其他的 Activity 一样,FlutterActivity 必须在项目的 AndroidManifest.xml 文件中注册。将下边的 XML 代码添加到你的 AndroidManifest.xml 文件中的 application 标签内

<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"   android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

2. 加载 FlutterActivity

确保使用如下的语句导入:

import io.flutter.embedding.android.FlutterActivity;
MyButton(onClick = {
    startActivity(
        FlutterActivity.createDefaultIntent(this)
    )
})

@Composable
fun MyButton(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Launch Flutter!")
    }
}

上述的例子假定了你的 Dart 代码入口是调用 main(),并且你的 Flutter 初始路由是 '/'。 Dart 代码入口不能通过 Intent 改变,但是初始路由可以通过 Intent 来修改。下面的例子讲解了如何打开一个自定义 Flutter 初始路由的 FlutterActivity

MyButton(onClick = {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
})

@Composable
fun MyButton(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Launch Flutter!")
    }
}

3. 使用缓存 Engine(推荐)

每一个 FlutterActivity 默认会创建它自己的 FlutterEngine。每一个 FlutterEngine 会有一个明显的预热时间。这意味着加载一个标准的 FlutterActivity 时,在你的 Flutter 交互页面可见之前会有一个短暂的延迟。想要最小化这个延迟时间,你可以在抵达你的 FlutterActivity 之前,初始化一个 FlutterEngine,然后使用这个已经预热好的 FlutterEngine

class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine

  override fun onCreate() {
    super.onCreate()

    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
使用
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}
为缓存的 FlutterEngine 设置初始路由
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine
  override fun onCreate() {
    super.onCreate()
    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)
    // Configure an initial route.
    flutterEngine.navigationChannel.setInitialRoute("your/route/here");
    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )
    // Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
通过设置路由的方式可以配置缓存引擎在执行 Dart 入口点之前使用自定义初始路由

五、Flutter 与 Android 通信

MethodChannel方式

Flutter

添加到合适的地方

static const methodChannel = MethodChannel('com.bluetoothCharacteristic');
methodChannel.setMethodCallHandler(_handleMethod);
Future<dynamic> _handleMethod(MethodCall call) async {
  switch (call.method) {
    case 'bluetoothCharacteristic':
      // 设置设备写入特征
      _device.setWriteCharacteristic(
        call.arguments as BluetoothCharacteristic,
      );
      break;
    default:
      throw PlatformException(code: 'Unrecognized Method');
  }
}

Android


private lateinit var methodChannel: MethodChannel
methodChannel = MethodChannel(
    flutterEngine.dartExecutor.binaryMessenger,
    "com.bluetoothCharacteristic"
)
使用
Button(
    onClick = {
        val intent = FlutterActivity
            .withCachedEngine("my_engine_id")
            .build(activity)
        methodChannel.invokeMethod(
            "bluetoothCharacteristic",
            "00002a37-0000-1000-8000-00805f9b34fb"
        );
        activity.startActivity(intent)
    },
    modifier = Modifier.padding(16.dp)
) {
    Text("跳转到flutter页面")
}

MethodChannel 中的 com.bluetoothCharacteristic 和methodChannel.invokeMethod中的bluetoothCharacterstic必须和 Flutter 中保持一致,否则接收不到数据。

EventChannel方式

EventChannel的使用方式大致和MethodChannel相同,我这里就把主要代码复制下来了。

class MainActivity : ComponentActivity() {
    private lateinit var flutterEngine: FlutterEngine
    private lateinit var eventChannel: EventChannel
    private var eventDataSink: EventChannel.EventSink? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        // 创建 FlutterEngine
        flutterEngine = FlutterEngine(this)
        //设置初始路由
        flutterEngine.navigationChannel.setInitialRoute("/settings")
        // 启动 Flutter 引擎
        flutterEngine.dartExecutor.executeDartEntrypoint(
            DartExecutor.DartEntrypoint.createDefault()
        )
        // 设置 EventChannel 用于实时数据传输
        eventChannel = EventChannel(flutterEngine.dartExecutor, "com.example.flutter_aar_demo/event_channel")
        eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
            override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
                eventDataSink = events
                startRealTimeDataTransmission()
            }
            override fun onCancel(arguments: Any?) {
                eventDataSink = null
            }
        })
        // 缓存 FlutterEngine
        FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine)
    }

我这里的安卓使用的是groovy的创建方式Kotlin DSL的方式是我直接从官网复制的代码可能有不正确的地方具体请查看将 Flutter module 集成到 Android 项目

零跑汽车:杜绝任何形式的价格欺诈和不正当竞争行为

2025年12月14日 18:00
国家市场监督管理总局发布《汽车行业价格行为合规指南(征求意见稿)》。14日,零跑汽车发文表示,积极响应《指南》倡导,杜绝任何形式的价格欺诈和不正当竞争行为,期待与行业同仁共同努力,维护公平理性的市场秩序,共筑中国汽车产业高质量发展。(财联社)

凌钢与上海钢联签署战略合作协议

2025年12月14日 17:00
近日,凌钢与上海钢联电子商务股份有限公司签署战略合作协议。根据协议,双方将聚焦三大核心方向:一是依托上海钢联数据优势,助力凌钢优特钢等拳头产品拓展市场;二是共建价格监测、供需分析等数字化服务体系,赋能凌钢生产经营决策;三是探索产业链延伸开拓等创新合作模式,通过数字赋能构建“采、产、销”一体化生态。(人民财讯)

长裕集团主板IPO12月19日上会

2025年12月14日 16:30
根据安排,长裕控股集团股份有限公司(以下简称“长裕集团”)主板IPO将于12月19日上会迎考。据了解,长裕集团主要从事锆类产品、特种尼龙产品、精细化工产品的研发、生产和销售,公司IPO于2025年5月21日获得上交所受理,当年6月13日进入问询阶段。本次冲击上市,长裕集团拟募集资金约7亿元,扣除发行费用后,将投资于4.5万吨超纯氧氯化锆及深加工项目、年产1万吨高性能尼龙弹性体制品项目、年产1000吨生物陶瓷及功能陶瓷制品项目。(北京商报) 分享到
❌
❌