2、#import和#include的区别是什么?#import<> 跟 #import""有什么区别?
.#import能避免头文件被重复包含的问题:
1) 一般来说,导入objective c的头文件时用#import,包含c/c++头文件时用#include。
使用include要注意重复引用的问题:
class A,class B都引用了class C,class D若引用class A与class B,就会报重复引用的错误。
2)#import 确定一个文件只能被导入一次,这使你在递归包含中不会出现问题。
所以,#import比起#include的好处就是它避免了重复引用的问题。所以在OC中我们基本用的都是import。
3)#import<> 包含iOS框架类库里的类,#import""包含项目里自定义的类。
1.用预处理指令#define声明一个常数,用以表示一年中有多少秒(忽略闰年的问题)
分析:通过这道面试题目,面试官想考察如下几个知识点:
①#define语法的基本常识(例如:不能以分号结束,括号的使用等等)
②写出你是如何计算一年中有多少秒而不是计算实际的值,会更有价值
③意识到这个表达式将使一个int数溢出,因此最好用的长整形,那么你就会为自己加分了
.#define SECOND_PER_YEAR (606024*365)UL
2.objective-C 有多重继承么?,没有的话,有什么替代方法?
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
Object-c的类没有多继承,只支持单继承,如果要实现多继承的话,可以通过类别和协议的方式来实现。OC类似于多继承,是在 用protocol委托代理来实现的;可以实现多个接口,通过实现多个接口可以完成C++的多重继承;Category是类别,一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。
3.Objective-C 有什么私有方法?私有变量呢?
objective c中既有私有方法,也有私有变量。
先说私有方法,
由于Objective-C的动态消息传递机制,OC中不存在真正意义上的私有方法。
但是如果你不在.h文件中声明,只在.m文件中实现,或在.m文件的Class Extension里声明,那么基本上和私有方法差不多。
至于私有变量是可以通过@private来声明的,例如:
@interface Sample : NSObject{
@private
NSString *tteesstt;
}
@property (nonatomic,strong) NSString *hoge;
-(void)foo;
@end则tteesstt变量是私有的。而属性hoge是默认公有。
现在Apple官方文档里是用property比较多,直接定义instance variable少。将property定义到.m的Class Extension也基本上和私有变量差不多。
简而言之,将你希望公有的放到.h文件,私有的放到.m文件。在import时只import .h文件(.m文件也是可以import的,但是我们一般不这么做)。
4.关键字const有什么含义:
const是一个修饰符,被修饰的对象或者变量是不可修改的,也就是说const可读不可改,const在谁的后面const就修饰谁,如果const在最前面,那么将const后移一位即可,二者是等效的。
const有以下这几个作用:
(1)如果我们想要阻止一个变量被改变,那么我们就可以使用const关键字来修饰它,由于被const修饰的对象或者变量是可读不可写的,因此我们在使用const的使用要对所修饰的对象或者变量进行初始化,否则以后没有机会再改变他的值。
(2)对于指针来说,const可以修饰指针本身,也可以修饰指针所指的数据,也可以修饰两者。
(3)在对一个函数进行声明的时候,可以使用const对形参进行修饰,表明它是一个输入参数,在函数的内部不可写。
(4)对于类的成员函数,如果被const修饰,表明它是一个常函数,不能修改类的成员变量。
(5)对于类的成员函数,有时候必须指定其返回值类型是const,这样使得该函数的返回值不是“左值”。我只要一听到被面试者说:"const意味着常数"(不是常数,可以是变量,只是你不能修改它),我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)
如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都是什么意思?
Const只是一个修饰符,不管怎么样a仍然是一个int型的变量
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
本质:const在谁后面谁就不可修改,const在最前面则将其后移一位即可,二者等效前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,指向的整型数是不可修改的,但指针可以,此最常见于函数的参数,当你只引用传进来指针所指向的值时应该加上const修饰符,程序中修改编译就不通过,可以减少程序的bug)。
第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。
如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 ,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
- 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
- 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
- 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
const关键字至少有下列n个作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:
const classA operator(const classA& a1,const classA& a2);
operator的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:
classA a, b, c;
(a * b) = c; // 对a*b的结果赋值
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。
5.关键字volatile有什么含义?并给出三个不同的例子
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:[状态寄存器](https://www.baidu.com/s?wd=%E7%8A%B6%E6%80%81%E5%AF%84%E5%AD%98%E5%99%A8&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1dBPHmvm19hnAcznhN9uhR10ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-bIi4WUvYETgN-TLwGUv3EnWfYn1fsnHf3))
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的[状态寄存器](https://www.baidu.com/s?wd=%E7%8A%B6%E6%80%81%E5%AF%84%E5%AD%98%E5%99%A8&tn=44039180_cpr&fenlei=mv6quAkxTZn0IZRqIHckPjm4nH00T1dBPHmvm19hnAcznhN9uhR10ZwV5Hcvrjm3rH6sPfKWUMw85HfYnjn4nH6sgvPsT6KdThsqpZwYTjCEQLGCpyw9Uz4Bmy-bIi4WUvYETgN-TLwGUv3EnWfYn1fsnHf3)。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr) {
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr) {
int a;
a = *ptr;
return a * a;
}
======
volatile 的意思是“易失的,易改变的”。这个限定词的含义是向编译器指明变量的内容可能会由于其他程序的修改而变化。通常在程序中申明了一个变量时,编译器会尽量把它存放在通用寄存器中,例如ebx。当CPU把其值放到ebx中后就不会再关心对应内存中的值。若此时其他程序(例如内核程序或一个中断)修改了内存中它的值,ebx中的值并不会随之更新。为了解决这种情况就创建了volatile限定词,让代码在引用该变量时一定要从指定位置取得其值。
关键字volatile有什么含意?并给出三个不同的例子。 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得volatile内容将会带来灾难。
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{return *ptr * *ptr;}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr) {
int a;
a = *ptr;
return a * a;
}
volatile的本意是“易变的”
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如:
static int i=0;
int main(void) {
...
while (1) {
if (i) dosomething();
}
}
/* Interrupt service routine. */
void ISR_2(void) {
i=1;
}
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此
可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。
一般说来,volatile用在如下的几个地方:
1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
2、多任务环境下各任务间共享的标志应该加volatile;
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;
另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实
现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
=============
指针类型也是一种变量,所以也是可以用volatile来修饰的.
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如
操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行
优化,从而可以提供对特殊地址的稳定访问。
使用该关键字的例子如下:
int volatile nVint;
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指
令刚刚从该处读取过数据。而且读取的数据立刻被保存。
例如:
volatile int i=10;
int a = i;
。。。//其他代码,并未明确告诉编译器,对i进行过操作
int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的
汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器发现两次从i读数据的代码之间
的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果
i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问
。
注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编
代码,测试有无volatile关键字,对程序最终代码的影响:
首先用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码:
#include <stdio.h>
void main()
{
int i=10;
int a = i;
printf( "i= %d\n ",a);
//下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf( "i= %d\n ",b);
}
然后,在调试版本模式运行程序,输出结果如下:
i = 10
i = 32
然后,在release版本模式运行程序,输出结果如下:
i = 10
i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#include <stdio.h>
void main()
{
volatile int i=10;
int a = i;
printf( "i= %d\n ",a);
__asm {
mov dword ptr [ebp-4], 20h
}
int b = i;
printf( "i= %d\n ",b);
}
分别在调试版本和release版本运行程序,输出都是:
i = 10
i = 32
这说明这个关键字发挥了它的作用!
6.对象是在什么时候被release的?
引用计数为0时。autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autoreleasepool中,当该pool被释放时,该pool中的所有Object会被调用Release。对于每一个Runloop,系统会隐式创建一个Autoreleasepool,这样所有的releasepool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autoreleasepool会被销毁,这样这个pool里的每个Object(就是autorelease的对象)会被release。那什么是一个Runloop呢?一个UI事件,Timercall,delegatecall,都会是一个新的Runloop
7.self.name = “object” 和 name = “object”的区别
self.name = "object"会调用对象的setName()方法,
name = "object"会直接把object赋值给当前对象的name 属性。
并且 self.name 这样retainCount会加1,而name就不会。
7、写一个setter方法用于完成@property(nonatomic,retain)NSString *name,
写一个setter方法用于完成@property(nonatomic,copy)NSString *name
-(void)setName:(NSString *)name{
_name = name;
}
-(void)setName:(NSString *)name{
_name = [name copy];
}
1、NAObject的load、initialize类方法的区别?
load 是类所在文件被引用就是调用
initialize 是类或子类第一个方法调使用被调用Apple的文档很清楚地说明了initialize和load的区别在于:load是只要类所在文件被引用就会被调用,而initialize是在类或者其子类的第一个方法被调用前调用。所以如果类没有被引用进项目,就不会有load调用;但即使类文件被引用进来,但是没有使用,那么initialize也不会被调用。
它们的相同点在于:方法只会被调用一次。(其实这是相对runtime来说的,后边会做进一步解释)。
文档也明确阐述了方法调用的顺序:父类(Superclass)的方法优先于子类(Subclass)的方法,类中的方法优先于类别(Category)中的方法。
不过还有很多地方是文章中没有解释详细的。所以再来看一些示例代码来明确其中应该注意的细节。
2、请简述你对strong和weak关键字的理解,以及_unsafe、_unretained与weak的区别?
strong 强指针 weak 弱指针 在ARC中可防止循环引用
copy关键字作用还是一样的,weak与_unsafe_unretained的区别在于,weak会将被释放指针赋值为nil,而_unsafe_unretained则会成为野指针。
_weak, _strong 用来修饰变量,此外还有 _unsafe_unretained, _autoreleasing 都是用来修饰变量的。
_strong 是缺省的关键词。
_weak 声明了一个可以自动 nil 化的弱引用。
_unsafe_unretained 声明一个弱应用,但是不会自动nil化,也就是说,如果所指向的内存区域被释放了,这个指针就是一个野指针了。
_autoreleasing 用来修饰一个函数的参数,这个参数会在函数返回的时候被自动释放。第一、strong关键字与retain关似,用了它,引用计数自动+1
第二、weak
声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。第三、unretained且unsafe,由于是unretained所以与weak有点类似,但是它是unsafe的。
crash 结果分析:p1执行内存被释放 unsafe_unretained声明的指针,由于book=nil已将内存释放掉了,但是string2并不知道已被释放了,所以是野指针。然后访问野指针的内存就造成crash. 所以尽量少用unsafe_unretained关键字
3、frame和bounds有什么不同?
frame: 该view在父view坐标系统中的位置和大小。(参照点是,父亲的坐标系统)
bounds:该view在本地坐标系统中的位置和大小。(参照点是,本地坐标系统,就相当于ViewB自己的坐标系统,以0,0点为起点)。
冒泡法
int main(int argc, const char * argv[])
{
int arr[] = {-10,-20,300,400,150,160};
int length = sizeof(arr) /sizeof(arr[0]);
for ( int i = 0 ; i < length -1; i++)
{
for (int j = 0; j< length -1 - i; j++)
{
if (arr[j] < arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
for ( int i = 0; i < length; i++)
{
printf("%d\n",arr[i]);
}
return 0;
}
5.请解释description方法的作用
description基本概念
1.NSLog(@"%@", objectA);这会自动调用objectA的description方法来输出ObjectA的描述信息.
2.description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
3.description方法是基类NSObject 所带的方法,因为其默认实现是返回类名和对象的内存地址, 这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法 的默认实现
description重写的方法
/对象方法:当使用NSLog输出该类的实例对象的时候调用/
-(NSString ) description {
return [NSString stringWithFormat:@"狗腿数:%d,狗眼数%d\n",_legNum,_eyeNum];
}
/类方法:当使用NSLog输出该类的类对象的时候调用*/
+(NSString *) description {
return @"+开头的description方法";
}
description陷阱
1.千万不要在description方法中同时使用%@和self,下面的写法是错误的
-(NSString *)description {
return [NSString stringWithFormat:@"%@", self];
}
2.同时使用了%@和self,代表要调用self的description方法,因此最终会导致程序陷入死循环,循 环调用description方法
3.当[NSString stringWithFormat:@“%@”, self]; 使用它时,循坏调用,导致系统会发生运行时错误。
4.当该方法使用NSLog(“%@”,self) 时候, 系统做了相关的优化,循坏调用3次后就会自动退出
6.如何使用一个工程编译出多个不同的版本应用?
如果同一个应用, 需要做一个带广告Lite版本, 一个不带广告的Pro版本. 那么问题来了, 该如何优雅的去实现呢?
一般来说有两种实现方法:
一个把当前工程拷贝然后再修改, 这样做会导致后期维护成本过高, 每次修改都要同时改两个工程, 到后期修改简直提心掉胆, 不过操作傻瓜式.
另外一个种就是在一个Project里面创建两个Target, 然后通过判断Target来修改代码, 这样都是基于同一套代码做修改, 只是部分不相同的地方通过Target来添加不同代码, 后期修改维护成本低, 推荐大家使用这种方式.不想看教程的童鞋, 点击这里可以下载我已经完成好的代码.
创建两个Target:
1.首先先选中Target一个已经存在的版本, 右键 Duplicate
2.然后在弹出来的选择框选择 Duplicate Only
3.创建完之后你的新Target应该是和我的一样, 接下来我们就要修改Target, Scheme, Info-plist, 如图这样修改:
4.修改完了之后Target, Scheme, plist的名字之后, 你需要在新的Target添加对应的plist文件, 修改CFBundleDisplayName就可以修改应用的名字了.
5.还要记得修改一下Product Name 不然你的Bundle Identifier的后缀名有copy和你的Target名字不一样, 你还需要在Bundle Setting做一下修改.
开始为两个不同的应用添加不同的AppIcon, LaunchImage
在这个选择使用Images.xcassets里面设置AppIcon和LaunchImage, 注意这里一个是AppIcon,另一个是AppIcon-2, 以后编译Target的时候他就会跟随这里的设置去拿了开机图和Icon
2.进入Images.xcassets看下图片是不是都是勾选了两个Target, 保持和我下图一样的勾选, 如果没有勾选的话, 你在编译的不同Target的时候会获取不到资源.
3.选择不同Target进行编译, 你的运行结果应该和我的截图一样, 有着不同的AppName和AppIcon,还有不同的LaunchImage,但是代码是共用, 到这里你已经成功了一半了, 接下来你肯定是想知道如何在代码里面区别不同Target, 然后给它们添加其他的特性.
在代码里面利用宏定义来区分不同的Traget
1.在Bundle Setting里面设置一下Proprecessor Macros添加一份KFREE KPRO的参数来区分到底是那个Traget. 在代码里面为需要用到这个宏去判断代码块.
2.在代码里面添加Proprecessor Macros里面宏定义, 你就会发现编译之前Xcode就会智能的选择不同代码. 这样你就共用一个项目管理两个不同版本的应用了, 大部分的代码都复用, 维护管理非常轻松.
总结:苹果每年都会推出新的技术, 都是为了减少开发成本和更好的设计, 作为开发者的开发模式应当和苹果的理念一致. 用最优雅, 简洁的方式去实现功能.
6.简述assign、retain、copy、weak和strong分别在什么情况下使用
7.@property有哪些属性关键字,其中哪些在ARC中是默认关键字?
@property中有哪些属性关键字?
@property和@synthesize是成对出现的,可以自动生成某个类成员变量的存取方法(getter和setter)。在Xcode4.5以及以后的版本中,@synthesize可以省略。
- atomic与nonatomic
atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步的问题,提供多线程安全。
nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,禁止多线程,变量保护,提高性能和效率。注:
atomic是Objc使用的一种线程保护技术,基本上来讲是防止在写未完成的时候另一个线程读取,造成的数据错误。而这种机制是非常耗费系统资源的,所以iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。而iOS开发中,普遍使用nonatomic也是基于性能这一点。
readwrite与readonly
readwrite:这个属性是默认的情况,会自动为你生成存取器。
readonly:只生成getter,不会生成setter方法。
注:
readwrite、readonly这两个属性的真正价值,不是提供成员变量的访问接口,而是控制成员变量的访问权限。strong与weak
strong:强引用,也是我们通常说的引用,其存亡直接决定了所指向对象的存亡,当强引用指向了某个对象,那便拥有了这个对象。如果不存在指向一个对象的引用,并且此对象不再使用,则对象就会被从内存中释放掉。默认所有实例变量和局部变量都是strong指针。
weak:弱引用,不决定对象的存亡,只是单纯的引用了某个对象但是并不拥有该对象。即使一个对象被持有无数个弱引用,只要没有强引用指向它,那么还是会被清除。
注:
strong与retain功能相似;weak与assign相似,只是当对象消失后weak会自动把指针置为nil,避免了野指针的产生,因而weak属性就不需要在dealloc中置nil了。assign、copy、retain
assgin:默认类型,setter方法直接赋值,不进行任何的retain操作,不改变引用计数。一般用来处理基本数据类型。
retain:释放旧的对象(release),将旧对象的值赋给新对象,再令新对象引用计数为1。可理解为指针拷贝。
copy:与retain的流程一样,先对旧的值release,再copy出新的对象,引用计数为1,为了减少对上下文的依赖而引入的机制。可以理解为内容的拷贝,也就意味着内容被copy后,内存中会有两个存储空间存储同样的内容。
注:
使用assign: 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)
使用copy: 对NSString
使用retain: 对其他NSObject和其子类@synthesize和@dynamic分别有什么作用?
简单来讲,通过@synthesize指令告诉编译器在编译期间产生getter和setter方法。如果自定义getter和setter方法则会覆盖编译器帮我们生成的方法。
@dynamic指令告诉编译器在编译期间不自动生成getter和setter方法,避免编译期间产生警告。然后由自己实现存取方法或存取方法在运行时动态创建绑定。其主要作用就是用在NSManageObject对象的属性声明中,由于此类对象的属性一般是从Core Data的属性中生成的,Core Data框架会在程序运行的时候为此类属性生成getter和setter方法。
ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
atomic,readwrite,strong(对象),assgin(基本数据类型)。用@property声明的NSString、NSArray、NSDictionary经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?
这个问题无非就是考察你对copy、strong这个两个修饰符的理解。简单来讲,strong是强引用,仍旧指向同一个内存地址;copy是内容拷贝,会另外开辟一个内存空间来存储被拷贝的内容,指针指向了一个不同的内存地址。注意,copy返回的是一个不可变对象。如果用strong修饰可变对象,那么这个对象就会有可能在不经意间被修改,有时候这并不是我们的想要看到的,而用copy便不会有这种意外发生了。
@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量么?
@synthesize表示由编译器来自动实现属性的getter/setter方法,不需要你自己再手动去实现。默认情况下,不需要指定实例变量的名称,编译器会自动生成一个属性名前加“_”的实例变量。当然也可以在实现代码里通过@synthesize语法来指定实例变量的名字。比如@synthesize foo = oof(oof为你想要定义的名称)。
如果property名为foo并且存在一个名为_foo的实例变量,编译器便不会为我们自动合成新变量了。下面有一段代码大家可以试一下帮助更好的理解。
@interface ViewController : UIViewController
@property (copy, nonatomic) NSString *testA;
@property (copy, nonatomic) NSString *testB;
@end
@interface ViewController ()
{
NSString *_testA;
NSString *_testB;
}
@end
@implementation ViewController
@synthesize testB = testBBBBB;
- (void)viewDidLoad {
[super viewDidLoad];
self.testA = @"1111";
self.testB = @"1111";
//输出结果为:self.testA = 1111,_testA = 1111,self.testB = 1111,testBBBBB = 1111,_testB = (null)
NSLog(@"self.testA = %@,_testA = %@,self.testB = %@,testBBBBB = %@,_testB = %@",self.testA,_testA,self.testB,testBBBBB,_testB);
_testA = @"2222222";
_testB = @"2222222";
//输出结果为:self.testA = 2222222,_testA = 2222222,self.testB = 1111,_testB = 2222222,testBBBBB = 1111
NSLog(@"self.testA = %@,_testA = %@,self.testB = %@,_testB = %@,testBBBBB = %@",self.testA,_testA,self.testB,_testB,testBBBBB);
testBBBBB = @"333333";
//输出结果:self.testB = 333333,testBBBBB = 333333,_testB =2222222
NSLog(@"self.testB = %@,testBBBBB = %@,_testB =%@",self.testB,testBBBBB,_testB);
}
9.在C/C++代码中怎么调用Objective-C函数
苹果的Objective-C编译器允许用户在同一个源文件里自由地混合使用C++和Objective-C,混编后的语言叫Objective-C++。有了它,你就可以在Objective-C应用程序中使用已有的C++类库。
Objective-C和C++混编的要点
在Objective-C++中,可以用C++代码调用方法也可以从Objective-C调用方法。在这两种语言里对象都是指针,可以在任何地方使用。例如,C++类可以使用Objective-C对象的指针作为数据成员,Objective-C类也可以有C++对象指针做实例变量。下例说明了这一点。
注意:Xcode需要源文件以".mm"为扩展名,这样才能启动编译器的Objective-C++扩展。
复制代码
/* [Hello.mm](http://Hello.mm)
* Compile with: g++ -x objective-c++ -framework Foundation [Hello.mm](http://Hello.mm) -o hello
*/
#import <Foundation/Foundation.h>
class Hello {
private:
id greeting_text; // holds an NSString
public:
Hello() {
greeting_text = @"Hello, world!";
}
Hello(const char* initial_greeting_text) {
greeting_text = [[NSString alloc] initWithUTF8String:initial_greeting_text];
}
void say_hello() {
printf("%s/n", [greeting_text UTF8String]);
}
};
@interface Greeting : NSObject {
@private
Hello *hello;
}
- (id)init;
- (void)dealloc;
- (void)sayGreeting;
- (void)sayGreeting:(Hello*)greeting;
@end
@implementation Greeting
- (id)init {
if (self = [super init]) {
hello = new Hello();
}
return self;
}
- (void)dealloc {
delete hello;
[super dealloc];
}
- (void)sayGreeting {
hello->say_hello();
}
- (void)sayGreeting:(Hello*)greeting {
greeting->say_hello();
}
@end
int main() {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Greeting *greeting = [[Greeting alloc] init];
[greeting sayGreeting]; // > Hello, world!
Hello *hello = new Hello("Bonjour, monde!");
[greeting sayGreeting:hello]; // > Bonjour, monde!
delete hello;
[greeting release];
[pool release];
return 0;
}
正如你可以在OC接口中声明C结构一样,你也可以在OC接口中声明C++类。跟C结构一样,OC接口中定义的C++类是全局范围的,不是OC类的内嵌类(这与标准C(尽管不是C++)提升嵌套结构定义为文件范围是一致的)。
为了允许你基于语言变种条件化地编写代码,OC++编译器定义了__cplusplus和__OBJC__预处理器常量,分别指定C++和OC。 如前所述,OC++不允许C++类继承自OC对象,也不允许OC类继承自C++对象。
复制代码
class Base { /* ... */ };
@interface ObjCClass: Base ... @end // ERROR!
class Derived: public ObjCClass ... // ERROR!
与OC不同的是,C++对象是静态类型的,有运行时多态是特殊情况。两种语言的对象模型因此不能直接兼容。更根本的,OC和C++对象在内存中的布局是互不相容的,也就是说,一般不可能创建一个对象实例从两种语言的角度来看都是有效的。因此,两种类型层次结构不能被混合。
你可以在OC类内部声明C++类,编译器把这些类当作已声明在全局名称空间来对待。就像下面:
复制代码
@interface Foo {
class Bar { ... } // OK
}
@end
Bar *barPtr; // OK
OC允许C结构作为实例变量,不管它是否声明在OC声明内部。
复制代码
@interface Foo {
struct CStruct { ... };
struct CStruct bigIvar; // OK
} …
@end
4.请解释NSError和NSException之间的关系
NSError 与NSException
异常和错误是完全不同的两个东西。如果抛出个异常但是你没捕获,那就会crash,但是如果是返回个错误,对外部来说可以随意怎么办,只是告诉你这个调用有错误。
NSError是专为非致命,可恢复错误。的问题是为了被一个NSError通常用户错误(或者是错误,可以向用户提供),通常可以从(因此-presentError:和NSErrorRecoveryAttempting)中恢复过来,和通常预期的或可预见的错误(如试图打开一个文件,你不可以访问,或者试图在不兼容的字符串编码)之间进行转换。NSException是专为可能致命的程序员的错误。这些错误是为了表示潜在缺陷在您的应用程序中,您没有正确检查先决条件来执行一些操作(如试图访问数组索引超出范围,或试图改变一个不可变对象)。介绍了异常编程指南解释了这一点
28.类别和继承有哪些区别
在iOS中,给类添加方法有两种方式:继承和类别。
- 继承
继承时面向对象的三大特性之一,子类会继承父类的所有方法和属性。2 类别
类别(其实我更喜欢翻译为分类)是oc的特性,可以在不改变原类及继承父类的前提下,实现对类方法的扩展。
既然继承和类别都可以扩展类方法,那类别存在的理由是什么呢?
不改变原类的条件下,向类添加方法,这点继承做不到。
可以把相关的方法分组到多个单独的文件中,便于管理(“分类”)。
这样是很方便的,所以可以很多项目都可以看大分类的使用。
当然,分类也有他的局限:如果扩展方法名和原类名相同,则会覆盖掉原类的方法,这是要注意的,还一个是分类无法扩展类的属性。
17.谈一下关键字static的作用?关键字const呢?
static作用:
1.作用于变量:
用static声明局部变量时,则改变变量的存储方式(生命期),使变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元。这样,使得该局部变量有记忆功能,可以记忆上次的数据,不过由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。用static声明外部变量-------外部变量指在所有代码块{}之外定义的变量,它缺省为静态变量,编译时分配内存,程序结束时释放内存单元。同时 其作用域很广,整个文件都有效甚至别的文件也能引用它。为了限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用static 关键字对其作出声明。
总结:用static声明局部变量,使其变为静态存储方式(静态数据区),作用域不变;用static声明外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。
2.作用于函数:
使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。
const作用: “只读(readonly)”
1.定义常量
(1)const
修饰变量,它的含义是:const修饰的变量值是不可变的,readonly。
(2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义
extend const int ValueName = value;
19.break、continue、return的区别是什么?
1.break直接跳出当前的循环,从当前循环外面开始执行,忽略循环体中任何其他语句和循环条件测试。他只能跳出一层循环,如果你的循环是嵌套循环,那么你需要按照你嵌套的层次,逐步使用break来跳出.
- (void) test {
for(int i =0;i<10;i++){
nslog(@"%@",i);
break;
}
}
这个时候使用break,会跳出当层的for循环,返回到test函数的调用者位置,有且也只能跳出一层,嵌套使用时候,需要层层跳出。
2.continue也是终止当前的循环过程,但他并不跳出循环,而是继续往下判断循环条件执行语句.他只能结束循环中的一次过程,但不能终止循环继续进行.
- (void) test {
for(int i =0;i<10;i++){
if(i==2){
nslog(@"我是测试字符串");
continue;
}
nslog(@"%@",i);
}
}
这个时候使用continue,并不会跳出整个的for循环,而是中断循环的一次,那么那个i就不会被打印,但是会在i>2时候打印,继续完成整个for循环。
3.return 语句可被用来使 正在执行分支程序返回到调用它方法。
- (void) test {
for(int i =0;i<10;i++){
nslog(@"%@",i);
break;
}
}
这个时候使用break,会直接跳出整个方法,返回到函数调用者的地方,跟break有点类似,但是returan权限更高。
20.int * p中*p和p分别代表什么?
*p指针的内容
p指针的值
&p变量p的地址
21.讲一下OC和C有哪些区别?
1.OC语言本质
OC不是一门全新的语言,C语言的基础上增加了一层最小的面向对象语法.
OC发明者:1986年,BradCox(布莱德·考克斯)在第一个纯面向对象语言Smalltalk基础上写成了Objective-C语言。2.C与OC在数据类型上的差异
- C语言和OC语言数据类型上的变化:
- OC新增了一些数据类型:
基本数据类型: 布尔类型(BOOL),只有两个取值真和假
指针类型:类(Class); id类型指针,NSObject *OC中的对象类型; 动态对象类型,万能指针
Block类型:Block类型,代码块数据类型
特殊类型:SEL,nil,选择器数据类型
注:动态类型
动态类型,说简单点就是id类型.动态类型和静态类型相对的.像内置的明确的基本数据类型都属于静态类型(int,NSString等).静态类型在编译的时候就能被识别出来.所以若是程序发生了类型不对应,编译器就会发出警告. 而动态类型就编译器编译的时候是不能被识别的,要等到运行时(run time),即程序运行时才会根据语境来识别.所以这里面就有两个概念要分清:编译时和运行时.类概念:类是用来描述对象的,它是一系列方法和属性的集合.
Objective-C的类声明和实现包括两个部分:接口部分和实现部分.
3.流程控制语句对比
C语言中使用的流程控制语句OC中都可以应用
OC中多了增强for循环,又称for-in循环
4.函数与方法对比
函数在文件中是平行的,没有所属权问题,所以可以出现在任何地方(不能嵌套定义!)
方法有其所属,对象方法/类方法.
5.面向对象新增特性:继承/封装/多态
6.面向对象新增语法特性
属性生成器---编译器特性(@property,@synthesize)
分类(category)---便于给原类增加方法,及重写方法
协议(protocol)----可以让不同类之间共享方法
Foundation框架
7.新增异常处理
处理错误信息(比如程序调用一个不存在的文件,如果没有异常处理,会出现闪退的状况!)
格式:@try...@catch ...@finally
实例:
//创建对象car
Car *car = [Car new];
@try {
//调用一个没有实现的方法
[car test];
}@catch (NSException *exception) {
NSLog(@"%@",exception.name);
}@finally {
NSLog(@"继续执行!\n");
}
8.#import和#include区别
.#import已默认有条件编译功能
9.NSLog 和printf区别
NSLog是Foundation框架供的Objective-C日志输出函数,与标准C中的printf函数类似,并可以格式化输出
NSLog输出的是对象(NSString类型的对象),printf输出的是char *类型的字符串
NSLog输出会自动换行,并且有时间戳和项目名称.
10.如何设计一个类
定义类其实就是定义类中的成员(成员变量/成员方法),还有不要忘了类名
11.类中的消息机制:使用对象调用方法就是OC中的消息机制
消息机制与SEL:想明白消息机制必须先知道动态绑定(dynamic binding).消息机制类似函数调用(编译时,将函数签名告诉编译器就OK),所谓某对象调用某个方法,其实就是给对象发送一条消息.这时,动态绑定的特性就来了,OC可以先跳过编译,到run time才动态添加方法调用,在运行时才决定要调什么方法,需要传什么参数进去. 这就是动态绑定,要实现run time 时调用某个方法,就必须用SEL变量(有点类似函数指针)绑定一个方法. 最终通过调用该保存有某方法ID的SEL变量,实现方法的调用. 这是不是意味着@selector()会给某方法生成一个ID??
12.类方法的优势和弊端
优势:类名调用类方法时不需要创建对象,节省创建对象需要的时间/空间.
劣势:由于不创建对象,也就意味着不能调用成员变量.
可见,类方法一般用于编写工具类!
类方法和对象方法可以同名,这和函数很不同!!
13.对象的存储细节
类创建对象,每个对象在内存中都占据一定的存储空间,每个对象都有一份属于自己的单独的成员变量,所有的对象公用类的成员方法,方法在整个内存中只有一份,类本身在内存中占据一份存储空间,类的方法存储于此。这么来看,方法和函数一样也是占据空间的!--->是的,占空间,在类对象内!
isa指针和消息机制
每一个对象都包含一个isa指针.这个指针指向当前对象所属的类。
[p eat];表示给p所指向的对象发送一条eat消息,调用对象的eat方法,此时对象会顺着内部的isa指针找到存 储于类中的方法,执行。可见,@selector(method)将方法包装成一个SEL类型的ID,实质就是整型. 而p对象通过其内的isa指针发送该消息给类对象,调用保存在其内的对象方法.
14.方法和函数
函数属于整个文件,可以写在文件中的任何位置,包括@implementation...@end中,但写在 @interface...@end会无法识别,函数的声明可以再main函数内部也可以在main函数外部。
方法归类\对象所有.
==============================
C与OC在数据类型上的差异
- 从编写、编译、链接的流程.
1). 创建1个.m的源文件.
2). 在这个文件中写上符合OC语法规范的源代码.
3). 编译.
a. 预编译: 执行预处理代码.
b. 检查语法.
c. 生成目标文件. .o的目标文件. object
4). 链接.
a. 添加启动代码.
b. 链接函数.
如果代码中使用到了框架中的功能.那么在链接的时候,就必须要告诉编译器去哪个框架中找这些函数.cc xx.o -framework 框架名称
和C程序各个阶段的文件后缀名对比.
源文件 目标文件 可执行文件
C语言 .c .o .out
OC语言 .m .o .out在Xcode中点击运行按钮.
所有的事情,Xcode自动完成.OC支持C语言中的所有的数据类型.
1). 基本数据类型
int
double
float
char
short
long
long long
unsinged
singed
2). 构造类型
数组.
结构体
枚举.
3). 指针.
4). typedef定义类型.
5). void空类型.OC在C的数据类型的基础之上,还新增了1些数据类型.
1). BOOL类型
这个类型的变量中可以存储YES或者NO 中的任意的1个数据.
一般情况下.BOOL类型的变量用来保存1个条件表达式的结果.
如果条件成立,那么结果就是YES 否则结果就是NO
在OC中,使用BOOL类型的变量来保存1个条件表达式的结果. 条件成立就是YES 否则就是NO
BOOL类型的本质:
typedef signed char BOOL;
实际上是1个有符号的char变量.
YES和NO的本质
.#define YES ((BOOL)1)
.#define NO ((BOOL)0)
其实YES就是1 NO就是0.2). Boolean类型
这个类型的变量只能存储true或者false这两个数据.
代表1个条件表达式的结果.
一般情况下.Boolean类型的变量用来保存1个条件表达式的结果.
如果条件成立,那么结果就是true 否则结果就是false.
Boolean的本质
typedef unsigned char Boolean;
是1个无符号的char变量.
true和false的本质.
.#define true 1
.#define false 0
一般都是使用BOOL类型的.3). class类型
NSString 类.4). nil
与NULL差不多.5). SEL
这个类型的变量是来保存方法的.6). block
这个类型的变量是用来保存1段代码的.
OC的运算符: OC支持C的所有的运算符.
1). 赋值运算符.
2). 算术运算符
3). 复合赋值运算符
4). 自增与自减运算符
5). 逗号运算符
6). 关系运算符 比较运算符
7). 逻辑运算符
8). 三元运算符
9). & 取地址运算符.
10). * 指针访问运算符.
11). 位运算符.
.........OC的控制语句: OC支持C的所有的控制语句.
C语言的代码完全可以写在OC中,并且效果一致.
1). IF
2). switch-case
3). while do-while for
4). 数组.
5). 指针.
6). 结构体 枚举
.......OC的关键字
1). OC支持C语言的所有的关键字. 并且效果是一样的.
2). OC在C的基础之上还新增了一些关键字.OC中新增的关键字绝大部分都是以@开头.
@interface
@implementation
@public
@protocol
......OC相对于C做了两件事情.
1). 将C语言中复杂的 繁琐的语法封装的更为简单.
2). 增加了面向对象的语法.
15.谈一下面向对象的几个主要特征
封装,继承,多态