从“做出来”到“做得好”:技术探索与实践的必要性
在iOS开发这条路上,我走过了五年。从最初面对Storyboard时的手忙脚乱,到现在参与架构设计、性能优化和项目拆分重构,这一路走来最大的感悟就是:技术的价值不在于你会不会写代码,而在于你是否愿意去“深挖”和“验证”。
今天我想分享一个真实项目的经历——我们是怎么从一次简单的功能迭代,最终演进成一场对架构和技术深度探索的旅程的。希望通过这个故事,能让你理解为什么我们需要不断进行技术探索与实践。
背景介绍:一场普通的模块重构

故事发生在我参与的一个中型社交类App改版项目上。我们团队负责用户发布内容的核心流程(俗称“发帖页”),这部分在整个App的DAU中占比非常大,但原有页面结构已经积攒了多年的技术债:大量冗余逻辑、强耦合的UI组件、复杂的生命周期管理,以及越来越多难以追踪的状态变化。
当时的需求其实很“简单”:
将原本只能上传一张图片的发帖页,升级为支持多张图片选择并可排序的功能,并接入新的图片上传链路以提升上传速度。
听起来是标准的UI/UX迭代?但我隐隐感觉这背后藏着更多技术挑战。
遇到的问题:复杂状态难维护

在接手后我迅速发现几个棘手问题:
- 数据状态难以统一管理
- 原有代码中,图片数据在View层、ViewModel层之间来回传递,部分逻辑甚至直接暴露给ViewController处理。
- 交互细节繁杂
- 新增图片拖动排序、实时缩略图预览、删除动画等,都让视图响应变得复杂。
- 上传流程不可控
- 图片上传过程分散在多个地方,失败重试机制没有统一接口。
- 测试难度极高
- 业务逻辑嵌套严重,单元测试几乎为零覆盖。
这些问题让我意识到:如果只是单纯加个UICollectionView完成功能,未来一定会埋下更大的隐患。
于是,我决定把这次重构当作一次全面的技术探索机会。
探索之路:从架构开始下手


我花了一周时间梳理现有代码,画出了整个发帖页的数据流向图,发现了以下几个关键点:
- ViewModel职责混乱
- 多处重复的网络请求代码
- UI事件流与数据流交织不清
- 单一文件代码量超过5000行(你没看错)
于是我提出了几个核心目标:
- 使用Redux-like状态管理方式,统一数据状态
- 引入独立的ImageUploader模块,解耦上传逻辑
- 抽离出ContentEditorManager作为状态中枢
- 实现基础的单元测试覆盖率
- 支持未来的可扩展性(比如加入视频、语音等内容类型)
这些想法起初遭到了一些质疑:“有必要为了这么一个页面搞这么多吗?”但当我展示出现有的代码质量报告之后,大家达成了共识:再不做技术改造,成本将越来越高。
我们做了什么:技术方案选型与实现思路
1. 状态管理 —— 自研轻量StateContainer
我们放弃了当时社区流行的ReSwift或Combine+Swinject这类重型框架(主要是担心学习成本),而是参考Redux思想搭建了一个简易的单向状态容器系统。
主要设计如下:
enum ContentAction {
case addPhoto(UIImage)
case removePhoto(index: Int)
case reorderPhoto(from: Int, to: Int)
case uploadStarted
case uploadFailed(error: Error)
}
struct ContentState {
var photos: [PhotoModel] = []
var isUploading: Bool = false
// 其他相关状态字段...
}
class ContentStore {
private(set) var state: ContentState {
didSet {
dispatchDidChange()
}
}
private let reducer: (ContentState, ContentAction) -> ContentState
private var observers: [ObjectIdentifier: (ContentState) -> Void] = [:]
init(initialState: ContentState, reducer: @escaping (ContentState, ContentAction) -> ContentState) {
self.state = initialState
self.reducer = reducer
}
func dispatch(_ action: ContentAction) {
state = reducer(state, action)
}
func addObserver<O: AnyObject>(_ object: O, handler: @escaping (O, ContentState) -> Void) {
let key = ObjectIdentifier(object)
observers[key] = { [weak object] state in
guard let obj = object else { return }
handler(obj, state)
}
}
func removeObserver<O: AnyObject>(_ object: O) {
let key = ObjectIdentifier(object)
observers.removeValue(forKey: key)
}
private func dispatchDidChange() {
for handler in observers.values {
handler(state)
}
}
}
这套系统虽然很简单,但我们通过清晰的Action定义,实现了所有图片相关的操作都被统一封装,并且状态变更具有可预测性和追溯能力。
2. 拖拽排序与性能优化
新需求包含图片拖动排序功能。原本打算用UICollectionViewDragDelegate + UICollectionViewDropDelegate,结果测试下来发现:
- 在iPad多任务环境下拖拽性能下降明显
- 多个CollectionView嵌套时容易触发冲突手势
- 动画不流畅,特别是快速拖动时卡顿感严重
最后我们调研了社区库,在综合对比后使用了InteractMove(非广告,是基于UIViewPropertyAnimator封装的一个开源库),实现了更灵活的交互控制,同时保持帧率稳定在60FPS以上。
核心代码片段:
let interact = InteractMove()
interact.delegate = self
collectionView.addGestureRecognizer(interact)
// MARK: - InteractMoveDelegate
func moveItemBegin(in view: UIView, at index: IndexPath?, touchLocation: CGPoint) {
// 开始拖动
if let index = index {
store.dispatch(.reorderStart(index))
}
}

