include
include <> :用于对系统文件的引用,编译器会在系统文件目录下去查找该文件。
#include "xx.h":用于对用户自定义的文件的引用,编译器首先会去用户目录下查找,然后去安装目录,最后去系统目录查找。
注:使用include要注意重复引用的问题:
class A,class B都引用了class C,class D若引用class A与class B,就会报重复引用的错误。
功能与include基本相同,不过它避免了重复引用的问题。所以在OC中我们基本用的都是import。
@class
@class就是告诉编译器有这个类存在,但是类是如何实现的不用告诉编译器.若.m文件用到了这个类,还是要在.m文件汇总import这个类的。
既然这样,为什么不直接在头文件中import呢,举个例子:
class A引用了class B,class B引用了class C…. , class A,B,C…的头文件又import了很多文件,那么 import了A的话,编译器就需要编译大量的文件,编译时间就会增加。
难道头文件中都是用@class吗?当然不是,有时也是需要#import的,那么什么时候该用什么呢?
(1)一般如果有继承关系的用#import,如B是A的子类那么在B中声明A时用#import;
(2) 另外就是如果有循环依赖关系,如:A->B,B->A这样相互依赖时,如果在两个文件的头文件中用#import分别声明对方,那么就会出现头文件循环利用的错误,这时在头文件中用@class声明就不会出错;
(3)还有就是自定义代理的时候,如果在头文件中想声明代理的话如@interface SecondViewController:UIViewController时应用#import不然的话会出错误,注意XXXXDelegate是自定义的。
区别:
当我们在代码中使用两次#include的时候会报错:因为#include相当于拷贝头文件中的声明内容,所以会报重复定义的错误
但是使用两次#import的话,不会报错,所以他可以解决重复导入的问题,他会做一次判断,如果已经导入一次就不导入了
二、#import与@class的区别
1.import会包含这个类的所有信息,包括实体变量和方法,而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑,后面会再告诉你。
2.在头文件中, 一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。
3.在编译效率方面考虑,如果你有100个头文件都#import了同一个头文件,或者这些文件是依次引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而是用@class则不会。
4.如果有循环依赖关系,如:A–>B, B–>A这样的相互依赖关系,如果使用#import来相互包含,那么就会出现编译错误,如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现。
所以,一般来说,@class是放在interface中的,只是为了在interface中引用这个类,把这个类作为一个类型来用的。 在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要import在@class中声明的类进来.
举个例子说明一下。
在ClassA.h中
如果
只是 ClassB 那就没有include ClassB.h。仅需要在需要用到ClassB的.m文件中 #import ClassB.h
那么什么时候可以用呢?
如果ClassA.h中仅需要声明一个ClassB的指针,那么就可以在ClassA.h中声明
@ClassB
…
ClassB *pointer;
假设,有两个类:ClassA和ClassB,两个之间相互使用到,即构成了circular dependency(循环依赖)。如果在头文件里面只用#import把对方的头文件包含进来(构成circular inclusions,循环包含),则编译器会报错:
Expected specifier-qualifier-list before ‘ClassA’
或者
Expected specifier-qualifier-list before ‘ClassB’
为了避免循环包含,在ClassA.h文件里面用@class classB把classB包含进来,同样,在ClassB.h文件里面用@class ClassA把ClassA包含进来。@class指令只是告诉编译器,这是个类,保留个空间来存放指针就可以了。
接下来,很可能在ClassA.m和ClassB.m中会有访问包含进来对象的成员的情况,这时必须让编译器知道更多信息,比如那个类有些什么方法可以调用,就必须用#import,再次把用到的类包含进来,告诉编译器所需要的额外信息。
否则,编译器会警告:
warning: receiver ‘ClassA’ is a forward class and corresponding @interface may not exist
还有另一种情况,使用有Categories的类,要在.h头文件里用#import把Categories包含进来。
总之,使用原则是:
头文件里面只#import超类 消息文件里面#import需要发消息过去的类 其他地方就用@class转向声明
import会包含这个类的所有信息,包括实体变量和方法(.h文件中),而@class只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,后面会再告诉你。
在头文件中, 一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。
一.通俗的说:
copy, mutableCopy
@interface A {
B *b;
}
浅拷贝只是拷贝对象本身,不会对里面的子对象进一步拷贝
深拷贝会对子对象以及子对象的子对象进一步拷贝
二.深入理解:
浅拷贝并不复制数据,只复制指向数据的指针,因此是多个指针指向同一份数据。 深拷贝会复制原始数据,每个指针指向一份独立的数据。通过下面的代码, 可以清楚地看出它们的区别:
struct Test{
char ptr;
};
void shallow_copy(Test &src, Test &dest){
dest.ptr = src.ptr;
}
void deep_copy(Test &src, Test &dest){
dest.ptr = (char)malloc(strlen(src.ptr) + 1);
memcpy(dest.ptr, src.ptr);
}
浅拷贝在构造和删除对象时容易产生问题,因此使用时要十分小心。如无必要, 尽量不用浅拷贝。当我们要传递复杂结构的信息,而又不想产生另一份数据时, 可以使用浅拷贝,比如引用传参。浅拷贝特别需要注意的就是析构时的问题, 当多个指针指向同一份内存时,删除这些指针将导致多次释放同一内存而出错。
实际情况下是很少使用浅拷贝的,而智能指针是浅拷贝概念的增强。 比如智能指针可以维护一个引用计数来表明指向某块内存的指针数量, 只有当引用计数减至0时,才真正释放内存。
大部分时候,我们用的是深拷贝,特别是当拷贝的结构不大的时候。
1)在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性
2)自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong。在下文也有论述:《IBOutlet连出来的视图属性为什么可以被设置成weak?》
二.不同点:
1)weak 此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似, 然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。 而 assign 的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或 NSlnteger 等)的简单赋值操作。
2)assigin 可以用非OC对象,而weak必须用于OC对象
@property 的本质是什么?
@property = ivar + getter + setter;
下面解释下:
“属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)。
“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为Objective-C 2.0的一部分。 而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。 正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:
编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。 所以你也可以这么说:
@property = getter + setter;
例如下面这个类:
@interface Person : NSObject
@property NSString firstName;
@property NSString lastName;
@end
上述代码写出来的类与下面这种写法等效:
@interface Person : NSObject
(NSString )firstName;
(void)setFirstName:(NSString )firstName;
(NSString )lastName;
(void)setLastName:(NSString )lastName; @end ivar、getter、setter 是如何生成并添加到这个类中的?
“自动合成”( autosynthesis)
完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”( autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为 _firstName与_lastName。也可以在类的实现代码里通过 @synthesize语法来指定实例变量的名字.
@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = myLastName;
@end
我为了搞清属性是怎么实现的,曾经反编译过相关的代码,大致生成了五个东西:
1)OBJCIVAR$类名$属性名称 :该属性的“偏移量” (offset),这个偏移量是“硬编码” (hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
2)setter与getter方法对应的实现函数
3)ivar_list :成员变量列表
4)method_list :方法列表
5)prop_list :属性列表
也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter与getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转.
补充:
Objective-C运行时定义了几种重要的类型。
Class:定义Objective-C类
Ivar:定义对象的实例变量,包括类型和名字。
Protocol:定义正式协议。
objc_property_t:定义属性。叫这个名字可能是为了防止和Objective-C 1.0中的用户类型冲突,那时候还没有属性。
Method:定义对象方法或类方法。这个类型提供了方法的名字(就是选择器)、参数数量和类型,以及返回值(这些信息合起来称为方法的签名),还有一个指向代码的函数指针(也就是方法的实现)。
SEL:定义选择器。选择器是方法名的唯一标识符。
IMP:定义方法实现。这只是一个指向某个函数的指针,该函数接受一个对象、一个选择器和一个可变长参数列表(varargs),返回一个对象
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i =0;i<10;i++){
dispatch_async(queue,^{
[self longtimeOperation:i];
});
}