二十、从OC到Swift

作者: 爱玩游戏的iOS菜鸟 | 来源:发表于2020-02-18 19:34 被阅读0次

    1、MARK 、TODO、FIXME

    • MARK: 类似OC中的#pragma mark
    • MARK: - 类似OC中的#pragma mark -
    • TODO: 用于标记未完成的任务
    • FIXME: 用于标记待修复的问题

    TODO:FIXME: 可配合#warning("")使用效果更好

    func test(){
        //TODO:未完成
        #warning("TODO:未完成")
    }
    
    func test2(){
        var age = 10
        //FIXME:修复
        #warning("FIXME:修复")
    }
    
    public class Person {
        //MARK: - 属性
        var age = 0
        var weight = 0
        var height = 0
        //MARK: - 私有方法
        //MARK: 跑
        private func run1(){
            
        }
        private func run2(){
            
        }
        //MARK: 吃
        private func eat1(){
            
        }
        private func eat2(){
            
        }
        
        //MARK: - 公共方法
        //MARK: 走
        public func walk1(){
            
        }
        public func walk2(){
            
        }
    }
    

    3、条件编译

    //操作系统:macOS、iOS、tvOS、watchOS、Linux、Android、Windows、FreeBSD

    #if os(macOS) || os(iOS)
    //CPU架构:i386\x86_64\arn\arm64
    #elseif arch(x86_64) || arch(arm64)
    //swifta版本
    #elseif swift(<5) && swift(>=3)
    //模拟器
    #elseif targetEnvironment(simulator)
    //可以导入某模块
    #elseif canImport(Foundation)
    #else
    #endif
    
    自定义标记
    #if DEBUG
    //debug模式
    #else
    //release模式
    #endif
    
    #if TEST
    //debug模式
    #else
    //release模式
    #endif
    
    #if OTHER
    //debug模式
    #else
    //release模式
    #endif
    

    4、打印

    //打印文件路径,代码行数,打印信息
    func ZQlog<T>(_ msg: T, file: NSString = #file, line: Int = #line, fn: String = #function) {
        #if DEBUG
        let prefix = "\(file.lastPathComponent)_\(line)_\(fn)"
        
        print(prefix,msg)
        #else
        
        #endif
    }
    

    5、系统版本检测

    系统版本检测
    @available(iOS 10, macOS 10.15, *)
    class Person { }//在低于某些版本即不可用
    
    struct Student {
        @available(*, unavailable, renamed: "study")
        func study_() { }//方法已不可用,使用study方法
        func study() { }
        
        
        
        @available(iOS, deprecated: 11)
        @available(macOS, deprecated: 10.12)
        func run() { }//方法版本已弃用
    }
    
    var stu = Student()
    stu.study()
    stu.run()//警告:'run()' was deprecated in macOS 10.12
    var person = Person()//报错:'Person' is only available in macOS 10.15 or newer
    

    更多用法参考:API可用性更多用法

    6、iOS程序的入口

    1. Appdelegate上面默认有一个@UIApplicationMain标记,这表示:
    • 编译器自动生成入口代码(main函数代码,自动设置Appdelegate)为APP的代理
    1. 也可以删掉@UIApplicationMain,自定义入口代码:新建一个main.swift文件
    //
    //  main.swift
    //  TestIOS
    //
    //  Created by apple on 2020/2/16.
    //  Copyright © 2020 apple. All rights reserved.
    //
    
    import UIKit
    
    class ZQUIApplication: UIApplication {
        
    }
    
    
    UIApplicationMain(CommandLine.argc, CommandLine.unsafeArgv, NSStringFromClass(ZQUIApplication.self), NSStringFromClass(AppDelegate.self))
    

    7、Swift调用OC代码

    • 新建一个桥接头文件,文件名格式默认为:targetName-Bridging-Header
      桥接头文件

    新建OC文件,也会自动生成桥接文件

    • targetName-Bridging-Header文件中#import OC需要暴露给Swift的内容
    Person.h
    //C语言
    int sum(int a, int b);
    
    //OC对象
    @interface Person : NSObject
    
    @property(nonatomic, copy)NSString *name;
    @property(nonatomic, assign) NSInteger age;
    
    - (instancetype)initWithAge:(NSInteger)age withName:(NSString *)name;
    
    + (instancetype)personWithAge:(NSInteger)age withName:(NSString *)name;
    
    - (void)run;
    + (void)run;
    
    - (void)eat:(NSString *)food other:(NSString *)other;
    + (void)eat:(NSString *)food other:(NSString *)other;
    
    @end
    
    Person.m
    
    #import "Person.h"
    
    @implementation Person
    
    -(instancetype)initWithAge:(NSInteger)age withName:(NSString *)name{
        if (self = [self init]) {
            self.age = age;
            self.name = name;
        }
        return self;
    }
    
    +(instancetype)personWithAge:(NSInteger)age withName:(NSString *)name{
        return [[self alloc]initWithAge:age withName:name];
    }
    
    +(void)run{
        NSLog(@"Person + run");
    }
    
    -(void)run{
        NSLog(@"%zd %@ - run",_age,_name);
    }
    
    +(void)eat:(NSString *)food other:(NSString *)other{
        NSLog(@"Person +eat %@ %@", food, other);
    }
    
    -(void)eat:(NSString *)food other:(NSString *)other{
        NSLog(@"%zd %@ -eat %@ %@", _age, _name, food, other);
    }
    
    @end
    
    int sum(int a, int b){
        return a + b;
    }
    
    Swift调用OC对象
    var person = Person(age: 10, withName: "Jack")
    
    person.age = 18
    person.name = "Rose"
    
    person.run()//输出:18 Rose - run
    person.eat("Apple", other: "Android")//输出:Rose -eat Apple Android
    
    Person.run()//输出:Person + run
    Person.eat("Java", other: "C++")//输出:Person +eat Java C++
    
    print(sum(10, 20))//输出:30
    
    • 如果C语言暴露给Swift的函数名跟Swift中的其他函数名冲突了,可以再Swift中使用@_silgen_name修改C函数名
    //调用C函数时 修改C函数名的同时 保证参数类型,返回值与原函数一致
    @_silgen_name("sum") func swift_sum(_ v1: Int32, _ v2: Int32) -> Int32
    print(swift_sum(10, 20))//输出:30
    

    8、OC调用Swift代码

    1. Xcode已经默认生成一个用于OC调用Swift的头文件,文件名格式是:targetName-Swift.h
      Xcode已经默认生成,可以调用(虽然项目中看不见)
    2. Swift会暴露给OC的类最终继承自NSObject(只要继承就会暴露,但如果不暴露其初始化器,则无法初始化)
    3. 使用@objc修饰需要暴露给OC的成员
    4. 使用@objcMembers修饰类
    • 代表默认所有成员都会暴露给OC(包括扩展中定义的成员)
    • 最终是否成功暴露,还需要考虑成员自身的访问级别
    Swift —— Car声明及扩展
    @objcMembers class Car: NSObject {//@objcMembers标记 暴露所有成员(方法,属性)
        var price: Double
        var band: String//@objc 标记单个属性、方法暴露给OC
        init(price: Double, band: String) {
            self.price = price
            self.band = band
        }
        func run() {
            print(price,band,"run")
        }
        static func run(){
            print("Car,run")
        }
    }
    
    extension Car{
        func test() {
            print(price,band,"test")
        }
    }
    
    testSwift()
    
    1. Xcode会根据swift生成对应的OC声明,写入taretName-Swift.h 文件
      编译后,example-Swift中生成对应的OC代码供OC文件使用
    .h
    void testSwift();
    
    .m
    #import "example-Swift.h"
    
    void testSwift(){
        NSLog(@"testSwift");
        
        Car *car = [[Car alloc] initWithPrice:110000 band:@"benz"];
        car.price = 120000;
        car.band = @"audi";
        [car run];
        [car test];
        [Car run];
    }
    
    1. 可以通过@objc重命名swift暴露给OC的符号名(类名、属性名、函数名等) ——相应生成的taretName-Swift.h 文件也会改动,调用方式也一并更改
    @objc(ZQCar)
    @objcMembers class Car: NSObject {
        var price: Double
        @objc(name)
        var band: String
        init(price: Double, band: String) {
            self.price = price
            self.band = band
        }
        
        @objc(drive)
        func run() {
            print(price,band,"run")
        }
        static func run(){
            print("Car,run")
        }
    }
    
    extension Car{
        @objc(fix)
        func test() {
            print(price,band,"test")
        }
    }
    

    选择器

    1. Swift中依然可以使用选择器,使用#seletor(name)定义一个选择器
    • 前提是必须被@objcMambers或@objc修饰的方法才可以定义选择器
    @objc(Swift_Person)
    @objcMembers class Person: NSObject{
        func test1(v1: Int) {
            print("test1")
        }
        func test2(v1: Int, v2: Int) {
            print("test2(v1:v2:)")
        }
        func test2(_ v1: Double, _ v2: Double) {
            print("test2(v1:v2:)")
        }
        func run() {
            perform(#selector(test1))
            perform(#selector(test1(v1:)))
            perform(#selector(test2(_:_:)))
            perform(#selector(test2(v1:v2:)))
            perform(#selector(test2 as (Double, Double) -> Void))
        }
    }
    

    String(具体用法见第三章 此处只讲SubString及String)

    Substring
    • Swift的字符串类型String,与OC的NSString,在Api设计上还是有很大差异的
    • String可以通过下标、prefix、subffix等截取字符串,子串类型不是String,而是Substring
    • Substring与它的base String共享字符串数据
    • Substring发生修改或转为String时,会分配新的内存存储字符串数据
    var str = "1_2_3_4_5#6*7"
    var substr1 = str.prefix(3)//String.SubSequence类型 Substring
    var substr2 = str.suffix(3)
    print(substr1,substr2)//输出:1_2 6*7
    
    var rang = str.startIndex ..< str.index(str.startIndex, offsetBy: 4)
    var substr3 = str[rang]
    
    //substring.base 原字符串
    print(substr3,substr3.base)//1_2 6*7  1_2_ 1_2_3_4_5#6*7
    
    //如果对substring进行修改或转为string时,会重新分配内存 也不会影响原字符串
    substr3.append(contentsOf: "zzz")
    print(substr3,substr3.base)////输出:1_2_zzz 1_2_zzz
    
    var str2 = String(substr3)//Substring转String
    
    String相关的协议
    1. BidirectionalCollection协议包含内容
    • startIndex、endIndex属性,Index方法
    • String、Array都遵守了这个协议
    1. RangeReplaceableCollection协议包含的内容
    • replaceSubrange 、append、insert、remove方法
    • String、Array都遵守了这个协议
    1. Dictionary、Set也有实现上述协议中声明的一些方法,只是并没有遵守上述协议
    String与NSString
    1. String与NSString之间可以随时随地的桥接转换
    var str1: String = "Jack"
    var str2: NSString = "rose"
    var str3: NSString = str1 as NSString//底层是调用了桥接函数的 且str1修改 str3不受影响
    var str4: String = str2 as String
    
    var str5 = str3.substring(with: NSRange(location: 0, length: 2))//String
    
    1. 比较字符串等价
    • String使用==运算符
    • NSString使用isEqual方法,也可以使用==运算符(本质还是调用了isEqual)

    Swift、OC桥接转换表

    桥接转换表

    协议

    只能被class继承的协议
    • 被@objc修饰的协议,还可以暴露给OC去遵守实现
    protocol Procotol1: AnyObject {
        
    }
    
    protocol Procotol2: class {
        
    }
    
    @objc protocol Procotol3 {
        
    }
    
    可选协议
    • 可以通过@objc定义可选协议,这种协议只能被class遵守
    • 也可以使用扩展实现协议,达到可选协议的效果
    @objc protocol Runnable {
        func run1()
        @objc optional func run2()
        func run3()
    }
    
    class Dog: Runnable {
        //Runnable只能被类遵守 run2()可以不用实现
        func run1(){
            
        }
        
        func run3() {
            
        }
    }
    

    dynamic

    • 被@objc dynamic 修饰的内容会有动态性,比如调用方法会走runtime那一套流程
    class Dog: NSObject {
        @objc dynamic func test1() {
            
        }
        func test2() {
            
        }
    }
    
    var dog = Dog()
    dog.test1()//OC runtime那一套 msgSend消息机制
    dog.test2()//Swift 虚表
    

    KVC/KVO

    1. Swift支持KVC \ KVO的条件
    • 属性所在的类、监听器最终继承自NSObject
    • 用@ibjc dynamic修饰对应的属性
    class Observer: NSObject {
       override class func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
           print("observeValue",change?[.newKey] as Any)
       }
    }
    
    class Student: NSObject {
       @objc dynamic var age :Int = 0
       var observer:Observer = Observer()
       override init() {
           super.init()
           self.addObserver(observer, forKeyPath: "age", options: .new, context: nil)
       }
       deinit {
           self.removeObserver(observer, forKeyPath: "age", context: nil)
       }
    }
    
    var person  = Student()
    person.age = 20
    person.setValue(25, forKey: "age")
    
    block方式的KVO
    class Student: NSObject {
        @objc dynamic var age:Int = 0
        var observation:NSKeyValueObservation?
        override init() {
            super.init()
            observation = observe(\Student.age, options: .new, changeHandler: { (person, change) in
                print(change.newValue as Any)
            })
        }
        
    }
    
    var stu = Student()
    stu.age = 20//输出:Optional(20)
    stu.setValue(25, forKey: "age")//输出:Optional(25)
    

    关联对象(Associated Object)

    • Swift对象中,class依然可以使用关联对象
    • 默认情况下,extension不可以增加存储属性
    • 借助关联对象,可以实现extension为class增加存储属性的效果
    class Student {
        
    }
    
    extension Student{
        private static var AGE_KEY:Void?
        var age: Int{
            get{
                ((objc_getAssociatedObject(self, &Self.AGE_KEY)) as? Int) ?? 0
                
            }
            set{
                (objc_setAssociatedObject(self, &Self.AGE_KEY, newValue, .OBJC_ASSOCIATION_ASSIGN))
            }
        }
    }
    

    资源名管理

    • 这种做法实际参考了Andriod的资源名管理方式


      资源名调用
      添加响相应的扩展
    资源名管理方式其他思路

    优秀的资源名管理方式参考1
    优秀的资源名管理方式参考2

    多线程开发

    异步
    
    DispatchQueue.global().async {
        //异步操作
        DispatchQueue.main.async {
            //回到主线程
        }
    }
    
    //使用DispatchWorkItem
    let item = DispatchWorkItem {
                print("先执行")
            }
    DispatchQueue.global().async(execute: item)
    item.notify(queue: DispatchQueue.main) {
      print("后执行")
    }
    
    延迟
    //主线程延迟second
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0) {
                print("333")
            }
    
    异步延迟
    let item = DispatchWorkItem {
                print("异步延迟执行")
            }
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3.0, execute: item)
            item.notify(queue: DispatchQueue.main) {
                print("回到主线程执行")
            }
    //可以取消
    item.cancel()
    
    

    下面对上面的进行简单的封装:

    //封装.swift文件
    public typealias Task = () -> Void
    
    struct Asyncs {
        
        //传一个异步任务
        public static func async(task: @escaping Task){
            _async(task: task)
        }
        //传一个异步任务 和 异步任务完成后回到主线程需要做的任务
        public static func async(task: @escaping Task, _ mainTask: @escaping Task){
            _async(task: task, mainTask)
        }
        
        private static func _async(task: @escaping Task, _ mainTask: Task? = nil){
            let item = DispatchWorkItem(block: task)
            DispatchQueue.global().async(execute: item)
            if let main = mainTask {
                item.notify(queue: DispatchQueue.main, execute: main)//
            }
        }
        
        //延迟
        @discardableResult
        public static func delay(_ second:Double, _ block: @escaping Task) -> DispatchWorkItem{
            let item = DispatchWorkItem(block: block)
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + second, execute: item)
            return item
        }
        
        //异步延迟
        @discardableResult
        public static func asyncDelay(_ seconds: Double, _ task:@escaping Task) -> DispatchWorkItem{
            //TODO:
            _asyncDelay(seconds, task)
        }
        
        //异步延迟
        @discardableResult
        public static func asyncDelay(_ seconds: Double, _ task:@escaping Task, _ maintask: @escaping Task) -> DispatchWorkItem{
            //TODO:
            _asyncDelay(seconds, task, maintask)
        }
        
        private static func _asyncDelay(_ seconds: Double, _ task:@escaping Task, _ maintask: Task? = nil) -> DispatchWorkItem{
            let item = DispatchWorkItem(block: task)
            DispatchQueue.global().asyncAfter(deadline: DispatchTime.now() + seconds, execute: item)
            if let main = maintask{
                item.notify(queue: DispatchQueue.main, execute: main)
            }
            return item
        }
    }
    
    //调用
            //异步线程
            Asyncs.async {
                print("1",Thread.current)//1 异步线程
            }
            //异步线程 回到主线程执行下一任务
            Asyncs.async(task: {
                print("2",Thread.current)//2 异步线程
            }) {
                print("3",Thread.current)//3 主线程
            }
            //主线程延迟
            Asyncs.delay(3.0) {
                print("4",Thread.current)
            }
            
            //异步延迟
            Asyncs.asyncDelay(4.0) {
                print("异步延迟",Thread.current)
            }
            
            Asyncs.asyncDelay(5.0, {
                print("异步延迟",Thread.current)
            }) {
                print("异步延迟,回到主线程",Thread.current)
            }
    
    once
    • swift中已经废弃了dispatch_once
    • 可以用类型属性或者全局变量\常量,因为默认自带lazy + dispatch_once效果

    此处可以查看属性章节-属性

    加锁
    • gcd信号量(方法1)
    • Foundation(方法2)
    public struct Cache{
        private static var data = [String: Any]()
    //    private static var lock = DispatchSemaphore(value: 1) 方法1:
    //    private static var lock = NSLock() 方法2:
    //    private static var lock = NSRecursiveLock() 递归锁
        
        public static func get(_ key: String) -> Any?{
            data[key]
        }
        
    //    public static func set(_ key: String, _ value: Any){  方法1:
    //        lock.wait()
    //        defer {
    //            lock.signal()
    //        }
    //        data[key] = value
    //    }
        
    //    public static func set(_ key: String, _ value: Any){ 方法2:
    //        lock.lock()
    //        defer {
    //            lock.unlock()
    //        }
    //        data[key] = value
    //    }
    }
    

    相关文章

      网友评论

        本文标题:二十、从OC到Swift

        本文链接:https://www.haomeiwen.com/subject/kyqexhtx.html