[iOS] Swift学习文档-框架版

一.前言

随着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)

未完待续, 持续更新中

finally enjoy it.

by objcat

2021.04.07

你可能感兴趣的:([iOS] Swift学习文档-框架版)