美文网首页
iOS基础题

iOS基础题

作者: 闪电迷 | 来源:发表于2016-09-01 16:10 被阅读309次

    一、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 的实现---原文来自顾鹏

    相关文章

      网友评论

          本文标题:iOS基础题

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