iOS 项目总结(一)
一、前言
尽管早就想开写这么一个系列,但迟迟没有付诸实践。就现在开始吧,这个系列会记录我项目中遇到的问题和一些我以前不知道的小知识点等等。
二、问题
2.1 消除 Pod 文件中的警告
作为强迫症患者,看到一个警告就会让我不舒服。但以前不知道 Pod
文件中的警告可以被忽略,直到有一天发现警告全部不见了,原来是同事在 Podfile
文件中加入了这么一个配置:
inhibit_all_warnings!
2.2 引入 R.swift 三方后,自定义 Warning 显示,脚本冲突
以前写 OC
时,我们可以用如下代码来让 Xcode
给出 Warning
提示:
#warning
但在 Swift
项目代码中,这种写法无效了,当然 Swift
也有新的解决办法:
如图所示点击 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
。
就像笔者第一次使用一样,直接放一个 UIView
到 UIScrollView
中,并约束为上下左右都等于父视图的边界。
问题就来了,报错说
UIScrollView
需要新的位置或大小约束,笔者目前也没想通为啥会报错。
实际开发中,页面的最终的内容肯定是确定的,所以可以根据界面反推 UIScrollView
的布局。但是就像上图中那样,如果一直给你显示布局错误,笔者和部分读者会觉得很烦。解决方法很简单:
直接添加内容视图的宽高约束等于 UIScrollView
,然后将高度约束的优先级调整为小于 1000。
然后在内容视图上布局界面时根据子视图将内容视图高度确定就OK了,此时因为前面设置的高度约束优先级小于 1000 ,所以会优先使用子视图确定的高度。当然读者也可以在布局完成时将前面的高度约束删除掉。
还有就是可以将控制器的界面设置为 Freeform
,对于一些超出屏幕的界面,很方便。
2.4 内存泄漏
最近导入了微信的一个三方内存泄漏检测工具。
发现同事的代码中有很多的内存泄漏,经过一番排查,这里也小小总结一下常见的和遇到的不常见的内存泄漏情况。
2.4.1 常见的
- 闭包循环引用
// 用来设置导航栏返回按钮的一个方法
self.setUseCommonNavigationBackButtonWithHandler { (_) in
self.navigationController?.popViewController(animated: true)
}
绝对多数读者肯定会避免这种问题,但我发现同事写了一个变种:
let vc = UIViewController()
vc.setUseCommonNavigationBackButtonWithHandler { (_) in
vc.navigationController?.popViewController(animated: true)
}
这里的 vc
和 self
是一回事。
- 不会释放对象持有需要释放对象
场景: 单例持有外部需要释放的对象
笔者项目中使用的是 SVProgressHUD
这个三方来做的加载,其中有一个方法是用来设置加载控件的容器视图的:
+ (void)setContainerView:(nullable UIView*)containerView;
项目中同事将当前控制器的视图作为了容器视图进行了配置,然后 SVProgressHUD
单例对象对其进行了持有,当控制器 pop
时,这个视图因为被单例持有,无法释放。
- 对象循环持有
场景:子控制器持有父控制器
对于一些需要向控制器中添加子控制器的界面,点击子控制器的内容,然后进行 push
操作,但因为这个场景真正需要进行 push
操作的其实是父控制器,所以往往都会将父控制器传给子控制器。
如果一不小心,声明了一个属性强持有了父控制器,就造成了循环持有。正确的应该是弱持有父控制器。
- 监听和通知需要移除
这个没什么说的,项目中没发现,只是提一下
2.4.2 不常见的
这里的不常见定义为笔者以前不知道的
- WKUserContentController移除位置
场景:WKWebView 进行 JS 交互
最初是在 deinit
方法中进行的移除操作,经过测试发现,WKUserContentController
实例对象进行了添加后,不会走 deinit
方法,坑爹!
目前采用的在 viewWillAppear
和 viewWillDisappear
方法中分别添加和移除。
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
就好了。
三、后记
掘金地址
又开新坑了。。。