iOS开发入门:Swift基础与国企程序员的实战碎碎念

星河程序员
2026-03-27 22:37
阅读 674

上周五晚上,我正躺在沙发上刷LeetCode准备跳槽面试题,突然收到领导微信:“小张啊,咱们内部用的那个设备巡检App,最近用户老说卡顿,你抽空看看能不能优化一下?顺便,听说你要学iOS?正好上手试试。”

我嘴角一抽——合着“抽空”是让我双休日加班的意思?好在我们这国企还算人性化,真不强制加班。但转念一想,反正闲着也是刷题,不如真搞个iOS项目练练手。毕竟简历上写“熟悉移动端开发”总比光写Java后端有竞争力。

于是,我火速翻出尘封已久的MacBook(感谢公司发的开发机),打开Xcode,准备从零开始搞个简化版的巡检App。本文就记录下这个过程中的开发心得,以及一个国企程序员如何靠WindsurfGemini和一点点倔强,在Swift世界里摸爬滚打的故事。


为什么选Swift?因为别无选择(也不是)

其实我本来想用React Native搞跨端,但领导一句“原生体验好”直接给我拍板了。得,那就Swift吧。说实话,之前只在学校里写过几行Objective-C,看到@autoreleasepool就头大。Swift倒是听人吹了多年,说是“安全、现代、快”,但到底咋样?

上手第一天,我就被它的类型推断和可选类型(Optional)整懵了。比如这段代码:

var name: String?
print(name!) // 崩溃!fatal error: unexpectedly found nil while unwrapping an Optional value

当时真的想砸电脑。但冷静下来一想:这不就是Java里的NullPointerException提前报错吗?只不过Swift逼你在编译期就处理,而不是等到线上炸了才哭。这设计,其实挺负责任的


从“Hello World”到能跑的项目:我的第一周踩坑实录

环境搭建:别信教程,信Xcode

网上一堆教程说要装Command Line Tools、CocoaPods、Homebrew……结果我打开Xcode,新建项目 → 选iOS App → 点Run,模拟器直接跑起来了。原来Xcode已经把90%的环境给你配好了。感动!

不过,有个坑:模拟器默认是iPhone 15 Pro Max,巨占内存。我这8G内存的MacBook Air直接风扇狂转。后来在Gemini里问:“怎么降低Xcode模拟器资源占用?” 它建议我改用iPhone SE(第3代)模拟器,果然流畅多了。

小技巧:在Xcode顶部菜单栏 Product → Destination 里可以切换设备型号。

第一个功能:扫码录入设备ID

巡检App的核心功能之一是扫二维码录入设备信息。我原以为得集成第三方SDK,结果发现iOS 11+自带AVFoundation就能搞定扫码!代码如下:

import AVFoundation

class ScanViewController: UIViewController {
    var captureSession: AVCaptureSession!
    var videoPreviewLayer: AVCaptureVideoPreviewLayer!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        captureSession = AVCaptureSession()
        guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
        let videoInput: AVCaptureDeviceInput
        
        do {
            videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
        } catch {
            return
        }
        
        if (captureStream?.canAddInput(videoInput))! {
            captureSession.addInput(videoInput)
        } else {
            // 处理错误
            return
        }
        
        let metadataOutput = AVCaptureMetadataOutput()
        if (captureSession.canAddOutput(metadataOutput)) {
            captureSession.addOutput(metadataOutput)
            metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            metadataOutput.metadataObjectTypes = [.qr]
        } else {
            return
        }
        
        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
        videoPreviewLayer.frame = view.layer.bounds
        videoPreviewLayer.videoGravity = .resizeAspectFill
        view.layer.addSublayer(videoPreviewLayer)
        captureSession.startRunning()
    }
}

extension ScanViewController: AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        if let metadataObject = metadataObjects.first {
            guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
            guard let stringValue = readableObject.stringValue else { return }
            
            AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
            captureSession.stopRunning()
            
            // 回传扫码结果
            DispatchQueue.main.async {
                self.dismiss(animated: true) {
                    // 这里回调给上一个页面
                }
            }
        }
    }
}

这段代码看似简单,但有几个血泪教训

  1. 必须在Info.plist里加权限描述,否则直接闪退:
    <key>NSCameraUsageDescription</key>
    <string>需要访问相机以扫描设备二维码</string>
    
  2. 扫码成功后要手动停止会话,否则后台持续耗电。
  3. 别忘了震动反馈,用户体验提升50%!

