一.前言
随着Swift的逐渐完善, 越来越多的开发者转型到Swift放弃OC, 其中一大部分是2017年后的培训机构, 一部分是OC开发者迫于公司项目转型的, 这样一来国内iOS市场不仅被跨平台蚕食又被Swift蚕食, 老的OC程序员找工作越来越困难, 作为一名 OC拥护者
我想我也有写一点东西的必要了, 本文主要讲的就是OC转型Swift需要注意的地方, 我希望把它作为长期项目写下去, 需要用到的时候就直接查找, 不至于手忙脚乱, 下面就跟着我们的文章一起来看吧.
温馨提示: 文章内容是根据自己的理解试出来的, 有些来自于百度, 请酌情阅读, 如有错误请提醒我及时修改.
二.MVC
(1) Model
我们可以看到Swift
中定义属性, 直接干掉了属性修饰符, 与之代替的是可选类型
和泛型
OC
@interface Student : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *arr;
@property (strong, nonatomic) NSDictionary *dic;
@end
温馨提示: 请不要跟我辩论字符串是否必须用copy谢谢
Swift
class Student {
var name: String?
var arr: Array?
var dic: Dictionary?
}
getter / setter
swift与oc不同的是 swift不会自动生成内部成员变量 如果想储存setter需要第二个变量 如果写set就必须写get 如果只写get那就是只读, 而oc只读使用readonly修饰
class Cat {
private var _name: String?
var name: String? {
set {
_name = newValue;
}
get {
return _name;
}
}
}
willSet / didSet
swift为了赋值方便提供了只写set的方法, 它相当于一个钩子, 虽然不能改变set的值, 但是能在赋值前和赋值后去做一些事情
class Cat {
var name: String? {
willSet {
print("赋值前 " + (self.name ?? ""))
}
didSet {
print("赋值后 " + (self.name ?? ""))
}
}
}
(2) View
这个模块感觉没什么好说的 以后想起来再说吧 上面刚讲完willset和didset 下面就来简单的讲一下如何使用
cell中赋值的方法
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
cell.model = self.arr?[indexPath.row] as? CustomModel;
cell.selectionStyle = .none;
return cell
}
class CustomTableViewCell: UITableViewCell {
var model: CustomModel? {
didSet {
self.titleLabel.text = self.model?.name;
}
}
@IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
(3) Controller
感觉这个也没啥好说的, 说一下dealloc吧, 我觉得视图控制器最重要的就是释放, 当然你如果觉得不重要就当我没说 - -
OC
- (void)dealloc {
NSLog(@"控制器释放---%@", NSStringFromClass([self class]));
}
Swift
可以看到swift使用deinit取代了dealloc
deinit {
print("控制器释放---\(type(of: self))")
}
三.框架
数据解析
下面来介绍一下 json字符串转字典和字典转模型
字符串转字典
let jsonString = """
{"name": "张三"}
"""
do {
let object = try JSONSerialization.jsonObject(with: jsonString.data(using: .utf8) ?? Data(), options: .allowFragments)
print(object)
} catch {
print(error)
}
或
// 处理可能没有
let object = try? JSONSerialization.jsonObject(with: jsonString.data(using: .utf8) ?? Data(), options: .allowFragments)
print(object ?? [])
// 处理一定有
let object = try! JSONSerialization.jsonObject(with: jsonString.data(using: .utf8) ?? Data(), options: .allowFragments)
print(object)
字典转model
pod 'KakaJSON'
https://github.com/kakaopensource/KakaJSON
class Cat: Convertible {
required init() {}
var name: String?
}
let catJson: [String: Any] = [
"name": "huahua"
]
let cat = catJson.kj.model(Cat.self)
数组转model数组
let catJsonArray: [Any] = [
["name": "huahua"],
["name": "zhangsan"],
["name": "lisi"]
]
let catArray = catJsonArray.kj.modelArray(Cat.self)
model中包含其他对象
class Person: Convertible {
required init() {}
var name: String?
var cat: Cat?
}
let personJson: [String: Any] = [
"name": "huahua",
"cat": ["name": "huahua"]
]
let person = personJson.kj.model(Person.self)
model数组属性中包含其他对象
class Person: Convertible {
required init() {}
var name: String?
var cat: Cat?
var cats: [Cat]?
}
let personJson: [String: Any] = [
"name": "huahua",
"cat": ["name": "huahua"],
"cats": [
["name": "huahua"],
["name": "zhangsan"],
["name": "lisi"]
]
]
let person = personJson.kj.model(Person.self)
model转json
let personDict = JSONObject(from: person)
model属性替换
有时候定义的属性不一定和json完全对上 为了能够进行解析 需要进行属性替换
比如这里有cat 但是定义在model中的是cat2 需要把cat解析到cat2上
这里有一个口诀(顺序)
没有cat2就找cat
let personJson: [String: Any] = [
"name": "huahua",
"cat": ["name": "huahua"],
"cats": [
["name": "huahua"],
["name": "zhangsan"],
["name": "lisi"]
]
]
class Person: Convertible {
required init() {}
var name: String?
var cat2: Cat?
var cats: [Cat]?
func kj_modelKey(from property: Property) -> ModelPropertyKey {
switch property.name {
case "cat2": return "cat"
default: return property.name
}
}
}
json属性替换
有时候转化成json的时候需要替换某些key
拿上面的举例子 转回json的时候应该是cat2 如果这时你想转回原json 也就是cat
那么需要进行json属性替换
func kj_JSONKey(from property: Property) -> JSONPropertyKey {
switch property.name {
case "cat2": return "cat"
default: return property.name
}
}
网络请求
pod 'Alamofire'
https://github.com/Alamofire/Alamofire
import Alamofire
// get 默认
AF.request("http://localhost:8080/api/v1/hello").response { response in
debugPrint(response)
}
// post
AF.request("http://localhost:8080/api/v1/hello2", method: .post).response { response in
debugPrint(response)
}
json解析成字典
AF.request("http://localhost:8080/api/v1/hello").response { response in
let json = try? JSONSerialization.jsonObject(with: response.data ?? Data(), options: .allowFragments) as? Dictionary
print(json ?? Dictionary())
}
json解析成对象
import Foundation
import KakaJSON
class DataModel: Convertible {
required init() {}
var success: Bool?
var result: Dictionary?
var code: Int?
}
AF.request("http://localhost:8080/api/v1/hello3").response { response in
let model = response.data?.kj.model(DataModel.self)
debugPrint(model?.result ?? Dictionary())
}
图片展示
没啥好说的
pod 'Kingfisher'
https://github.com/onevcat/Kingfisher
import Kingfisher
self.coverImageView?.kf.setImage(with: URL(string: "https://img2.baidu.com/it/u=2037072674,1804134981&fm=26&fmt=auto&gp=0.jpg"))
布局框架
基本就跟Masonry
一样
pod 'SnapKit'
https://github.com/SnapKit/SnapKit
完全铺满
import SnapKit
let view = UIView()
view.backgroundColor = .red
self.view.addSubview(view)
view.snp.makeConstraints { (make) in
make.edges.equalTo(self.view)
}
安全区域铺满
view.snp.makeConstraints { (make) in
if #available(iOS 11, *) {
make.edges.equalTo(self.view.safeAreaLayoutGuide)
} else {
make.top.equalTo(self.topLayoutGuide.snp.top)
make.bottom.equalTo(self.bottomLayoutGuide.snp.bottom)
make.left.equalTo(self.view)
make.right.equalTo(self.view)
}
}
// 其中make.edges.equalTo(self.view.safeAreaLayoutGuide)可以拆分成
self.view.safeAreaLayoutGuide.snp.top
self.view.safeAreaLayoutGuide.snp.bottom
self.view.safeAreaLayoutGuide.snp.left
self.view.safeAreaLayoutGuide.snp.right
看到区别了么 导航栏上面的红色不见了 这就是安全区域
Toast
pod 'Toast-Swift'
https://github.com/scalessec/Toast-Swift
基本使用
self.view.makeToast("123", duration: 3, position: .center)
全局设置样式
主要是用 ToastManager 的单例实现的
var toastStyle = ToastStyle()
toastStyle.messageColor = .blue
ToastManager.shared.style = toastStyle
ToastManager.shared.isTapToDismissEnabled = false
ToastManager.shared.isTapToDismissEnabled = false
四.混编
在swift开发中难免会用到oc的库, 那么要怎么使用呢
导入库
如果是库直接就可以使用
import SDWebImage
self.coverImageView?.sd_setImage(with: URL(string: "https://img2.baidu.com/it/u=2037072674,1804134981&fm=26&fmt=auto&gp=0.jpg"), completed: nil)
自定义类
如果是自定义类 就需要创建桥接文件了
Swift中调用OC
1.在桥接文件中引入OC类
2.直接在swift中使用
let person = Person()
person.name = "123"
print(person)
OC中调用Swift
1.导入swift头文件 规则是#import "项目名-Swift.h"
, 我的项目叫SwiftProject
#import
#import "SwiftProject-Swift.h"
@interface Abc : NSObject
@end
2.使用
虽然Student是Swift类, 但使用方法与OC无异, 唯一需要注意的是Swift类的成员变量OC不能直接使用, 需要@objc
, 否则OC无法访问, 并且一定要继承一个基类, 否则OC无法识别
class Student: NSObject {
@objc var name: String?
}
@implementation Abc
- (instancetype)init
{
self = [super init];
if (self) {
Student *stu = [[Student alloc] init];
stu.name = @"123";
NSLog(@"%@", stu.name);
}
return self;
}
@end
但有时候会出现这个问题
Cycle inside SwiftProject; building could produce unreliable results. This usually can be resolved by moving the target's Headers build phase before Compile Sources.
Cycle details:
→ Target 'SwiftProject': CodeSign /Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Products/Debug-iphonesimulator/SwiftProject.app
Target 'SwiftProject' has process command with output '/Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Products/Debug-iphonesimulator/SwiftProject.app/Info.plist'
Target 'SwiftProject' has compile command with input '/Users/macmini/demo/SwiftProject/SwiftProject/Assets.xcassets'
Target 'SwiftProject' has compile command with input '/Users/macmini/demo/SwiftProject/SwiftProject/Abc.m'
Target 'SwiftProject' has compile command for Swift source files
Target 'SwiftProject': Ditto /Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Intermediates.noindex/SwiftProject.build/Debug-iphonesimulator/SwiftProject.build/DerivedSources/SwiftProject-Swift.h /Users/macmini/Library/Developer/Xcode/DerivedData/SwiftProject-djnxjwwjwnrhjqbulwazwcvkbjnh/Build/Intermediates.noindex/SwiftProject.build/Debug-iphonesimulator/SwiftProject.build/Objects-normal/x86_64/SwiftProject-Swift.h
Target 'SwiftProject' has compile command for Swift source files
原因是可能是swift和oc头文件循环引用了, 解决方案是去掉bridge中的oc类 或者 去掉类中的swift头文件
个人总结: 即一个开放给swift的OC类不能再调用swift的类
五.文档
数据类型
OC
typedef NS_ENUM(NSUInteger, StudentGender) {
StudentGenderBoy,
StudentGenderGirl
};
@interface Student : NSObject
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSArray *arr;
@property (strong, nonatomic) NSDictionary *dic;
@property (strong, nonatomic) NSNumber *number;
@property (assign, nonatomic) NSInteger integer;
@property (assign, nonatomic) float testFloat;
@property (assign, nonatomic) double testDouble;
@property (assign, nonatomic) BOOL selected;
@property (assign, nonatomic) StudentGender gender;
@end
Swift
enum StudentGender {
case StudentGenderBoy
case StudentGenderGirl
}
class Student: NSObject {
var name: String?
var arr: Array?
var dic: Dictionary?
var number: Int?
var testFloat: Float?
var testDouble: Double?
var selected: Bool?
var gender: StudentGender?
}
Block
之所以放在这里是因为Block是与OC区别最大的东西, 下面就简单来说一说
创建
let testBlock = {() -> Void in
print("无参数无返回值")
}
testBlock()
// 或 下面这种是匿名立即执行的简便写法
// 无参数无返回值
{() -> Void in
print("无参数无返回值")
}()
// 有参数无返回值
{(a: String) -> Void in
print(a)
print("有参数无返回值")
}("123")
// 有参数有返回值
{(a: String) -> String in
print("有参数有返回值")
return a;
}("123")
回调
block回调在oc中非常常见, 那么在swift中要怎么使用呢
func say(callback: ()->Void) -> Void {
callback()
}
func say(name: String, callback: ()->Void) -> Void {
callback()
}
func say(name: String, callback: ()->Void, callback2: ()->Void) -> Void {
callback()
callback2()
}
func say(name: String, callback: (_ a: String)->Void) -> Void {
callback(name)
}
使用也很简单 自己领悟吧
let stu = Student()
stu.say {
}
stu.say(name: "456") {
}
stu.say(name: "4564") {
} callback2: {
}
stu.say(name: "789") { (a) in
print(a)
}
解决循环引用
先制造一个
var callback: (() -> Void)?
var stu: Student?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.stu = Student()
self.stu?.say {
self.navigationController?.title = "123"
}
}
点击返回发现控制器不释放了 stu和self循环引用了 怎么解决呢
self.stu?.say { [weak self] in
self?.navigationController?.title = "123"
}
可以看到 swift 是用了 [weak self]
解决
六.SwiftUI
在老项目中引用
let vc = UIHostingController(rootView: SwiftUIView())
// push
self.navigationController?.pushViewController(vc, animated: true)
未完待续, 持续更新中