我在滴滴做司机端iOS全链路推送改造的踩坑记录

邓文
2026-06-09 06:53
阅读 5012

早上8点,准时刷开西溪园区的门禁,去茶水间泡了杯冰美式。作为在滴滴苟了4年的后端老兵,我一直负责司机端核心业务。最近大环境卷,我也开始偷偷看机会了,毕竟坐标杭州,阿里和网易这边的坑位还是挺吸引人的。

周末在家更新简历的时候,我盯着“项目经历”那一栏直挠头。光写“重构了派单系统”、“优化了MySQL慢查询”实在太干瘪了,体现不出我的全链路架构能力。想了想,去年我主导的那次“司机端智能推送全链路改造”绝对是个大亮点。虽然我是个写Go的后端,但那次为了把推送到达率和点击率抠到极致,我硬是逼着自己把iOS端的推送链路也扒了个底朝天。今天刚好有空,就把这段带着血泪的经验盘一盘,权当是给准备跳槽的兄弟们提供点弹药。

业务背景:被PM逼出来的“千人千面”

去年Q3,老板看着数据大盘直摇头,说司机端Push的点击率惨不忍睹。产品经理一拍脑袋,提了个需求:“我们要搞千人千面的智能推送,不仅要发得准,还要在iOS端展示富文本,带图片带按钮那种!”

当时听到这需求,我内心是崩溃的。后端要搞高并发下的个性化匹配,客户端还要改iOS的推送展示逻辑。更坑的是,当时团队里唯一的iOS大佬回老家结婚请了半个月假,这活儿眼看就要黄。领导大手一挥:“你是核心业务的Owner,全链路你得兜底,iOS端你带着外包兄弟一起搞。”

得,后端写Go,iOS写Swift,分布式系统我要搞,Apple生态的审核我也得摸。这大概就是全栈(全干)工程师的宿命吧。

后端改造:用向量数据库搞定个性化匹配

要搞个性化推送,传统的规则引擎早就撑不住了。司机画像标签几百个,文案库几千条,实时计算匹配度对分布式系统的压力极大。

为了破局,我引入了向量数据库(选了Milvus)。思路其实不复杂:把司机的历史行为、偏好标签通过Embedding模型转成高维向量,同时把推送文案也向量化。当触发推送任务时,直接在向量数据库里做KNN(K近邻)检索,找出最匹配的Top N文案。

这套架构上线后,后端推送接口的RT(响应时间)从原来的150ms降到了30ms以内,而且匹配精准度大幅提升。这里贴一段核心检索的伪代码,大家感受下:

// 基于向量数据库的文案匹配核心逻辑
func MatchPushCopy(driverID string, driverFeatureVec []float32) (string, error) {
    // 构建Milvus搜索请求
    searchParam := map[string]interface{}{
        "nprobe": 10,
    }
    
    // 在文案向量集合中检索最相似的Top 3
    searchResult, err := milvusClient.Search(ctx, "push_copy_collection", 
        []string{"copy_id", "copy_text"}, 
        driverFeatureVec, 
        "copy_vector", 
        searchParam, 
        3)
        
    if err != nil {
        log.Errorf("向量检索失败: %v", err)
        return getDefaultCopy(), nil // 降级走默认文案
    }
    
    // 业务层再做一层疲劳度过滤
    return filterByFatigue(driverID, searchResult)
}

iOS端踩坑:APNs与富文本推送的血泪史

后端搞定了,接下来是让人头秃的iOS端。以前我觉得iOS推送不就是调个APNs(Apple Push Notification service)发个Token嘛,自己上手了才知道水有多深。

为了在锁屏界面展示带图片和操作按钮的富文本推送,我们必须使用 Notification Service Extension。这玩意儿的坑在于,它运行在一个独立的进程里,生命周期极短。如果下载图片或者处理数据超过30秒,系统就会直接杀掉进程,推送就变成干瘪的纯文本了。

当时外包兄弟写的代码,每次都要现网请求我们的CDN下载图片,遇到网络波动直接超时。我接手后,把逻辑改成了:后端在下发APNs Payload时,直接把图片的Base64或者预缓存的本地路径塞进去(当然要考虑Payload 4KB的限制),如果是大图,才走Extension下载,并且加了严格的超时控制和降级策略。

