Swift3.0 版时光电影项目笔记

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 ,即可修改命名空间,如图:


Swift3.0 版时光电影项目笔记_第1张图片
2373BC66-3171-4EE9-998F-D715DD43D680.png

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..

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 混编


Swift3.0 版时光电影项目笔记_第2张图片
screenshot.png

可以在Building Settings中自己设置桥接头文件

Swift3.0 版时光电影项目笔记_第3张图片
screenshot 2.png

这里有个简便方法生成这个桥接文件,就是我们在项目中创建一个 OC 文件,Xcode就会自动帮我们生成一个桥接头文件
有了头文件之后,我们只需要在桥接文件中导入我们需要使用的第三方库,就可以愉快的使用了~~~~

Swift3.0 版时光电影项目笔记_第4张图片
screenshot 3.png

10.子类实现父类的代理方法

在这里,让我们首先看项目中的例子
我们声明了一个 BaseCollectionView 继承于 UICollectionView ,并将代理设置为自己


Swift3.0 版时光电影项目笔记_第5张图片
screenshot.png

然后我们又声明了一个 PosterCollectionView 继承了BaseCollectionView


Swift3.0 版时光电影项目笔记_第6张图片
screenshot.png

在 OC 中,我们想要在 PosterCollectionView 里实现 UICollectionView 的 delegate 方法,那么直接在子类 PosterCollectionView 中书写就可以了,但是在Swift中,我们需要在父类 BaseCollectionView 中实现,然后在子类 PosterCollectionView 中重写

父类:


Swift3.0 版时光电影项目笔记_第7张图片
screenshot.png

子类:

Swift3.0 版时光电影项目笔记_第8张图片
screenshot.png

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版初学小项目

你可能感兴趣的:(Swift3.0 版时光电影项目笔记)