首先来看一下Objective-C中的拷贝与可变性。为了解释方便,定义两个类:PersonMyObject,它们都继承自NSObject。他们的关系如下:

// Person.h
@property (strong,nonatomic,nullable) MyObject *object;
// MyObjec.h
copy,168)">nonatomic) Nsstring *name;

普通对象拷贝

对于一个OC中的对象来说,可能涉及拷贝的有三种操作:

  1. retain操作:

    objc Person *p = [[Person alloc] init]; Person *p1 = p;

    这里的p1默认是__strong,所以它会对p进行retain操作。retain与复制无关,只会对引用计数加1。p1p的地址是完全一样的:

    objc 2015-12-23 21:24:31.893 copy[1300:120857] p = 0x1006012c0 2015-12-23 21:24:31.894 copy[1300:120857] p1 = 0x1006012c0

    这种写法最简单,而且严格来说不是复制,但值得一提,因为在接下来的OC和Swift中,都会涉及到这样的代码。

  2. copy方法:

    调用copy方法需要实现NScopying协议,并提供copyWithZone方法:

    ```objc

    • (id)copyWithZone:(NSZone *)zone { Person *copyInstance = [[self class] allocWithZone:zone]; copyInstance.object = self.object; return copyInstance; } ```

    第二行代码就是刚刚所说的retain操作。因此,我们虽然复制了Person对象的指针,但是其内部的属性,依然和原来对象的相同。

  3. 自定义拷贝方法:

    我们当然可以自己定义一个拷贝方法,在复制Person对象的同时,把其中的object属性也复制,而不是仅仅retain

第二三种复制方法的区别如图所示:

浅拷贝与深拷贝

标为红色的是两种拷贝方式的不同之处。对于左边这种,只拷贝指针本身的拷贝方法,我们称为浅拷贝。对于右边那种,不仅拷贝指针自身,还拷贝指针中所有元素的拷贝方法,我们称为深拷贝。

没有明确的限制copy和自定义的拷贝方法要如何实现。也就是说copy方法可以用来进行深拷贝,我们也可以自定义浅拷贝的方法。这完全取决于我们自己如何实现copy方法和自定义的拷贝方法。在OC中,对于自定义的类来说,浅拷贝与深拷贝只是一种概念,并没有明确的标注哪种方法就是浅拷贝。

注意

“深拷贝将被拷贝的对象完全复制”这种说法不完全正确,比如上图中我们看到data的地址永远不会拷贝。这是因为,深拷贝只负责了对象的拷贝,以及对象中所有属性的拷贝。正是因为拷贝了属性,将p深拷贝后得到的p'object指针地址和pobject指针地址不同。

但是至于data会不会被拷贝,这取决于MyObject类如何设计,如果MyObjectcopy方法只是浅拷贝,就会形成如上图所示的情况。如果copy方法也是深拷贝,那么data的地址也会不同。

容器对象拷贝

在OC中,所有Foundation中的容器类,分为可变容器和不可变容器,它们的拷贝都是浅拷贝。这也就是为什么建议自定义的对象实现浅拷贝,如果有需要才自定义深拷贝方法。因为这样一来,所有的方法调用就都可以统一,不至于造成误解。

如果我们把数组想象成一个三层的数据结构,第一层是数组自己的指针,第二层是存放在数组中的指针,第三层(如果第二层是指针)则是这些指针指向的对象。那么在复制数组时,复制的是前两层,第三层的对象不会被复制。如果把前两层看做指针,第三层看做对象,那么数组的拷贝,无论是copy还是mutablecopy都是浅拷贝。当然,也有人把这个称为“单层深拷贝”,这些概念性的定义都不重要,重要的是知道数组拷贝时的原理。

