iOS 项目总结(一)

一、前言

尽管早就想开写这么一个系列,但迟迟没有付诸实践。就现在开始吧,这个系列会记录我项目中遇到的问题和一些我以前不知道的小知识点等等。

二、问题

2.1 消除 Pod 文件中的警告

作为强迫症患者,看到一个警告就会让我不舒服。但以前不知道 Pod 文件中的警告可以被忽略,直到有一天发现警告全部不见了,原来是同事在 Podfile 文件中加入了这么一个配置:

inhibit_all_warnings!

2.2 引入 R.swift 三方后,自定义 Warning 显示,脚本冲突

以前写 OC 时,我们可以用如下代码来让 Xcode 给出 Warning 提示:

#warning

但在 Swift 项目代码中,这种写法无效了,当然 Swift 也有新的解决办法:

image

如图所示点击 New Run Script Phase, 然后在添加的 Run Script 中写入如下脚本代码:

TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT} for ${TAGS}"
find "${SRCROOT}" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"

image

然后在项目中,就能使用 FIXME:&TODO: 来让 Xcode 显示提醒,当然你可以自定义标识符。

image

这段脚本代码笔者本来就是从网上复制的,也从来没仔细看过,直到引入了 R.swift(强烈推荐) 这个三方,造成冲突了。冲突的原因很简单,因为上面脚本代码中匹配的是 .swift,结果这个三方名字中就含有这个,尼玛!所以解决方法就是忽略 Pod 文件。

TAGS="TODO:|FIXME:"
echo "searching ${SRCROOT}/项目工程文件本名 for ${TAGS}"
find "${SRCROOT}/项目工程文件名" \( -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/"

2.3 直接在 StoryBoard 中使用 UIScrollView 布局界面

最近笔者完全沦为 IB 党了,哈哈!

没有尝试过的读者会认为这个不会有什么问题。往下看吧:

image

往控制器中拖入一个 UIScrollView ,上下左右约束都为 Safe Area
就像笔者第一次使用一样,直接放一个 UIViewUIScrollView 中,并约束为上下左右都等于父视图的边界。

image

问题就来了,报错说 UIScrollView 需要新的位置或大小约束,笔者目前也没想通为啥会报错。

实际开发中,页面的最终的内容肯定是确定的,所以可以根据界面反推 UIScrollView 的布局。但是就像上图中那样,如果一直给你显示布局错误,笔者和部分读者会觉得很烦。解决方法很简单:

直接添加内容视图的宽高约束等于 UIScrollView,然后将高度约束的优先级调整为小于 1000。

image

然后在内容视图上布局界面时根据子视图将内容视图高度确定就OK了,此时因为前面设置的高度约束优先级小于 1000 ,所以会优先使用子视图确定的高度。当然读者也可以在布局完成时将前面的高度约束删除掉。

还有就是可以将控制器的界面设置为 Freeform,对于一些超出屏幕的界面,很方便。

image

2.4 内存泄漏

最近导入了微信的一个三方内存泄漏检测工具。

MLeaksFinder

发现同事的代码中有很多的内存泄漏,经过一番排查,这里也小小总结一下常见的和遇到的不常见的内存泄漏情况。

2.4.1 常见的

  • 闭包循环引用
// 用来设置导航栏返回按钮的一个方法 
self.setUseCommonNavigationBackButtonWithHandler { (_) in
    self.navigationController?.popViewController(animated: true)
}

绝对多数读者肯定会避免这种问题,但我发现同事写了一个变种:

let vc = UIViewController()
vc.setUseCommonNavigationBackButtonWithHandler { (_) in
    vc.navigationController?.popViewController(animated: true)
}

这里的 vcself 是一回事。

  • 不会释放对象持有需要释放对象

场景: 单例持有外部需要释放的对象

笔者项目中使用的是 SVProgressHUD 这个三方来做的加载,其中有一个方法是用来设置加载控件的容器视图的:

+ (void)setContainerView:(nullable UIView*)containerView;   

项目中同事将当前控制器的视图作为了容器视图进行了配置,然后 SVProgressHUD 单例对象对其进行了持有,当控制器 pop 时,这个视图因为被单例持有,无法释放。

  • 对象循环持有

场景:子控制器持有父控制器

对于一些需要向控制器中添加子控制器的界面,点击子控制器的内容,然后进行 push操作,但因为这个场景真正需要进行 push 操作的其实是父控制器,所以往往都会将父控制器传给子控制器。

如果一不小心,声明了一个属性强持有了父控制器,就造成了循环持有。正确的应该是弱持有父控制器。

  • 监听和通知需要移除

这个没什么说的,项目中没发现,只是提一下

2.4.2 不常见的

这里的不常见定义为笔者以前不知道的

  • WKUserContentController移除位置

场景:WKWebView 进行 JS 交互

最初是在 deinit 方法中进行的移除操作,经过测试发现,WKUserContentController 实例对象进行了添加后,不会走 deinit 方法,坑爹!

目前采用的在 viewWillAppearviewWillDisappear 方法中分别添加和移除。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    userContent.add(self, name: "xxx")
}
    
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    userContent.removeScriptMessageHandler(forName: "xxx")
}
  • UIAlertController内存泄漏
    直接模拟场景代码
class DetailViewController: UIViewController {
    var alertController: CustomViewController!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        alertController = CustomViewController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
        let textAction = UIAlertAction(title: "拍照", style: UIAlertActionStyle.default) { (action:UIAlertAction) in
        // 问题所在处
            self.title = "Test"
        }
        alertController.addAction(textAction)
    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        present(alertController, animated: true, completion: nil)
    }
    
    deinit {
        print("DetailViewController init")
    }
}

DetailViewController 持有了 alertController

alertController 添加 textAction 后持有了它

textAction 事件闭包中使用了 self, 于是持有了 DetailViewController

一个循环引用环形成,GG。

解决方法也很简单,闭包中使用 weak

2.5 swift协议默认实现的静态分发

详见忽略子类中的协议默认实现不参与动态分派

这里我项目中场景是定义了一个协议来控制当前控制器是否能够滑动返回

protocol NavigationControllerGestureAble: NSObjectProtocol  {
    var isCanSlidePop: Bool { get }
}

extension UIViewController: NavigationControllerGestureAble {
// 这里@objc需要注意
    @objc var isCanSlidePop: Bool {
        return true
    }
}

...其余的略

这里默认为 ture,因为 OC 中没有计算属性,其实就是一个 set&get 方法的语法糖。所以这里需要加 @objc(我是这样理解的),于是我们在需要关闭的控制器中重写这个并返回 false 就好了。

三、后记

掘金地址
又开新坑了。。。

推荐阅读更多精彩内容

  • 很幸运,有一位能在潜移默化中推动我成长的大学闺蜜! 第一次写书评的她第一个想到和我分享,当时看了好几遍。默默心想:...
    黄筱惠阅读 137评论 0 1
  • 今天是2016年的最后一天,晚上准备去参加罗辑思维的深圳的线下活动,我期望这是一个有意义的跨年,在去跨年之前我想...
    erdai222阅读 18评论 0 1
  • 最近真的是负能量爆棚,感谢那些愿意听我发泄情绪的小伙伴,毕竟不是每个人都愿意听你瞎bb,不要在意我的不开心,我本来...
    原来是新悦阿阅读 25评论 0 0
  • 2018年5月4日 星期五 晴转多云 回头看看,加入简书将近两年时间,2016年10月,发表了第一篇文章。...
    江小麦阅读 383评论 10 10