func moveItemMoving(in view: UIView, movingIndex: IndexPath, to targetIndex: IndexPath, touchLocation: CGPoint) {
collectionView.performBatchUpdates({
let item = photoDataSource.remove(at: movingIndex.row)
photoDataSource.insert(item, at: targetIndex.row)
collectionView.moveItem(at: movingIndex, to: targetIndex)
}, completion: nil)
}
func moveItemEnd(in view: UIView, from sourceIndex: IndexPath, to targetIndex: IndexPath) {
store.dispatch(.reorderPhoto(from: sourceIndex.row, to: targetIndex.row))
}
3. 图片上传服务抽离
之前上传图片的逻辑散落在多个VC中,每次调整上传策略都要修改N个地方。这次我们封装了一个独立的ImageUploader模块:
protocol ImageUploadable {
func upload(image: UIImage, forKey key: String, progress: ((Double) -> Void)?, completion: @escaping (Result<String, Error>) -> Void)
}
class CloudinaryUploader: ImageUploadable {
func upload(...) {
// 实现具体的上传逻辑
}
}
class ImageUploader {
private let uploader: ImageUploadable
init(uploader: ImageUploadable) {
self.uploader = uploader
}
func startUploads(images: [UIImage], keys: [String], completion: @escaping ([String]) -> Void) {
let group = DispatchGroup()
var uploadedURLs: [String] = []
zip(images, keys).forEach { image, key in
group.enter()
uploader.upload(image: image, forKey: key) { result in
switch result {
case .success(let url):
uploadedURLs.append(url)
case .failure:
break
}
group.leave()
}
}
group.notify(queue: .main) {
completion(uploadedURLs)
}
}
}
这样的封装不仅统一了上传接口,也为将来换用AWS S3或其他CDN留下了扩展空间。
踩坑经验:那些我们没想到的角落
内存暴涨 —— 因为Thumbnail缓存没控制住
原本我们在UICollectionViewCell里直接加载全尺寸图片用于预览,结果导致内存飙升。后来加上了异步压缩和缓存策略才解决。
解决方案:
- 在图片添加时即异步生成thumbnail
- 缓存在自定义的ImageCache类中,限制最大数量
- 使用NSCache而非NSDictionary避免内存泄漏
let thumbnailSize = CGSize(width: 128, height: 128)
let scaledImage = originalImage.resize(to: thumbnailSize, scaleMode: .aspectFill)
图片方向翻转 —— EXIF信息导致显示异常
某些iPhone前置摄像头拍照的图片方向会因为EXIF被自动旋转,但在UIImageView中又不会自动处理。这个问题一度让产品以为是我们代码逻辑错误。
解决办法:
- 在加载图片时统一修正方向
extension UIImage {
func fixedOrientation() -> UIImage? {
guard imageOrientation != .up else { return self }
UIGraphicsBeginImageContextWithOptions(size, false, scale)
draw(in: CGRect(origin: .zero, size: size))
let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return normalizedImage
}
}
效果总结:不只是功能上线
重构完成后,我们做了以下评估:
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 页面加载帧率 | ~52 FPS | ~60 FPS |
| 上传成功率 | 92% | 97.5% |
| 新人上手时间 | 2周+ | 3天以内 |
| 单元测试覆盖率 | <10% | >70% |
| 文件行数 | 5132行 | 分为6个小文件,合计3156行 |
更重要的是:
- 产品经理提出的“明天要加个视频贴纸”需求我们只用了半天完成接入;
- 后续又有两个团队复用了我们的状态管理模块;
- 发帖页成为新入职同学的“教学样板”,不再需要解释“这段代码是十年前写的”。
经验总结:为什么要做技术探索?
回顾这次经历,我特别想告诉刚入行的小伙伴们一句话:
“做出来的代码叫交付,做好的代码叫工程。”
技术探索从来不是“炫技”,而是为了解决真实世界中的问题。以下是我在这些年里积累的一些体会,希望能帮到你们:
✅ 不要怕“过度设计”
有时候我们总被“够用就行”这句话束缚。但实际上,“够用”的标准是要结合项目周期、人力配置和长期维护成本来看的。如果你知道某个功能会在未来频繁改动,提前做好抽象和封装是非常值得的。
✅ 工具服务于目的
在技术选型的时候不要盲目追求热门框架或最先进模式。适合当前团队能力和项目节奏的才是最好的。哪怕是自己动手写一个小工具,只要能解决问题、保证可控性,都是合理的做法。
✅ 架构是一种思维方式
真正的架构能力不在于用了多少协议或者依赖了多少外部库,而在于能否清晰地看到数据的流动路径和边界。哪怕是一个小功能,也可以具备良好的架构意识。
✅ 性能优化不是万能药
我们曾经陷入一种误区:一味追求帧率数字、代码效率。但实际使用场景下,用户感受远比测试数据重要。比如在图片上传时,即使后台稍微慢一点,但给出合理的反馈提示,用户也不会觉得卡顿。
✅ 文化比技术更重要
技术探索要能落地,需要有一个允许犯错、鼓励尝试的环境。我们组内有个不成文的规定:每次迭代后都要开个小复盘,不论成败都聊聊“哪些可以做得更好”。久而久之,团队成员都养成了主动思考的习惯。
最后的一点建议
如果你正处在这样的心态中:
“我就是个普通开发者,没必要研究太多底层原理。”
或者
“我现在做的项目太简单,学不到东西。”
我想告诉你:每一个伟大的工程,都始于平凡的一行代码。
技术探索并不是遥不可及的大词,它可能就藏在一个小小的函数封装中,也可能是一段被优化的内存占用日志里。
我始终相信一句话:
“写得好,不如问得深;走得快,不如看得远。”
希望这篇来自实战的真实分享,能给你带来一些启发。也欢迎大家留言交流,一起聊聊你在开发中遇到的那些“技术探索时刻”。

评论 0