一、category 和 extension 的区别?(分类和扩展的区别)
1,分类有名字,类扩展没有名字,是一种特殊的分类。
2,分类只能增加方法,增加的属性仅仅是声明,并没有真正的实现,类扩展可以扩展属性、成员变量和方法。
二、define 和 const 的区别?
1,define 在预处理阶段进行替换,const 常量在编译阶段使用。
2,define 不做类型检查,只是进行替换,const 常量有数据类型,会执行类型检查。
3,define 不能调试,const 常量可以调试。
4,define 定义的常量在替换后运行过程中会不断的占用内存,而const 定义的常量储存在数据段只会copy 一份,节省内存,效率更高。
5,define 可以定义一些简单的饿函数,const 不可以。
三、block 和 weak 修饰符的区别?
1,_block 不管在 ARC 还是 MRC 模式下都能使用,可以修饰对象也可以修饰基本数据类型。
2,_weak 只能在 ARC 模式下使用,只能修饰对象,不能修饰基本数据类型。
3,_block 修饰的对象可以在block 中重新赋值,_weak 修饰的饿对象不可以。
四、static 关键字的作用?
1,static 在函数或者方法中修饰的变量的作用域为当前所在的函数或方法中,这个变量的内存只被分配一次,所以他的值在下一次使用时还是上一次的值。
2,static 在模块内修饰的全局变量可以被模块内的所有函数访问,但不能被模块外的其他函数访问。
3,static 在模块内修饰的函数可以被模块内的其他函数调用,这个函数的使用范围被限制在声明他的模块内。
4,static 在类中修饰的成员变量属于整个类所拥有,对类的所有对象只有一份copy。
5,static 在类中修饰的成员函数属于整个类所拥有,这个函数不接受this 指针,因而只能访问类的static 成员变量。
五、堆 和 栈 的区别?
1,从管理方式来讲:
① 对于栈,是由编译器自动管理,不需要手动控制。
② 对于堆,释放工作是由开发者控制,容易产生内存泄漏(memory leak)。
2,从申请的大小方面来讲:
① 栈的空间比较小。
② 堆得空间比较大。
3,从数据存储方面讲:
① 栈中存储的一般都是基本类型,和对象的地址。
② 堆中存储的一般都是对象本身,block 的 copy 等。
六、Objective-C 使用什么机制管理对象内存?
1,MRC 手动引用计数。
2,ARC 自动引用计数。
3,通过retainCount 的机制来决定对象是否需要释放。每次runloop 的时候,都会检查对象的retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,就可以释放了。
七、ARC 通过什么方式帮助开发者管理内存?
1,通过编译器在编译的时候,插入类似内存管理的代码。有个自动释放池。
八、ARC是为了解决什么问题产生的?
1,ARC,就是automatic reference counting自动引用计数。
2,MRC的缺点:
①在MRC时代当我们要释放一个堆内存时,首先要确定指向这个堆空间的指针都被release了。
②释放指针指向的堆空间,首先要确定哪些指针指向同一个堆,这些指针只能释放一次,MRC下是谁创建,谁释放,避免重复释放。
③模块化操作时,对象可能被多个模块创建和使用,不能确定最后由谁去释放。
④多线程操作时,不确定哪个线程最后使用完毕。
九、ARC 模式下还会存在内存泄漏的情况吗?
1,会,如果有两个对象循环引用就会造成内存泄漏。
2,Objective-C 的对象与 CoreFoundation 对象进行桥接的时候如果管理不当也会造成内存泄漏。
3,CoreFoundation 中的对象不受 ARC 管理,需要开发者手动释放。
十、什么情况下使用 weak 关键字,相比assign 有什么不同?
1,weak 的使用场景:
① ARC 中在有可能出现循环引用的时候,可以在其中一方使用weak 来引用另一方。比如代理属性就是使用weak,代理属性也可以使用assign。
② 自身已经对他进行一次强引用,没有必要再强引用一次,此时可以使用weak。比如,一个控件作为另一个控件的子控件的时候,因为父控件里的subview 属性已经是对子控件的一个强引用了,所以在需要声明为属性的时候使用weak。
2,weak 和 assign 的不同点:
① weak 策略在属性所指向的对象被销毁时,系统会将weak 修饰的属性对象的指针指向 nil ,在 OC 中给 nil 发消息时不会有问题的(就是访问 nil ),如果使用 assign 策略在属性所指的对象销毁时,属性对象指针还是指向原来的对象,由于该对象已经被销毁,这时候就产生了野指针的情况,如果访问一个野指针,就会导致程序崩溃。
② assign 可以修饰非 OC 对象,而 weak 必须用于 OC 对象。
十一、@property 的本质是什么?
1,@property 其实就是在编译阶段由编译器自动帮我们生成 ivar 成员变量,getter 方法和 setter 方法。
十二、ivar、getter、setter是如何生成并添加到这个类中的?
1,使用自动合成(autosynthesis)。
2,这个过程由编译器在编译阶段执行自动合成,所以编译器里看不到这些合成方法的源代码。
3,除了生成getter和setter方法外,编译器还要自动向类中添加成员变量,在属性名前面添加下划线,以此作为实例变量的名字。
4,大致生成了五个东西:
①每次增加一个属性,系统都会在ivar_list中添加一个成员变量的描述。
②在方法列表中(method_list)增加setter和getter方法的描述。
③在属性列表中(prop_list)增加一个属性的描述。
④计算该属性在对象中的偏移量。
⑤然后给出setter和getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量位置开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了强制类型转换。
十三、@protocol和category中如何使用@property?(协议和分类中)
1,在protocol中使用property只会生成setter和getter方法的声明,我们使用属性的目的,是希望遵守这个协议的对象能够实现该属性。
2,category使用@property也是只会生成setter和getter方法声明,如果我们真的需要给category增加属性的实现,需要借助runtime的两个函数:
①objc_setAssociatedObject和
②objc_getAssociatedObject。
十四、@property后面可以有哪些修饰符?
1,原子性natomic特质:
①如果不写默认就是atomic(系统会自动加上同步锁,影响性能)。
②在iOS开发中尽量指定为nonatomic,这样有助于提高程序的性能。
2,读写权限,readwrite(可读可写)、readonly(只读)。
3,内存管理语义assign、strong、weak、unsafe_unretained、copy。
4,方法名getter=、setter= .
5,还有不常用的:nonnull、null_resettable、nullable。
十五、使用atomic一定是线程安全的吗?
1,不是,因为atomic的本意是指属性的存取方法是线程安全的,并不保证整个对象是线程安全的。
2,举例:声明一个NSMutableArray的原子属性stuff,此时self.stuff和self.stuff = othersulf都是线程安全的。但是,使用[self.stuff objectAtIndex:index]就不是线程安全的,需要用互斥锁来保证线程安全性。
十六、@synthesize和@dynamic分别有什么作用?
1,@synthesize和@dynamic是@property的两个对应的词,如果都没写,就默认是@synthesize。
2,@synthesize的语义是如果没有手动实现setter方法和getter方法,那么编译器就会自动加上这两个方法。
3,@dynamic告诉编译器:属性的getter和setter方法由用户自己实现,不会自动生成(readonly只读的属性只需提供getter方法即可)。
注意:如果一个属性被声明为@dynamic var,然后没有提供setter和getter方法,编译的时候没问题,但是当程序运行到instance.var = someVar,由于缺setter方法,就会导致程序崩溃,或者当程序运行到someVar = instance.var时,由于缺少getter方法同样会导致崩溃,编译器没问题,运行时才执行响应的方法,这就是所谓的动态绑定。
十七、ARC下,不显示指定任何属性关键字时,默认的关键字都有哪些?
1,基本数据的话默认就是:atomic、readwrite、assign。
2,普通的OC对象默认的就是:atomic、readwrite、strong。
十八、@synthesize合成实例变量的规则是什么?假如property名为foo,存在一个名为_foo的实例变量,那么还会自动合成新变量吗?
1,@synthesize合成成员变量的规则:
①如果指定了成员变量的名称,会生成一个指定名称的成员变量。
②如果这个成员变量已经存在了就不在生成了.
③如果指定@synthesize foo ,就会生成一个名为foo的成员变量,也就是说,会生成一个与属性名相同的成员变量。
④如果是@synthesize foo = _foo,就不会生成成员变凉了。
2,所以,如果指定了成员变量名称(@synthesize foo),就会生成与属性同名的成员变量,如果不指定名称,默认生成的是带下划线的成员变量,因为已经存在一个与属性同名且带有下划线的成员变量了,由规则②可得,不会再生成了。
十九、在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
1,首先要搞清楚什么情况下不会自动合成(autosynthesis):
①同时重写了setter和getter方法时不会自动合成。
②重写了只读属性的getter方法时不会自动合成。
③使用了@dynamic时。
④在@protocol中定义的所有属性不会自动合成。
⑤在category中定义的所有属性不会自动合成。
⑥重载的属性,当在子类中重载了父类中的属性,必须使用@synthesize来手动合成ivar。
2,应用场景:
①当同时重写了setter和getter方法时,系统就不会申城ivar。这个时候要么就手动创建,要不就使用@synthesize foo = _foo;关联@property与ivar.
②可以用来修改成员变量名,建议不这样做,最好就是使用系统自动生成的。
二十、怎么使用copy关键字?
1,NSString、NSArray、NSDictionary等等经常使用copy关键字,是因为他们有对应的可变类型NSMutableString、NSMutableArray、NSMutableDictionary。为确保对象中的属性值不会无意间变动,应该在设置新属性的时候拷贝一份,保护其封装性。
2,block也经常使用copy关键字:
①block使用copy是从MRC遗留下来的传统,在MRC中,方法内部的block是在栈区的,使用copy可以把它放到堆区。
②在ARC中可写可不写,对于block使用copy还是strong效果都是一样的,但是建议写上copy,这样显示告知调用者编译器会自动对block进行了copy操作。
二十一、用@property声明的NSString、NSArray、NSDictionary为什么经常使用copy关键字,如果改用strong,可能造成什么问题?
1,因为使用父类指针可以指向子类对象,使用copy的目的是为了让本身对象的属性不受外界影响,使用copy无论传入的是一个可变的对象还是不可变得对象,本身持有的就是一个不可变的副本。当外界拿到这个属性进行使用的时候,其实是拷贝了一份,外界修改这个属性修改的只是copy的那一份,并不会影响这个属性原来的值。
2,如果我们使用strong,那么这个属性就有可能指向一个可变的对象,如果这个外部拿到了这个属性,其实是拿到的一个指向原来属性值的内存地址指针,如果进行修改,则修改的就是原始值。
二十二、深拷贝和浅拷贝?
1,浅拷贝(shallow copy):对于被复制对象的每一层都是指针赋值。拷贝指针,对象唯一,修改一处则其他处都受影响。
2,深拷贝(one-level-deep copy):在深拷贝操作时,对于被复制对象,至少有一层是深拷贝。深拷贝(拷贝了对象)。修改某一处不一定会影响其他处。
3,完全拷贝(real-deep copy):在完全拷贝操作时,对于被复制对象的每一层都是对象的拷贝。修改一处一定不会影响其他处。
4,非集合类对象的copy与mutableCopy:
{
不可变对象copy:浅拷贝
不可变对象mutableCopy:深拷贝
可变对象copy:深拷贝
可变对象mutableCopy:深拷贝
}
5,集合类对象的copy与mutableCopy:
{
不可变对象copy:浅拷贝
不可变对象mutableCopy:单层深拷贝
可变对象copy:单层深拷贝
可变对象mutableCopy:单层深拷贝
}
6,这里需要注意的是集合对象的内容复制仅限于对象本身,集合对象里的元素仍然是指针拷贝,也就是浅拷贝。
二十三、这个写法会出现什么问题:@property (copy) NSMutableArray *array;
1,因为copy策略拷贝出来的是一个不可变的对象,使用copy,无论声明的这个属性是个可变还是不可变的,在别处拿到这个属性的时候其实是一个不可变的,把一个真是类型为不可变的对象当做声明中的可变的来使用,会造成崩溃。
2,这里还有一个问题,该属性使用了同步锁,atomic nonatomic都不写的话默认就是atomic,有同步锁,会在创建的时候生成一些额外的代码用于帮助编写多线程程序,这回带来性能问题,通过声明nonatomic可以节省这些不必要的额外开销,在iOS开发中应该使用nonatomic替代atomic。
二十四、如何让自定义类可以用copy修饰符?如何重写带copy关键字的setter?
1,要想让自己写的对象具有拷贝动能,则需要实现NSCopying协议,如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopying和NSMutableCopying协议,不过一般没什么必要,实现NSCopying协议就够了。
2,//实现不可变版本拷贝
- (id)copyWithZone:(NSZone *)zone;
//实现可变版本拷贝
- (id)mutableCopyWithZone:(NSZone *)zone;
//重写带copy关键字的setter
- (void)setName:(NSString *)name
{
_name = [name copy];
}
二十五、+(void)load;和+(void)initialize;有什么用?
1,+(void)load;
①当类对象被引入项目时,runtime会向每一个类对象发送load消息。
②load方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序,父类>子类>分类。
③由于load方法会在类被import时调用一次,而这个时候往往是改变类的行为的最佳时机,在这里可以使用利用method swizlling来修改原有的方法。
④load方法不会被类自动继承。
2,+(void)initialize;
①也是在第一次使用这个类的时候调用这个方法,也就是说initialize也是懒加载的。
3,总结:
①在Objective-C中,runtime会自动调用每个类的这两个方法。
②+load会在类的初始加载时调用。
③+initialize会在第一次调用类的类方法和实例方法之前被调用。
④这两个方法都是可选的,且只有在实现了他们时才会被调用。
⑤两者的共同点:两个方法都是只被调用一次。
二十六、Foundation对象和Core Foundation对象又什么区别?
1,Foundation框架使用OC实现的,Core Foundation是使用C语言实现的。
2,Foundation对象和Core Foundation对象之间的转换:俗称桥接
ARC环境下桥接关键字:
__bridge //用于Foundation对象和Core Foundation对象之间的转换
__bridge_retained // Foundation对象转为Core Foundation对象
__bridge_transfer // Core Foundation对象转为Foundation对象
3,Foundation对象转为Core Foundation对象:
①使用__bridge进行桥接,他仅仅是将OC对象的地址传给了C对象,并没有转移对象的所有权,也就是说,使用这个关键字进行桥接,如果OC对象释放了,C对象也就不能用了。在ARC条件下,使用了__bridge来桥接,被转为OC对象被转为C对象,那么C对象可以不用主动释放,因为他们还是同一个地址,ARC会自动管理OC对象,和C对象。
{
NSString *strOC1 = [NSString stringWithFormat:@"abcdefg"];
CFStringRef strC1 = (__bridge CFStringRef)strOC1;
}
②使用__bridge_retained桥接,会将对象的所有权转给C对象,就算是OC对象释放了,C对象也能使用。
在ARC条件下,如果使用了这个关键字桥接,那么C对象必须自己手动释放,因为桥接的时候转移了所有权,C对象不归ARC管理。
{
NSString *strOC2 = [NSString stringWithFormat:@"abcdefg"];
//CFStringRef strC2 = (__bridge_retained CFStringRef)strOC2;
CFStringRef strC2 = CFBridgingRetain(strOC2); //这一句,就等同于上一句
CFRelease(strC2);
}
4,Core Foundation对象转Foundation对象:
①使用__bridge桥接,不转移所有权,C对象释放了OC对象也就不能用了。
{
CFStringRef strC3 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
NSString *strOC3 = (__bridge NSString *)strC3;
CFRelease(strC3);
}
②使用__bridge_transfer桥接,转移了所有权,C对象释放了OC对象也能用,会自动释放C对象,也就是说不用手动释放C对象了。
{
CFStringRef strC4 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
//NSString *strOC = (__bridge_transfer NSString *)strC;
NSString *strOC4 = CFBridgingRelease(strC4); //这一句,就等同于上一句
}
MRC环境下直接强转:
{
//将Foundation对象转换为Core Foundation对象,直接强制类型转换即可
NSString *strOC1 = [NSString stringWithFormat:@"xxxxxx"];
CFStringRef strC1 = (CFStringRef)strOC1;
NSLog(@"%@ %@", strOC1, strC1);
[strOC1 release];
CFRelease(strC1);
//将Core Foundation对象转换为Foundation对象,直接强制类型转换即可
CFStringRef strC2 = CFStringCreateWithCString(CFAllocatorGetDefault(), "12345678", kCFStringEncodingASCII);
NSString *strOC2 = (NSString *)strC2;
NSLog(@"%@ %@", strOC2, strC2);
[strOC2 release];
CFRelease(strC2);
}
二十七、addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
**
1. self.person:要监听的对象
2.参数说明
1>观察者,负责处理监听事件的对象
2>要监听的属性
3>观察的选项(观察新、旧值,也可以都观察)
4>上下文,用于传递数据,可以利用上下文区分不同的监听
*/
//[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
/**
*当监控的某个属性的值改变了就会调用
*
*@param keyPath监听的属性名
*@param object属性所属的对象
*@param change属性的修改情况(属性原来的值、属性最新的值)
*@param context传递的上下文数据,与监听的时候传递的一致,可以利用上下文区分不同的监听
*//*
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSLog(@"%@对象的%@属性改变了:%@", object, keyPath, change);
}
二十八、KVO的内部实现原理?(苹果用什么实现的对一个对象的KVO键值监听)
1,KVO是基于runtime机制实现的。
2,当某个类的属性对象第一次被观察时,系统就会在运行时动态的创建一个该类的派生类,在这个派生类中重写基类中任何被观察属性的setter方法,派生类在被重写的setter方法内部实现真正的通知机制。
3,如果原来的类为Person,那么生成的派生类名为NSKVONotifying_Person(在原类类名的基础前加NSKVONotifying_)。
4,每个类的对象都有一个isa指针指向当前的类,当一个类的对象第一次被观察,那么系统就会将isa指针指向动态生成的这个派生类,在给被监控的属性赋值时执行的其实是派生出来的这个类的setter方法。
5,键值观察通知依赖于NSObject的两个方法:willChangeValueForKey;和didChangeValueForKey;在一个被观察属性发生改变之前,will方法一定会被调用,这一次记录旧值,当发生改变之后did方法又会被调用,这一次记录新值,继而调用observeValueForKey:ofObject:change:context:
6,补充:KVO的这套实现机制中苹果偷偷重写了class方法,让我们误认为还是使用的当前类,从而隐藏派生类。
二十九、如何手动触发一个value的KVO?
1,自动触发场景:在注册KVO之前设置一个初始值,注册之后设置一个不一样的值,值变化了就触发了。
2,手动触发:
@property (nonatomic, strong) NSDate *now;
- (void)viewDidLoad
{
[super viewDidLoad];
// “手动触发self.now的KVO”,必写。
[self willChangeValueForKey:@"now"];
// “手动触发self.now的KVO”,必写。
[self didChangeValueForKey:@"now"];
}
三十、KVC中,有个实例变量NSString *_foo ,调用setValue:forKey:时是以foo还是_foo作为key?
1,都可以。
三十一、KVC的keyPath中的集合运算符如何使用?
1,必须用在集合对象上或普通对象的集合属性上。
2,简单集合运算符有@avg , @count, @max ,@min , @sum.
3,格式@"@sum.age"或@"集合属性.@max.age"
三十二、KVC和KVO的keyPath一定是属性吗?
1,可以是成员变量。
三十三、如何关闭KVO的默认实现,并进入自定义的KVO实现?
具体内容连接自自定义KVO 的实现---原文来自顾鹏
网友评论