美文网首页IOS网友们的篇章IOS三人行Swift&Objective-C
Swift的struct设计理念 - 简单又可靠

Swift的struct设计理念 - 简单又可靠

作者: flionel | 来源:发表于2016-08-06 12:21 被阅读4451次
    city.jpg

    Struct概述

    Swift语言有两种基本的数据类型,即类(class)和结构体(struct),class这样的概念大家不会陌生,而struct也并不是什么新的概念,在Objective-C和C++也有struct,不过swift将struct提升到一个更高更重要的层次,甚至swift Foundation框架的SDK,诸如String,Array,Dictionary都是基于struct实现的。

    笔者刚开始接触swift时,认为struct是一个附属品,然而随着开发的深入和阅读代码量的上升,发现struct的使用场景很多,而且很好用。

    那么struct与class相比,有什么区别呢?主要的区别就在于class是类型引用,而struct是值引用,在Objective-C时代,我们对类型引用和值引用就有了一定的了解,例如在Objective-C中常用的NSArray, NSDictionary, NSString, UIKit等都是类型引用;而NSInteger, CGFloat, CGRect等则是值引用。显然,在Objective-C中,引用类型占据了很大的比重,现在使用swift开发应用程序,开发者需要转变观念,因为struct在swift变得越来越重要,观念的转变不仅在于多使用struct,还要求开发者理解struct的原理,优点及缺点。

    在swift中,类型引用和值引用的区别在于,对于类型引用(class reference),将变量a赋值给变量b,即b = a,这样的赋值语句仅仅将b的指针与a的指针一样,指向同一块内存区域,此时改变b的值,a也会跟着改变;而对于值引用(value reference),赋值语句b = a处理的过程是开辟一个新的内存b,将a变量的内容拷贝后存放到内存b,这时a和b完全没有关系的两个变量,对b的改变不会影响到a,反之亦然。

    下面运行于Xcode playground的demo说明了class的类型引用和struct的值引用的区别,

    Swift class类型引用(class reference)的demo展示

    class SomeClass {
        var name: String?
        init(name: String) {
            self.name = name
        }
    }
    
    var aClass = SomeClass(name: "dante")
    var bClass = aClass //此时bClass和aClass指向同一个内存区域
    // 改变bClass的name值
    bClass.name = "flion"
    print(aClass.name) // "flion"
    print(bClass.name) // "flion"
    

    此处的print结果是Optional值,"flion"是默认已经解包Optional变量,简写是为了demo说明,下面的demo也一样,不再赘述。

    上述代码首先定义了SomeClass以及-init初始化方法,然后创建name值为"dante"的aClass,接着将aClass赋值给bClass,此时改变bClass的name值为"flion",aClass的name值也跟着改变,这证明了类型引用的赋值操作实际将两个变量的指针指向了同一块区域。

    用图简单说明,

    reference-assignment.png

    Swift struct值引用(value reference)的demo展示

    struct SomeStruct {
        var name: String?
        init(name: String) {
            self.name = name
        }
    }
    
    var aStruct = SomeStruct(name: "dante")
    var bStruct = aStruct
    bStruct.name = "flion"
    print(aStruct.name) // "dante"
    print(bStruct.name) // "flion"
    

    这里定义了struct SomeStruct,创建name值为"dante"的aStruct变量,将aStruct赋值给bStruct变量,改变bStruct的name值为"flion",此时aStruct的name仍然是"dante",并没有改变,这是因为aStruct和bStruct是两块不同的内存,两者之间并没有联系。

    值引用赋值过程,如图所示,

    value-assignment.png

    为什么需要struct?

    struct和class的主要区别,

    • struct是值引用,而class是类型引用
    • struct没有继承的功能,class有继承功能

    struct和class这两个基本层面的区别,体现了区别于Objective-C语言,swift语言带来了全新的天翻地覆的改变。

    首先说第一点区别,从swift的更新和struct不断完善来看,苹果公司更加推荐使用struct来代替class,因为struct值引用class类型引用这点区别,保证使用struct编码能写出更加安全可靠的代码。为什么这样说呢,class类型引用在赋值时是将变量指向了同一块内存地址,这在一个长时间的跨度上会带来一些意想不到的问题,试想一个简单的例子,viewControllerA持有一个NSMutableArray数组mutalbeArray,它包含100条user信息,此时将mutableArray赋值给viewControllerB,对于viewControllerB而言,它仅仅需要前10条user信息,所以它将mutableArray多余的信息删除了,这样一个脑残的操作导致了viewControllerA模块展示错误和潜在的逻辑错误。而使用struct值引用则不会出现这样的问题。

    第二点区别,struct没有继承的功能,这是因为swift在本质上来说是面向协议(Protocol Oriented)的语言,struct没有也不需要继承的功能,为了实现某个功能,struct去服从并实现某个协议就即可,从一个较高的层次来看,struct+protocol是构成swift面向协议语言的两个基石。这一点不在本文讨论范围,不再赘述。

    为什么要使用struct呢?总结就是struct可以保证代码更加安全可靠,以及struct+protocol更加切合swift面向协议编程的初衷。

    struct基本语法

    和class一样,struct也可以定义属性和方法,同样struct也要求完整初始化,即保证初始化过程中每一个non-optional属性要赋予明确的初始值,如下代码定义了Person struct,

    struct Person {
        var firstName: String
        var secondName: String? // optional value
        var salary: Int
        
        // default designed init - 默认的指定初始化方法
        init() {
            firstName = "flion"
            salary = 100
        }
        
        // 带参数的指定初始化
        init(firstName: String, secondName: String?, salary: Int) {
            self.firstName = firstName
            self.secondName = secondName
            self.salary = salary
        }
        
        
        func eat(foodName: String) -> Void {
            print("eat food " + foodName)
        }
        
        
        func fullName() -> String {
            if let tempSecondName = secondName {
                return firstName + "-" + tempSecondName
            } else {
                return firstName
            }
        }
    }
    
    let flion = Person(firstName: "flion", secondName: "dep", salary: 100)
    print(flion.fullName())
    

    swift的class -init初始化方法有很多的规范和要求,对于初学者来说这是一个很大的难度和挑战,这不在本篇文章的讨论范围,读者可参考苹果官方文档Swift Init查看更多内容,在此不再赘述。

    与class的-init初始化方法不同的是,struct的-init并没有便利初始化(convenience init)方法;又因为struct没有继承,所以struct的-init也不需要required关键字。这样对比,struct的-init初始化方法比起class的-init初始化方法来说要简单的多,因为struct只有designed init即指定初始化,struct只需要保证指定初始化过程中每个非可选属性都赋值,没有复杂的-init初始化规则和规范,struct相比于class也显得更加简单,更有亲和力。

    笔者在struct init尝试使用convenicence和requeired关键字时候,Xcode提示编译错误,所以根据观察现象和思维分析作的这样一个结论,并没有参考官方文档或者经过严格的验证,如果这个说法有错误,请帮我指正,thank U。

    参考链接

    公众号

    欢迎关注本人公众号 foolishlion,请扫描下方二维码,

    foolishlion.jpg

    相关文章

      网友评论

      • Oo晨晨oO:如果模型层使用了Struct 那么还能用YYModel之类的来直接转换Json成模型吗? 我觉得如果有网络访问的话,直接跟网络模型对应的Model还是要使用Class 以便于使用各种轮子. 但是对于MVVM模式的工程来说 , viewModel可以使用struct来完全代替会非常好
      • Yinper:楼主的实例 SomeStruct name 属性 , 不用初始化也可以

        NSDictionary* dictA = @{@"a": @"a"};
        NSDictionary* dictB = dictA;
        dictB = @{@"b": @"b"};
        NSLog(@"%@- %p",dictA, &dictA); // {a = a}
        NSLog(@"%@- %p",dictB, &dictB); // {b = b}
        // 内存地址也不同, 难道 oc 这种基础的数据结构也是 [值引用]? 还是系统默认 copy ?
        iOS程序员asdf:你这个相当于给dictB重新赋值了,不是改变这个内存里面的元素
      • 孤独枫叶:那是不是可以用struct来代替class,实现各种功能那,或者说,就相当于不用class了
        孤独枫叶:@foolishlionel 哦,数据处理层可以用,但是UI的还是不能替代的是吧
        flionel:@孤独枫叶 模型层可以完全用struct来替代class

      本文标题:Swift的struct设计理念 - 简单又可靠

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