Swift 刚学几天,以练手的心态改写以前一个 OC 的小项目,这里记录一些遇到的坑点。由于是初学者,各位大牛如果发现错误,欢迎指出。闲话不多说,下面就正式开始
1.私有属性和方法
在 Swift 中,是没有私有属性的,也就是说,只要在同一个命名空间(后面会有介绍),我们都可以访问的到。但是Swift中是有有 private 关键字的,根据特点决定,如果编写 App 的话,直接用默认的就好了,就是啥也不用敲,如果编写 Framework,请认真思考流程,认真设计,外部接口要设置 public,而一些不想让别人看见的就可以用 private 或者 internal 修饰了
2.Swift中的宏定义
在Swift中是没有宏定义的,但是我们可以使用公用函数来替代宏,比如
func kTabBarWidth(object: UITabBarController) -> CGFloat {return object.tabBar.frame.size.width}
func kTabBarHeight(object: UITabBarController) -> CGFloat {return object.tabBar.frame.size.height}
func kButtonWidth(object: UITabBarController) -> CGFloat {return kTabBarWidth(object: object)/CGFloat((object.viewControllers?.count)!)}
在公用函数中取不到的变量我们可以以参数的形式传入
3.Swift 中不同类型变量的运算
Swift 是强语言,如果两个不同类型的相加,必须进行类型转换,比如
let num1 = 10
let num2 = 9.9
let iSum = num1 + Int(num2)
let dSum = Double(num1) + num2
4.按钮添加点击事件
为按钮添加点击事件,这里使用的是#Selector(方法名)的样式
//按钮添加点击事件
button.addTarget(self, action: #selector(selectedVC(button:)), for: UIControlEvents.touchUpInside)
值得注意的是,我们后面的按钮点击方式的枚举类型应该用
枚举名+“.”+枚举值
的方式来调用
5.闭包中的self与循环引用
Swift 中的闭包,也就是 OC 中的 Block ,怎么使用这里就不多叙述了,我们这里要说的,是要特别注意的点
在闭包中使用本类的属性或调用方法,必须使用Self调用,这样的话,就产生了循环引用的问题,和OC中相差不多,我们利用一个 weak 关键字来解决这一问题
let homeViewModel = HomeViewModel()
weak var weakSelf = self
homeViewModel.loadMovieData { (data) in
weakSelf!.dataList = data
}
6.Swift中的命名空间
Objective-C 是没有命名空间的,在应用开发时,所有的代码和引用的静态库最终都会被编译到同一个域和二进制中。这样的后果是一旦我们有重复的类名的话,就会导致编译时的冲突和失败。在 Swift 中,由于可以使用命名空间了,即使是名字相同的类型,只要是来自不同的命名空间的话,都是可以和平共处的。Swift 中的命名空间的使用不是一个项目,而是需要跨项目,在一个项目中,都是一个命名空间,在同一个命名空间下,所有全局变量或者函数共享,不需要 import
我们项目中使用到了命名空间,在我们动态创建控制器的时候
let imgNames = ["home","payticket","store","discover","myinfo"]
//创建标签控制器数组存储标签控制器名
let viewContorllersArray = ["HomeViewController","PayTicketViewController","StoreViewController","DiscoverViewController","MyInfoViewController"]
var bnvVcArray:[UIViewController] = []
for i in 0..<5 {
let str = viewContorllersArray[i]
//通过一个字符串创建控制器对象
//获取命名空间
//namespace在info.plist 对应的是 CFBundleExecutable,我们可以在info.plist中任意右击一行,选中Show Raw Keys/Values
let namespace = Bundle.main.infoDictionary!["CFBundleExecutable"] as! String
let uivcType = NSClassFromString(namespace + "." + str) as? UIViewController.Type
//可选绑定
if let type = uivcType {
//创建
let uiVC = type.init()
uiVC.tabBarItem.selectedImage = UIImage(named: imgNames[i] + "_on")
uiVC.tabBarItem.image = UIImage(named: imgNames[i])
uiVC.title = imgNames[i]
let bnv = BaseNavViewController(rootViewController: uiVC)
bnvVcArray.append(bnv)
}
}
如果新建项目时,项目名称中包含有中文,可以进入是 Build Settings 中选中 "All" ,搜索 product name ,即可修改命名空间,如图:
7.使用 Runtime 实现字典转模型
我们在项目中自定义了一个 BaseModel 自定义了一个构造方法传入一个字典,来方便的实现字典转模型,在实现过程中,我们在 Swift 中使用了 Runtime
我们知道 OC 是动态语言,能够通过 runtime API 调用和替换任意方法,纯 Swift 类的函数调用已经不再是 OC 的运行时发消息,而是在编译时就确定了调用哪个函数,所以没法通过 runtime 获取方法、属性,而Swift为了兼容 OC ,凡是继承自 NSObject 的类都会保留其动态性,所以我们能通过 runtime 拿到继承与 NSObject 的类的方法和属性
func setAttribut(dic: [String:Any]) -> Void {
let attributDic = attributesDic(dic: dic)
//Runtime获取本类属性
var count:UInt32 = 0
let ivars = class_copyIvarList(self.classForCoder, &count)
for i in 0..<count {
//取出属性名
let ivar = ivars?[Int(i)]
let ivarName = ivar_getName(ivar!)
let nName = String(cString: ivarName!)
//取出要赋值的值
let attribut = attributDic[nName]
var value:NSObject
if dic[attribut!] != nil {
value = dic[attribut!] as! NSObject
} else {
value = "" as NSObject
}
//利用KVC给本类的属性赋值
self.setValue(value, forKey: nName)
}
}
8.Swift 中的异常处理
在 OC 中调用方法时,通常是通过一个 NSError 参数来返回异常信息的,但是在Swift中返回异常的方式就有些不同了。下面,我们自定义一个返回异常的函数,并且调用这个函数
//定义一个抛出异常的方法
//在一切正常的情况下,返回值是String类型,
func someFunctionWhichCanFail(param: Int) throws -> String {
if param > 0 {
return "成功"
} else {
throw NSError(domain: "啦啦啦,失败了", code: 499, userInfo: nil)
}
}
//do-try-catch这种错误模式,本意就是尝试(try)做一件事情,如果失败则捕获(catch)处理。
//要注意的是你可以在 do 代码段中写多于一行的代码(并且 try 可以调用不止一个抛错误的方法)。如果一切顺利的话,将会像预期的那样执行那些方法,但是一旦方法出错就会跳出 do 代码段,进入 catch 处。
do {
//尝试做一件事情
let result = try someFunctionWhichCanFail(param: -1)
print("\(result)")
} catch let error {
//在catch中捕获错误信息
print("\(error)")
}
这样,我们就基本明白了 Swift 中处理异常的基本方式,再看我们的项目。
在项目中,我们封装了一个JSON文件解析类,这个类有一个方法,传入JSON文件名,返回一个解析好的字典或是数组
class func jsonObjectFromFileName(fileName: String) -> NSDictionary? {
//获取文件路径
let path = Bundle.main.path(forResource: fileName, ofType: "json")
//解析
let data = NSData.init(contentsOfFile: path!)
let dic = try! JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.allowFragments) as? NSDictionary
return dic
}
这里的
JSONSerialization.jsonObject(with: data! as Data, options: JSONSerialization.ReadingOptions.allowFragments)
方法是要求我们必须捕获一个异常的,我们这里代码中使用了一个 try! 关键字,这里还可以使用 try? 关键字。try? 会将错误转换为可选值,当调用 try? +函数或方法语句 的时候,如果函数或方法抛出错误,程序不会发崩溃,而返回一个nil,如果没有抛出错误则返回可选值,不会含有更多的造成特定的错误或者异常原因的信息。try! 打破了错误传播链条,但是如果真的发生错误就出现运行期错误,导致程序的崩溃。所以使用 try! 打破错误传播链条时,应该确保程序不会发生错误
9.Swift 项目中使用 CocoaPods
Swift 项目中使用 Cocoapods 导入第三方库,导入的过程与 OC 项目无异,但是使用时,需要创建一个 Bridging-Header.h 桥接头文件,来实现 OC 与 Swift 混编
可以在Building Settings中自己设置桥接头文件
这里有个简便方法生成这个桥接文件,就是我们在项目中创建一个 OC 文件,Xcode就会自动帮我们生成一个桥接头文件
有了头文件之后,我们只需要在桥接文件中导入我们需要使用的第三方库,就可以愉快的使用了~~~~
10.子类实现父类的代理方法
在这里,让我们首先看项目中的例子
我们声明了一个 BaseCollectionView 继承于 UICollectionView ,并将代理设置为自己
然后我们又声明了一个 PosterCollectionView 继承了BaseCollectionView
在 OC 中,我们想要在 PosterCollectionView 里实现 UICollectionView 的 delegate 方法,那么直接在子类 PosterCollectionView 中书写就可以了,但是在Swift中,我们需要在父类 BaseCollectionView 中实现,然后在子类 PosterCollectionView 中重写
父类:
子类:
11.Swift 中的 KVO
Swift 中的 KVO 使用与 OC 相差不多,但是还是有一些坑点,下面我们只是简单的说一下
1.观察者和被观察者都必须是 NSObject 的子类,因为 OC 中 KVO 的实现基于 KVC 和 runtime 机制,只有是 NSObject 的子类才能利用这些特性
2.要观察的属性使用 @dynamic 修饰,表示该属性的存取都由 runtime 在运行时来决定,由于 Swift 基于效率的考量默认禁止了动态派发机制,因此要加上该修饰符来开启动态派发
这里是项目中的实现代码
//被观察的属性,加dynamic关键字
//记录下标
dynamic var currentIndex:Int = 0
//添加监听
func addObserver() -> Void {
//添加indexCollectionView的监听
indexCollectionView?.addObserver(self, forKeyPath: "currentIndex", options: NSKeyValueObservingOptions.new, context: nil)
//添加posterCollectionView的监听
posterCollectionView?.addObserver(self, forKeyPath: "currentIndex", options: NSKeyValueObservingOptions.new, context: nil)
}
//观察者方法
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
//变化后的值
let index = change?[NSKeyValueChangeKey.newKey] as! Int
//创建indexPath
let indexPath = IndexPath(item: index, section: 0)
//判断是否是currenIndex属性
guard keyPath == "currentIndex" else {
return
}
//判断对象的类型
if object is PosterCollectionView {
//滑动到指定单元格
indexCollectionView?.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: true)
} else if object is IndexCollectionView{
//滑动到指定单元格
posterCollectionView?.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.centeredHorizontally, animated: true)
}
//变换标题
titleLabel?.text = dataList?[index].titleCn
}
//销毁
deinit {
//移除观察者
indexCollectionView?.removeObserver(self, forKeyPath: "currentIndex")
posterCollectionView?.removeObserver(self, forKeyPath: "currentIndex")
}
这里注意一定要在析构函数中移除监听
如果你还对 Swift 中的 KVO 感兴趣的话,可以看一下这篇文章
Swift: KVO 注意事项和属性观察器
在最后,放上小项目的地址,还是那句话,说再多也不如自己敲一敲
时光电影Swift版初学小项目