一个面试问题的答案总结
- 1.@property中有哪些属性关键字?
-
1.strong表示指向并拥有对象。其修饰的对象引用计数+1,如果引用计数不为0,该对象不会被销毁,如果强制销毁,可以设置该对象为nil。 2.copy与strong类型。不同的是,strong的复制是多个指针指向同一个地址,而copy的复制是在内存中复制一份对象,指针对象的是不同的地址。copy一般用于修改有对应可变类型的不可变对象上,例如:NSArray、NSDictionary、NSString。 3.weak表示指向不拥有对象。其修饰的对象引用计数不会+1,也无需手动管理,会在内存中自动销毁。 4.assign主要是修改基本数据类型,例如:NSInteger和CGFloat等,。这些数值主要存在于栈中。 (weak一般用于修饰对象,assign一般用于修饰基本数据类型。原因是:assign修改的对象被释放后,指针的地址依然存在,会造成“野指针”,在堆上容易造成崩溃,在栈上的内存系统会自动处理。) 5.actomic 修饰的对象会保证setter和getter的完整性,任何县城访问它都会得到一个完整的初始化后的对象。当多个县城同时调用set和get时,就会导致获得的对象值不一样。要想线程绝对安全,就要用@synchroized。 6.nonactomic 修饰的对象不保证setter和getter的完整性,当多个线程访问它时,它可能会返回为初始话的对象。
- weak属性需要在dealloc中置nil么?
-
不需要,在 ARC 环境下,无论是强指针还是弱指针都不需要在 dealloc 中置为 nil ,ARC 会自动处理的。
- @synthesize和@dynamic分别有什么作用?
-
@property有两个对应的词,一个是 @synthesize,一个是 @dynamic。如果 @synthesize和 @dynamic都没写,那么默认的就是@syntheszie var = _var; @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法。 @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)。假如一个属性被声明为 @dynamic var,然后你没有提供 @setter方法和 @getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter 方法会导致程序崩溃;或者当运行到 someVar = var 时,由于缺 getter 方法同样会导致崩溃。编译时没问题,运行时才执行相应的方法,这就是所谓的动态绑定。 这两者的应用环境: 一般情况下,我们通常会用@synthesize来完成相关的属性操作,这样我们不写get和set方法完全没问题,因为编译时编译器默认已经生成了一套get和set方法。目前,现在已经不需要写synthesize。 @dynamic最常用的地方是类别里添加属性操作。都知道,在category里面不能添加属性。当我们有这个需求时,我们必须通过运行时runtime来完成,这时编译器不会自动给我们生成get和set方法,我们就需要自己来完成实现。这时我们就需要告诉编译器这个方法我自己去实现,这个时候需要给编译器一个标记@dynamic。然后我们再通过runtime的objc_setAssociatedObject/objc_getAssociatedObject来完成属性的get和set实现。
- ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些?
-
atomic,readwrite,strong(对象),assgin(基本数据类型)
- 用@property声明的
NSString
(或NSArray
,NSDictionary
)经常使用copy
关键字,为什么?如果改用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); }
- 在有了自动合成属性实例变量之后,@synthesize还有哪些使用场景?
-
当重写一个父类的属性,必须显示合成它。值得注意的是,合成一个属性会自动合成backing ivar,如果缺少属性合成,ivar也会消失,除非显示声明。 另外使用显示@synthesize的是指定一个不同的实例变量名。
可参考链接:https://stackoverflow.com/questions/19784454/when-should-i-use-synthesize-explicitly/19821816#19821816 中的第10楼的答案
You have two choices for making it exist. You can either change the @implementation to this: @implementation MyObject { NSString *_title; } - (NSString *)title { return _title; } - (void)setTitle:(NSString *)title { _title = [title copy]; } @end Or change it to this: @implementation MyObject @synthesize title = _title; - (NSString *)title { return _title; } - (void)setTitle:(NSString *)title { _title = [title copy]; } @end
- objc中向一个nil对象发送消息将会发生什么?
-
OC中nil是被当做0定义的。也就是说runtime要去获取这个nil的信息,会去读取内存中0的位置,这肯定是不允许的,会返回nil,0,0.0等数据,根据返回值类型。 如果一个方法返回值是一个对象,那么发送给nil的消息将返回0(nil)。例如: Person * motherInlaw = [[aPerson spouse] mother]; 如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。 objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,然后在发送消息的时候,objc_msgSend方法不会返回值,所谓的返回内容都是具体调用时执行的。那么,如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。
- objc中向一个对象发送消息
[obj foo]
和objc_msgSend()
函数之间有什么关系? -
[obj foo]方法编译之后就是objc_msgSend()函数调用.
objc_msgSend()是[obj foo]的具体实现。
在runtime中,objc_msgSend()是一个c函数,[obj foo]会被翻译成这样的形式objc_msgSend(obj, foo)。- 去obj的对应的类中找方法
- 先找缓存,找不到再去找方法列表,
- 再找父类,如此向上传递。
- 什么时候会报unrecognized selector的异常?
-
1.对象未实现该方法。 2.对象已经被释放。 参考链接:https://www.jianshu.com/p/4d40ff407d0e
- 一个objc对象如何进行内存布局?(考虑有父类的情况)
-
所有父类的成员变量和自己的成员变量都会存放在该对象所对应的存储空间中. 每一个对象内部都有一个isa指针,指向他的类对象,类对象中存放着本对象的 1.对象方法列表(对象能够接收的消息列表,保存在它所对应的类对象中) 2.成员变量的列表, 3.属性列表, 它内部也有一个isa指针指向元对象(meta class),元对象内部存放的是类方法列表,类对象内部还有一个superclass的指针,指向他的父类对象。 链接:https://www.jianshu.com/p/d5fec5da72b2
- 成员变量、属性和实例变量的区别
-
@interface StartPageViewController () { NSString *testName1; //成员变量 } @property (nonatomic, copy) NSString *testName2; //这个如果写在.h上,就是属性 @end @implementation StartPageViewController - (void)viewDidLoad { [super viewDidLoad]; _testName2 = @"1111"; //实例变量 }
注意:属性 本质还是成员变量 , 只不过编译器会生成对应的set get方法 - 一个objc对象的
isa
的指针指向什么?有什么作用? -
isa 指的就是: 对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。 isa帮助一个对象找到它的方法。 isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。 同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。
- runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)
-
类对象中有类方法和实例方法的列表,列表中记录着方法的名词、参数和实现,而selector本质就是方法名称,runtime通过这个方法名称就可以在列表中找到该方法对应的实现。 struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class const char *name long version long info long instance_size struct objc_ivar_list *ivars struct objc_method_list **methodLists struct objc_cache *cache struct objc_protocol_list *protocols #endif } OBJC2_UNAVAILABLE; 这里声明了一个指向struct objc_method_list指针的指针,可以包含类方法列表和实例方法列表 具体实现 在寻找IMP的地址时,runtime提供了两种方法 IMP class_getMethodImplementation(Class cls, SEL name); IMP method_getImplementation(Method m) 而根据官方描述,第一种方法可能会更快一些 @note
class_getMethodImplementation may be faster than
method_getImplementation(class_getInstanceMethod(cls, name)). 对于第一种方法而言,类方法和实例方法实际上都是通过调用class_getMethodImplementation()来寻找IMP地址的,不同之处在于传入的第一个参数不同 类方法(假设有一个类A) class_getMethodImplementation(objc_getMetaClass("A"),@selector(methodName)); 实例方法: class_getMethodImplementation([A class],@selector(methodName)); 通过该传入的参数不同,找到不同的方法列表,方法列表中保存着下面方法的结构体,结构体中包含这方法的实现,selector本质就是方法的名称,通过该方法名称,即可在结构体中找到相应的实现。 struct objc_method { SEL method_name char *method_types IMP method_imp } 而对于第二种方法而言,传入的参数只有method,区分类方法和实例方法在于封装method的函数 类方法: Method class_getClassMethod(Class cls, SEL name) 实例方法: Method class_getInstanceMethod(Class cls, SEL name) 最后调用IMP method_getImplementation(Method m) 获取IMP地址
还有一点有趣的是class_getClassMethod()的第一个参数无论传入objc_getClass()还是objc_getMetaClass(),最终调用method_getImplementation()都可以成功的找到类方法的实现。
而class_getInstanceMethod()的第一个参数如果传入objc_getMetaClass(),再调用method_getImplementation()时无法找到实例方法的实现却可以找到类方法的实现。
- 使用
runtime Associate
方法关联的对象,需要在主对象dealloc的时候释放么? -
首先看一下,绑定的方法: objc_setAssociatedObject(self, @selector(associatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 最后的这个参数类似于property的属性设置,有如下几个 OBJC_ASSOCIATION_ASSIGN OBJC_ASSOCIATION_RETAIN_NONATOMIC OBJC_ASSOCIATION_COPY_NONATOMIC OBJC_ASSOCIATION_RETAIN OBJC_ASSOCIATION_COPY 可以根据我们的具体需要进行使用, 在ARC下不需要 在MRC中,对于使用retain或copy策略的需要
- objc中的类方法和实例方法有什么本质区别和联系?
-
1.类方法: 在OC类定义方法时以 + 开头的方法,又称为静态方法。如:+ (viod)方法名{ }. 作用:不用实例化就可以直接调用的方法,一般是有返回值的,返回对应的实例(数组、字符串等),还有可能就是本身类的实例对象。常用于一些common或者UtiKit等公共方法类中。 调用:[类名 方法名】 限制: 不能使用自身属性 ,想要使用的类方法就要在定义类的.h文件中声明哟,否则不能直接调用! 2.实例方法: 在OC定义中以 - 开头的方法。如: - (viod)方法名{ } 作用:必须先创建实例对象,相当于回应消息模式。实例对象调用的方法。 调用:[对象 方法】 可以使用自身属性。 其中: 类方法: 类方法是属于类对象的 类方法只能通过类对象调用 类方法中的self是类对象 类方法可以调用其他的类方法 类方法中不能访问成员变量 类方法中不定直接调用对象方法 实例方法: 实例方法是属于实例对象的 实例方法只能通过实例对象调用 实例方法中的self是实例对象 实例方法中可以访问成员变量 实例方法中直接调用实例方法 实例方法中也可以调用类方法(通过类名)
- runtime如何实现weak变量的自动置nil
-
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。 weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)
- 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
-
不能向编译后得到的类中增加实例变量; 能向运行时创建的类中添加实例变量; 原因如下: 因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量; 运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。
- runloop和线程有什么关系?
-
RunLoop的字面意思就是“运行回路”,每一个线程都有一个RunLoop。 不一样的是,主线程的RunLoop是一直在启动的。而后台线程的RunLoop是默认没有启动的。 Runloop提供了Event Loop,管理事件、消息,让线程在没有任务时休眠以避免占用资源、有消息进来时立刻被唤醒。 Event Loop 消息循环流程 "接受消息"->"等待"->"处理消息" Runloop和线程是一一对应保存在全局的字典里面。线程(非主线程)刚创建时并没有Runloop,当你需要时创建,线程结束时Runloop销毁。 苹果不允许直接创建 RunLoop,你只能在线程内部获取,它提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
- runloop的mode作用是什么?
-
model 主要是用来指定事件在运行循环中的优先级的,分为: NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态 UITrackingRunLoopMode:ScrollView滑动时 UIInitializationRunLoopMode:启动时 NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合 苹果公开提供的 Mode 有两个: NSDefaultRunLoopMode(kCFRunLoopDefaultMode) NSRunLoopCommonModes(kCFRunLoopCommonModes)
- 以
+ scheduledTimerWithTimeInterval...
的方式触发的timer,在滑动页面上的列表时,timer会暂定回调,为什么?如何解决? -
RunLoop只能运行在一种mode下,如果要换mode,当前的loop也需要停下重启成新的。利用这个机制,ScrollView滚动过程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode会切换到UITrackingRunLoopMode来保证ScrollView的流畅滑动:只能在NSDefaultRunLoopMode模式下处理的事件会影响scrllView的滑动。 如果我们把一个NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环中的时候, ScrollView滚动过程中会因为mode的切换,而导致NSTimer将不再被调度。 同时因为mode还是可定制的,所以: Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes(kCFRunLoopCommonModes)来解决。 或者通过在创建一个线程来运行,避免mode切换来解决。