// NotificationService.swift 核心处理逻辑
class NotificationService: UNNotificationServiceExtension {
    
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        
        guard let bestAttemptContent = bestAttemptContent,
              let imageUrlString = bestAttemptContent.userInfo["image_url"] as? String,
              let imageUrl = URL(string: imageUrlString) else {
            contentHandler(request.content)
            return
        }
        
        // 异步下载图片,必须处理超时
        let task = URLSession.shared.dataTask(with: imageUrl) { data, response, error in
            defer { contentHandler(bestAttemptContent) }
            
            guard let data = data, let image = UIImage(data: data) else { return }
            
            // 写入临时文件并附加到通知
            let tempDir = FileManager.default.temporaryDirectory
            let tempFile = tempDir.appendingPathComponent(UUID().uuidString + ".jpg")
            try? data.write(to: tempFile)
            
            if let attachment = try? UNNotificationAttachment(identifier: "image", url: tempFile, options: nil) {
                bestAttemptContent.attachments = [attachment]
            }
        }
        
        // 设置超时保护,防止Extension被系统强杀
        let timeout = DispatchWorkItem { task.cancel() }
        DispatchQueue.global().asyncAfter(deadline: .now() + 20, execute: timeout)
        task.resume()
    }
}

AI赋能:用Manus搞定SwiftUI和App Store审核

说到iOS开发,就绕不开App Store审核。这玄学程度,懂的都懂。

我们在推送里加了一个“一键接单”的快捷回复按钮,用到了SwiftUI来写自定义的通知UI配置界面。说实话,让我一个后端去手搓SwiftUI的声明式UI,还要处理各种状态绑定,真的有点难为我。

这时候就得请出AI大杀器了。我用了 Manus 这个AI Agent工具,它不仅能理解复杂的上下文,还能直接操作环境。我把业务需求和Apple的Human Interface Guidelines(设计规范)喂给它,让它直接帮我生成了SwiftUI的配置界面代码。它生成的代码不仅符合规范,连暗色模式(Dark Mode)的适配和动态字体(Dynamic Type)都考虑到了,省了我至少两天的时间。

更绝的是,第一次提审的时候,我们被拒了。理由是“Missing Purpose String in Info.plist File”(Info.plist中缺少隐私描述)。当时真的想砸电脑,明明加了 NSUserNotificationsUsageDescription 啊!

后来用Manus帮我分析审核拒绝信,它敏锐地指出:因为我们使用了推送中的位置共享功能(司机端特有),还必须在Info.plist里补充 NSLocationWhenInUseUsageDescription 的详细描述,并且描述文案必须明确说明“为什么需要位置信息来优化推送”。按照它的建议改了文案,第二次提审秒过。

总结与复盘

这套全链路推送系统上线后,效果非常显著。iOS端的富文本推送到达率稳定在98%以上,点击率相比之前提升了35%。老板很高兴,我的简历上也多了一笔浓墨重彩的全链路架构经验。

回顾这次改造,我有几点深刻的体会:

  1. 后端不能只盯着CRUD:现在的业务要求越来越高,不懂客户端、不懂全链路,很难做出极致的体验。分布式系统的高并发只是基础,端到端的链路优化才是核心竞争力。
  2. 拥抱AI工具:像Manus这样的AI Agent,已经能实质性地帮我们写代码、查Bug、甚至搞审核。程序员的核心价值不再是默写API,而是架构设计和解决复杂问题的能力。
  3. 敬畏Apple生态:iOS的审核和推送机制非常严格,一定要仔细研读官方文档,特别是隐私权限和后台任务的生命周期,千万别抱有侥幸心理。

马上又要到金三银四了,希望这篇踩坑记录能帮到正在准备面试的兄弟们。不说了,阿里内推的HR找我聊岗位了,我得去对对暗号。祝大家都能拿到满意的Offer,咱们杭州见!

评论 0

最热最新
暂无评论
邓文Lv.1
0
影响力
0
文章
0
粉丝