开发工具链:ChatGPT + Windsurf + Gemini = 我的外挂大脑

作为一个重度依赖AI辅助开发的程序员,我日常三件套:

  • ChatGPT:写复杂逻辑、解释概念
  • Windsurf(基于Claude的IDE插件):实时代码补全、重构建议
  • Gemini:查文档、对比方案、生成测试用例

比如我在写TableView时,不确定cellForRowAt的最佳实践,直接在Windsurf里敲注释:

// Windsurf提示:避免在cellForRowAt中做耗时操作,用预加载模型
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "DeviceCell", for: indexPath)
    let device = devices[indexPath.row]
    
    // ✅ 正确:直接赋值,不解析JSON或网络请求
    cell.textLabel?.text = device.name
    cell.detailTextLabel?.text = device.id
    
    return cell
}

而当我纠结“该用Storyboard还是纯代码布局”时,Gemini给了我一张对比表:

方案 优点 缺点 适用场景
Storyboard 可视化拖拽,快速原型 冲突多,难协作,性能略低 小型项目、内部工具
SwiftUI 声明式,跨平台潜力大 iOS 13+,学习曲线陡 新项目、追求现代化
纯代码 (UIKit) 灵活、可控、易版本管理 代码量大 复杂交互、长期维护

我们这内部App用户就几十人,果断选Storyboard!省时间就是省生命,毕竟我还得刷题呢。


性能优化:国企项目也不能太摆烂

虽然只是内部工具,但用户抱怨“卡”,那肯定得治。我用了Xcode自带的Instruments工具分析,发现两个问题:

  1. TableView滚动卡顿:因为每次cellForRowAt都重新创建UILabel
  2. 内存峰值过高:图片没压缩,直接加载原图

解决方案:

  • 复用Cell:确保Identifier一致,用dequeueReusableCell
  • 异步加载图片:用DispatchQueue.global().async + 缓存
  • 限制图片尺寸:用UIImage.resize(to:)压缩
extension UIImage {
    func resize(to size: CGSize) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        draw(in: CGRect(origin: .zero, size: size))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return resizedImage ?? self
    }
}

优化后,帧率从45 FPS稳到60 FPS,内存占用降了40%。用户群里终于没人吐槽了,领导还夸我“效率高”。(其实都是AI帮的忙,嘘——)


发布上架?先过TestFlight这关

虽然只是内部分发,但走App Store太麻烦,我们用的是企业证书 + TestFlight。流程如下:

  1. 在Apple Developer注册App ID
  2. 生成Provisioning Profile
  3. Xcode Archive → Distribute to TestFlight
  4. 邀请内部测试员(用邮箱)

但第一次上传失败,报错:

ITMS-90725: SDK Version Issue - This app was built with the iOS 17.0 SDK.

查了才知道,TestFlight要求用正式版SDK构建,不能用beta版Xcode。赶紧切回Xcode 15.0正式版,重新Archive,搞定。

经验:内部App也建议走TestFlight,比直接发IPA稳定多了,还能收集崩溃日志。


最后:一个国企程序员的真实心态

说实话,在国企写代码,很多时候是“能跑就行”。但这次重拾iOS,让我找回了写代码的乐趣——不是为了应付KPI,而是真想做出点东西。

而且,准备跳槽的压力反而成了学习动力。每天刷两道算法题,再捣鼓一小时Swift,感觉技术栈又厚了一层。说不定下次面试,我就能自信地说:“我做过完整的iOS项目,从扫码到上架,全流程闭环。”

当然,也感谢那些AI助手们。没有它们,我可能还在和Optional搏斗。但记住:AI是桨,你是船长。别让它替你思考,而是帮你加速。

如果你也在国企,想学新技术但怕没机会——自己造个项目。哪怕只是个待办清单,只要跑起来,就是进步。

共勉。


关键词回顾

  • 开发心得:安全优先、用户体验、工具提效
  • Windsurf:实时代码辅助,重构神器
  • 项目:内部设备巡检App,从0到TestFlight
  • Gemini:方案对比、文档查询、测试生成

(全文约2860字,刚好够我下周技术分享用 😎)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