这一点很好理解。首先,指针所指向的对象,也许很大,深拷贝可能占用过多的内存和时间。其次,容器不知道自己存储的对象是否实现了NScopying协议。如果容器的拷贝默认是深拷贝,同时你在数组中存放了Person类的对象,而Person类根本没有实现NScopying协议,后果是复制容器会导致程序崩溃。这是任何语言开发者都不希望看到的,所以设身处地想一下,如果是你来设计OC,也不会让数组深拷贝吧。

观察下面这段代码,思考一下为什么a1[0] = @0没有影响a2

NSMutableArray *a1 = [[NSMutableArray alloc] initWithObjects:@1,@2,nil];  
NSMutableArray *a2 = [a1 mutablecopy];  
a1[0] = @0;  
NSLog(@"a2 = %@",a2);

/* 2015-12-23 23:11:53.711 copy[1795:220469] a2 = ( 0,2 ) */

可变性

容器对象分为可变容器与不可变容器,NSDataNSArrayNsstring等都是不可变容器,以NSMutable开头的则是它们的可变版本。下面统一用NSArrayNSMutableArray举例说明。

因为NSMutableArrayNSArray的子类,所以NSArray对象不能强制转换成NSMutableArray,否则在调用addobject方法时会崩溃。反之,NSMutableArray可以转换成它的父类NSArray,这么做会导致它失去可变性。

容器拷贝的难点在于可变性的变化。容器有两种方法:copymutablecopy,再次强调这两者都是浅拷贝。它们的区别在于,返回值是否是可变的。前者返回不可变容器,后者返回可变容器。

这也就是说,返回值的可变性与被拷贝对象的可变性无关,仅取决于调用了何种拷贝方法。比如:

NSMutableArray *mutableArray = [[NSMutableArray *array = [mutableArray copy];  
[array addobject:@3];

尽管我们调用了mutableArray的拷贝方法,返回值也声明为NSMutableArray,但是调用addobject方法时依然会导致运行时错误。这是由错误的调用了copy方法导致的。

调用一个对象的浅拷贝方法会得到一个新的对象(地址不同),但是容器类中有一个特例:

NSArray *array1 = @[@2];  
NSArray *array2 = [array1 copy];  
// array1和array2指向的数组对象地址相同

这是因为既然array1array2都不能再修改,那么两者共用同一块内存也是无所谓的,所以OC做了这样的优化。

字符串拷贝

字符串也可以被当做容器来理解。它有NsstringNSMutableString两个版本。

于是为什么字符串属性要定义成@property(copy,nonatomic)就很好理解了。它主要用于处理这种情况:

NSMutableString *string = @"hello";  
self.propertyString = string;  
[string appendString:@" world"];

如果属性定义成strong,那么在第二步执行了retain操作,第三步对string的修改就会影响到原来的属性。现在我们把属性定义为copy,那么第二步操作其实是得到了一个新的,不可变字符串。这符合我们的预期目的。

Swift拷贝

结构体拷贝

数组、字典等容器在Swift中被定义成了结构体,它们的拷贝规则和OC完全不同:

var array1 = [3]  
var array2 = array1

array1[0] = 0  
print(array2) // 输出结果:[1,2,3] 

可以看到,即使是最简单的等号赋值,也会浅拷贝原来的值。这是由Swift中结构体的值语义决定的。之所以说是浅拷贝而不是深拷贝,理由参见前文解释OC中容器的浅拷贝,尤其是第二点理由,不管是对于OC还是Swift来说都是通用的。

对象拷贝

和OC中指针赋值类似,对象的直接赋值操作与拷贝无关:

class Person {  
    var name: String;
    init(name:String) {
        self.name = name
    }
}

let person1 = Person(name: "zxy")  
let person2 = person1  
person1.name = "new name"

print(person2.name) //结果是“new name” 

如果要拷贝对象,有两种方法。首先,最自然想到的是实现NScopying协议,注意只有NSObject类的对象才能实现这个协议:

Person : NSObject,NScopying {  
    self.name = name
    }

    func copyWithZone(zone: NSZone) -> AnyObject {
        return Person(name: self.name)
    }
}

但这样做最大的问题在于,你必须继承自NSObject,这就又回到了OC的那一套。如果我们希望定义纯粹的Swift类,完全可以自己定义并实现拷贝方法。

“面向接口编程”的原则告诉我们,我们应该让Person实现某个接口,而不是继承自某个子类:

protocol copyable {  
    copy() -> copyable } class copyable {  
    copyable {
        self.name)
    }
}

