iOS 快速从OC过渡到Swift,由理论到实战-OC和Swift混编

游星啊 2018-09-19 11:16:49 7427
本文来自 LOLITA0164 ,作者 游星啊

接上篇文章:iOS 快速从OC过渡到Swift,由理论到实战-Swift基础

OC和Swift混编

a. Swift 和 OC 的映射关系

1.png

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<UITouch *> *)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 AViewControllerUIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Swift页面";
        self.view.backgroundColor = UIColor.red
    }

    override func touchesEnded(_ touches: Set<UITouch>, 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..<endIndex]
        startIndex = cString.index(startIndex, offsetBy: 2)
        endIndex = cString.index(startIndex, offsetBy: 2)
        let gStr = cString[startIndex..<endIndex]
        startIndex = cString.index(startIndex, offsetBy: 2)
        endIndex = cString.index(startIndex, offsetBy: 2)
        let bStr = cString[startIndex..<endIndex]
        var r :UInt32 = 0x0;
        var g :UInt32 = 0x0;
        var b :UInt32 = 0x0;
        Scanner.init(string: String(rStr)).scanHexInt32(&r);
        Scanner.init(string: String(gStr)).scanHexInt32(&g);
        Scanner.init(string: String(bStr)).scanHexInt32(&b);
        return rgb(CGFloat(r), CGFloat(g), CGFloat(b))
    }
    /// 随机色
    static var rand: UIColor {
        return rgb(CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)), CGFloat(arc4random_uniform(255)))
    }
}

字符转数字类

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 AVTableModelNSObject {
    var title: String?
    var subTitle: String?
    var image: String?
}

View-Model:

import UIKit

class AVTableManagerNSObject {
    // 定义数据请求,使用闭包回调
    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 AViewControllerUIViewController,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 AVTableViewCellUITableViewCell {
    @IBOutlet weak var titleLabel: UILabel!      // 标题
    @IBOutlet weak var subTitleLabel: UILabel!   // 子标题
    @IBOutlet weak var imgView: UIImageView!     // 图片
}

在控制器中完成列表cell的注册,以及cell的赋值

import UIKit

class AViewControllerUIViewController,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

链接:https://blog.csdn.net/LOLITA0164/article/details/82017800