接上篇文章:iOS 快速从OC过渡到Swift,由理论到实战-Swift基础
OC和Swift混编
a. Swift 和 OC 的映射关系
Swift 兼容来大部分 OC,当然还有一些 Swift 不能够使用的,例如 OC 中的预处理指令,即宏定义不可使用,虽然在目前4.2版本下,已经开始支持了少量的宏,如
#if DEBUG
#else
#endif
这种简单的预处理指令。Swift 中推荐使用常量定义,定义一些全局的常量实例、全局方法来充当 宏 的作用。
我们直到,Swift 中的一些自定义基类可以是不继承自 NSObject,这是 OC 调用这类的的时刻,就需要使用关键自 @objc 来标记,这样就可以正常使用来。
Swift中还有很多小细节部分,待大家在进一步学习过程中慢慢体会。
b. Objc 调用 Swift
由于国内大部分还都是 OC 编程环境,所以通常是先学习 OC 下调用 Swift。接下来我演示以下 OC 下配置桥接文件,以及 Objc 和 Swift 的相互调用。
当我们在项目中第一次添加不同语言的文件时,Xcode就会询问你是否创建桥接文件,你只需要点击”YES”,Xcode就可以帮你完成桥接配置(注意:第二次不会提示你,即使你删除来桥接文件)。
这份桥接文件是 Swift 调用 OC 文件的时,导入 OC 头文件使用的。在桥接文件中你可看到以下内容。
// Use this file to import your target's public headers that you would like to expose to Swift.
当然你也可以手动创建桥接文件,自己配置环境。
我们可以创建”项目名称-Bridging-Header”一个 .h 文件作为桥接文件,然后为项目配置此文件的路径。
此文件是存放 Swift 使用的 OC 头文件,此小节先不管它。
在 OC 调用 Swift 情况下,是通过Swift生成的一个头文件实现的,这个头文件是编译器自动完成的,它会将你在项目是到的 Swift 文件自动映射称 OC 语法,其他并不需要开发关注,该文件名称为 “项目名称-Swift”,你可以在 Build Settings 里搜索 Swift 可以看到:
这里还可以配置其他 Swift 的选项,如语言版本等。
接下来我们创建一个名为 “AViewController.swift” 的控制器,我们现在要在 OC 文件使用到该文件,首先,我们导入”项目名称-Swift.h”的文件,不会提示,需要你手动写入(可以按住Command点击头文件查看)。
在 OC 的 “ViewController.h” 文件中,我们进行跳转控制器,这个控制器的使用和使用 OC的类没有任何却别,系统的”项目名称-Swift.h”文件已经帮我完成了映射。
#import "ViewController.h"
#import "TestDemo-Swift.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"OC页面";
self.view.backgroundColor = [UIColor whiteColor];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
AViewController *ctrl = [AViewController new];
[self.navigationController pushViewController:ctrl animated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
c. Swift 调用 Objc
前面提到了 “TestDemo-Bridging-Header.h” 文件,这个文件中存储的是用于供给 Swift 使用的 OC 头文件。
我们将 OC 控制器文件 “ViewController.h” 导入该文件,这样我们在 Swift 文件中就可以使用该文件。
import UIKit
class AViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Swift页面";
self.view.backgroundColor = UIColor.red
}
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
let ctrl = ViewController()
self.navigationController?.pushViewController(ctrl, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
上面介绍了在 OC 工程中创建 Swift 文件,其实在 Swift 工程中创建 OC 文件是类似的,大家可以尝试一下。
Swift中的’宏’和工具类
通过前面的学习,我们已经对 Swift 的语法,和 OC 进行混编等知识有了一定的了解,下面演示一些在项目中可能使用到的工具类。
我们知道,在目前的Swift中,还不能够调用OC中的宏定义,这些东西势必需要我们重新写过。在Swift中,所对应OC的宏,只不过是一些全局常量或者方法。下面列举一些我的在OC项目中映射多来的”宏”。
// MARK: -------------------------- 适配 --------------------------
/// 屏幕宽度
let kScreenHeight = UIScreen.main.bounds.height
/// 屏幕高度
let kScreenWidth = UIScreen.main.bounds.width
/// 状态栏高度
let kStatusBarHeight = UIApplication.shared.statusBarFrame.size.height
/// 导航栏高度
let kNavigationbarHeight = (kStatusBarHeight+44.0)
/// Tab高度
let kTabBarHeight = kStatusBarHeight > 20 ? 83 : 49
/// 屏幕比率
let kScreenWidthRatio = (kScreenWidth/375.0)
/// 调整
func AdaptedValue(x:Float) -> Float {
return Float(CGFloat(x) * kScreenWidthRatio)
}
func ShiPei(x:Float) -> Float{
return AdaptedValue(x: x)
}
/// 调整字体大小
func AdaptedFontSizeValue(x:Float) -> Float {
return (x) * Float(kScreenWidthRatio)
}
// MARK: -------------------------- 颜色 --------------------------
// MARK: 请参考 UIColor_Extentsion.swift 文件
// MARK: -------------------------- 偏好 --------------------------
func SaveInfoForKey(_ value:String ,_ Key:String) {
UserDefaults.standard.set(value, forKey: Key)
UserDefaults.standard.synchronize()
}
func GetInfoForKey(_ Key:String) -> Any! {
return UserDefaults.standard.object(forKey: Key)
}
func RemoveObjectForKey(_ Key:String){
UserDefaults.standard.removeObject(forKey: Key)
UserDefaults.standard.synchronize()
}
// MARK: -------------------------- 输出 --------------------------
// 带方法名、行数
func printLog(_ message:T,method:String = #function,line:Int = #line){
print("-[method:\(method)] " + "[line:\(line)] " + "\(message)")
}
// 只在Debug下输出,为了给习惯OC输出写法的同事
func DLog(_ format: String, method:String = #function,line:Int = #line,_ args: CVarArg...){
// print("-[method:\(method)] " + "[line:\(line)] ", separator: "", terminator: "")
#if DEBUG
let va_list = getVaList(args)
NSLogv(format, va_list)
#else
#endif
}
// 只在Debug下输出,为了为习惯PHP输出写法的同事
func echo(_ format: String,_ args: CVarArg...) {
#if DEBUG
let va_list = getVaList(args)
NSLogv(format, va_list)
#else
#endif
}
使用【扩展】功能创建一些工具
颜色类
extension UIColor {
// RGB颜色
static func rgba(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat,_ a:CGFloat) -> UIColor {
return UIColor.init(red: (r)/255.0, green: (g)/255.0, blue: (b)/255.0, alpha: a)
}
static func rgb(_ r:CGFloat,_ g:CGFloat,_ b:CGFloat) -> UIColor {
return rgba((r), (g), (b), 1)
}
/// 十六进制的色值 例如:0xfff000
static func hex(_ value:Int) -> UIColor{
return rgb(CGFloat((value & 0xFF0000) >> 16),
CGFloat((value & 0x00FF00) >> 8),
CGFloat((value & 0x0000FF)))
}
/// 十六进制的色值,例如:“#FF0000”,“0xFF0000”
static func hexString(_ value:String) -> UIColor{
var cString:String = value.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
cString = cString.lowercased()
if cString.hasPrefix("#") {
cString = cString.substring(from: cString.index(after: cString.startIndex))
}
if cString.hasPrefix("0x") {
cString = cString.substring(from: cString.index(cString.startIndex, offsetBy: 2))
}
if cString.lengthOfBytes(using: String.Encoding.utf8) != 6 {
return UIColor.clear
}
var startIndex = cString.startIndex
var endIndex = cString.index(startIndex, offsetBy: 2)
let rStr = cString[startIndex..
字符转数字类
extension String {
/// 将字符串转换为合法的数字字符
///
/// - Returns: String类型
func toPureNumber() -> String {
let characters:String = "0123456789."
var originString:String = ""
for c in self {
if characters.contains(c){
if ".".contains(c) && originString.contains(c){}
else{
originString.append(c)
}
}
}
return originString
}
/// 将字符串转换为 Double 类型的数字
///
/// - Returns: Double类型
func toDouble() -> Double {
return Double(self.toPureNumber())!
}
/// 将字符串转换为 Float 类型的数字
///
/// - Returns: Float类型
func toFloat() -> Float {
return Float(self.toDouble())
}
/// 将字符串转换为 Int 类型的数字
///
/// - Returns: Int类型
func toInt() -> Int {
return Int(self.toDouble())
}
// 保留旧版本的字符转换为数字类型,例:"123".floatValue
var pureNumber: String {
return self.toPureNumber()
}
var doubleValue: Double {
return self.toDouble()
}
var floatValue: Float {
return self.toFloat()
}
var intValue: Int {
return self.toInt()
}
}
以上介绍了几种工具类的创建方式,相信大家进过基础一节过后,都能看懂。
实战篇
在这一节中,我们利用之前的测试项目简单的演示Swift下如何搭建页面、和OC进行数据传递,其中会涉及到自定义数据模型、延迟加载、类型转换、协议、协议代理模式、弱引用、闭包、闭包传值、检测释放等等,希望能都帮助到第一次接触 Swift 的小白们。
Demo中,简单搭建一个Swift页面,页面中展示一个列表,给列表Cell有标题,自标题和一张图片,用户点击某个cell时,将数据回传到上一个OC页面。
a. 定义Model层
首先我们需要要一个自定义模型,以及一个用来获取、处理数据的View-Model。
首先是自定义模型:
import UIKit
// 列表数据源
class AVTableModel: NSObject {
var title: String?
var subTitle: String?
var image: String?
}
View-Model:
import UIKit
class AVTableManager: NSObject {
// 定义数据请求,使用闭包回调
class func requestData(response:@escaping (_ flag : Bool,_ resArray : [AVTableModel]?) -> Void) {
// 模拟数据
var resList = [AVTableModel]()
for i in 0...30 {
// 使用自定义模型
let avtm:AVTableModel = AVTableModel()
avtm.title = "\(i) row"
avtm.subTitle = [
"月光推开窗亲吻脸颊,沁得芬芳轻叩门,敛去湖中婆娑影,拈起肩上落梨花",
"屋檐下的风轻轻拂过了衣角,弄皱了桌上的画卷,月影疏疏,落花朵朵,不经意间,看成了风景",
"远处的烟穿过水路,哼着不知名的小调,踏碎了一方的圆月",
"谁家的酒酿醉了空气中潮湿的时光,睁眼瞬间,藏进了楼阁"
][i%4]
avtm.image = ["0.jpg","1.jpg","2.jpg"][i%3]
resList.append(avtm)
}
// 回调数据和当前状态
response(true, resList)
}
}
b. 控制器
控制器需要完成UI的搭建,因此需要一个列表,完成相应的列表代理,再者就是定义数据,完成页面交互逻辑。
Swift 中列表是使用和 OC 非常类型,仅仅是方法的实现和调用方面是链式调用。
import UIKit
class AViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { // 实现列表的代理
// 数据源,延迟属性
lazy var dataArray = [AVTableModel]()
// 列表视图
@IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Swift页面";
// 配置UI
self.setUpUI()
// 获取数据
self.getData() // 获取数据
}
// 配置UI
func setUpUI() {
self.table.tableFooterView = UIView()
}
// 获取数据
func getData() {
// 这里因为是类方法,不会产生循环引用
AVTableManager.requestData { (flag, resList) in
if flag {
self.dataArray = resList!
self.table.reloadData()
}
}
}
// tableView的代理
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 创建cell,并返回给tableView
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.5
}
// 检测释放
deinit {
print("\(self.classForCoder) 释放了。。。")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
c. 视图层
我这里使用的是 xib,使用方式和 OC 下并无多少差异,将一些必要的控件拉到类中做入口。
import UIKit
class AVTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel! // 标题
@IBOutlet weak var subTitleLabel: UILabel! // 子标题
@IBOutlet weak var imgView: UIImageView! // 图片
}
在控制器中完成列表cell的注册,以及cell的赋值
import UIKit
class AViewController: UIViewController,UITableViewDelegate,UITableViewDataSource {
// 数据源,延迟属性
lazy var dataArray = [AVTableModel]()
// 列表视图
@IBOutlet weak var table: UITableView!
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Swift页面";
// 配置UI
self.setUpUI()
// 获取数据
self.getData() // 获取数据
}
// 配置UI
func setUpUI() {
self.table.tableFooterView = UIView()
// 注册cell
self.table .register(UINib.init(nibName: "AVTableViewCell", bundle: nil), forCellReuseIdentifier: cellId)
}
// 获取数据
func getData() {
// 这里因为是类方法,不会产生循环引用
AVTableManager.requestData { (flag, resList) in
if flag {
self.dataArray = resList!
self.table.reloadData()
}
}
}
// tableView的代理
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 将UITableViewCell向下转换为AVTableViewCell
let cell: AVTableViewCell = table.dequeueReusableCell(withIdentifier: cellId) as! AVTableViewCell
let model = self.dataArray[indexPath.row]
// 将数据赋值给cell
cell.titleLabel.text = model.title
cell.subTitleLabel.text = model.subTitle
cell.imgView.image = UIImage.init(named: model.image!)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60.5
}
deinit {
print("\(self.classForCoder) 释放了。。。")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
d. 将数据回传到 OC 页面
这里演示代理和闭包两种方式
首先是代理,和 OC 一样,我们需要先定义一个协议用来约束遵循者完成该协议方法。
import Foundation
@objc protocol AViewControllerDelegate {
// 默认是必须实现的,这里将其定义为可选类型
@objc optional func aViewCtrlData(model: AVTableModel) -> Void
}
那么在控制器中,代理和闭包的声明就可以想下面那样
// 代理,将数据传递回其他类,可选类型
weak var delegate: AViewControllerDelegate?
// 闭包,将第一条数据传递回其他类, 该闭包和代理一样是可选类型
var returnModelBlock: ((_ model: AVTableModel) ->Void)?
我们在用户点击事件中将对应数据回传到 OC 页面中去
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// 我们将前十项的数据使用代理方法回传,剩余的数据使用闭包方式回传
if indexPath.row > 10 {
// 将数据通过代理回传给 OC 页面
let model = self.dataArray[indexPath.row]
self.delegate?.aViewCtrlData!(model: model)
}
else {
// 将数据回传给其他类
if returnModelBlock != nil {
returnModelBlock!(self.dataArray.first!)
}
}
// 返回
self.navigationController?.popViewController(animated: true)
}
f. 在 OC 页面中,使用 Swift 页面中的block和代理和平常没有什么区别
AViewController *ctrl = [AViewController new];
ctrl.delegate = self;
// 这里并没有循环引用,因为self并没有持有ctrl,ctrl仅是被self.navigationController所持有
ctrl.returnModelBlock = ^(AVTableModel * model) {
[self aViewCtrlDataWithModel:model];
};
[self.navigationController pushViewController:ctrl animated:YES];
演示:
注:由于 OC 和 Swift 混编是由桥接文件实现桥接的,因此并不是你写完一个public属性在OC中就能够使用的,在使用前,你应该手动 command + B 进行编译完成桥接文件的内容。你可以使用 command + 点击TestDemo-Swift.h文件 进入桥接文件查看桥接内容。
声明
上述部分内容参考自:
Swift 4.0 教程 – Swift编程
Kenshin Cui’s Blog
作者:LOLITA0164