let person2 = person1.copy() as! Person

这样就完美的实现Swift-Style拷贝了。

总结

在OC中,浅拷贝通常由NScopying协议的copyWithZone方法实现,深拷贝需要自定义方法。直接赋值意味着retain而不是拷贝。

在Swift中,值类型直接用等号赋值意味着浅拷贝,引用类型的拷贝可以通过实现自定义的copyable协议或OC的NScopying协议完成。

在OC中,我们需要容器的可变性,而Swift在这一点做的要比OC好得多。它的可变性非常简单,完全通过letvar控制,这也是Swift相比于OC的一个优点吧,毕竟高级的语言应该尽可能封装底层实现。

Swift与OC中拷贝与可变性的更多相关文章

  1. html5利用canvas实现颜色容差抠图功能

    这篇文章主要介绍了html5利用canvas实现颜色容差抠图功能,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下

  2. html5使用canvas实现弹幕功能示例

    这篇文章主要介绍了html5使用canvas实现弹幕功能示例的相关资料,需要的朋友可以参考下

  3. 前端实现弹幕效果的方法总结(包含css3和canvas的实现方式)

    这篇文章主要介绍了前端实现弹幕效果的方法总结(包含css3和canvas的实现方式)的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  4. H5 canvas实现贪吃蛇小游戏

    本篇文章主要介绍了H5 canvas实现贪吃蛇小游戏,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. HTML5页面无缝闪开的问题及解决方案

    这篇文章主要介绍了HTML5页面无缝闪开方案,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. ios – 声明NSDictionary并在Swift中添加键值对?

    我一直在尝试使用类类型键和值来声明一个NSDictionary,如下所示:这里,“Category”和“SubCategory”是全局类.我知道我不能将类类型用于关键字段.但是,无论如何,我应该做到这一点.有没有办法做到这一点?如何声明专门的NSDictionary或类似的东西来做到这一点?

  7. ios – parse.com用于键,预期字符串的无效类型,但是得到了数组

    我尝试将我的数据保存到parse.com.我已经预先在parse.com上创建了一个名为’SomeClass’的类.它有一个名为’mySpecialColumn’的列,其数据类型为String.这是我尝试使用以下代码保存数据的代码:如果我运行这个我得到:错误:密钥mySpecialColumn的无效类型,预期字符串,但得到数组这就是我在parse.com上的核心外观:有谁知道我为什么会收到这个错误?

  8. ios – Swift相当于`[NSDictionary initWithObjects:forKeys:]`

    Swift的原生字典是否与[NSDictionaryinitWithObjects:forKeys:]相当?假设我有两个带键和值的数组,并希望将它们放在字典中.在Objective-C中,我这样做:当然我可以通过两个数组迭代一个计数器,使用vardict:[String:Int]并逐步添加东西.但这似乎不是一个好的解决方案.使用zip和enumerate可能是同时迭代两者的更好方法.然而,这种方法

  9. nsmutablearray – Sprite Kit iOS7 – SKNode UserData属性不存储值

    谢谢解决方法userData属性最初为零.您必须先创建一个字典并进行分配:

  10. ios – 上下文类型’NSFastEnumeration’不能与数组文字一起使用

    斯威夫特3,你会这样做吗?解决方法正如您所发现的,您不能使用as-casting将数组文字的类型指定为NSFastEnumeration.您需要找到一个符合NSFastEnumeration的正确类,在您的情况下它是NSArray.通常写这样的东西:

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部