1. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
- property本质
@property = ivar(实例变量) + getter + setter(存取方 法) struct property_t { const char *name; const char *attributes; }
- ivar、getter、setter 是如何生成并添加到这个类中的?
“自动合成”( autosynthesis)
由于@property只是对getter和setter方法进行了声明,其他的什么也没有干,所以完成属性的定义以后,编译器自动在编译阶段根据@synthesize 去实现getter和setter和ivar,默认是@synthesize ivar = _ivar
- 底层操作
每次在增加一个属性,系统都会在 类中的ivar_list 中添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出 setter 与 getter 方法对应的实现,在 setter 方法中从偏移量的位置开始赋值,在 getter 方法中从偏移量开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了类型强转。
2. @synthesize和@dynamic分别有什么作用?
-
@property与@synthesize
、@property与@dynamic
,是一一对应并且成对出现的,@property
只起到getter和setter方法声明的作用,而实现则是由@synthesize来完成。如果@synthesize
和@dynamic
都没写,那么编译器默认添加的就是@syntheszie var = _var
- @synthesize语义是如果没有手动生成getter 和setter,那么编译器自动生成。并且它自动合成的一共有三项
_ivar
、setter
、getter
,合成规则:要么合成三项,要么都不合成。 - @dynamic语义是告诉编译器不要为我生成_ivar变量、和getter 和setter方法,我要自己实现
- 什么情况下自动合成@synthesize会失效?
- 当我们想要自己手动管理的时候
- 同时重写了 setter 和 getter 时
- 重写了只读属性的 getter 时
- 使用了 @dynamic 时
- 在 @protocol 中定义的所有属性
- 在 category 中定义的所有属性
- 重载的属性
- @synthesize失效的例子
// 打开第14行和第17行中任意一行,就可编译成功 @import Foundation; @interface CYLObject : NSObject @property (nonatomic, copy) NSString *title; @end @implementation CYLObject { // NSString *_title; } //@synthesize title = _title; - (instancetype)init { self = [super init]; if (self) { _title = @"微博@iOS程序犭袁"; } return self; } - (NSString *)title { return _title; } - (void)setTitle:(NSString *)title { _title = [title copy]; } @end
- @synthesize使用方法
// 声明自动合成get和set方法,@property默认就是这种情况 @synthesize mArray = _mArray;// 默认 @synthesize mArray;// 生成的名称是 mArray @synthesize mArray = _abcd;// 生成的名称是 _abcd
3. runtime 如何实现 weak 属性
- weak属性的特点
- 表示一种非拥有的属性关系,setter方法赋值时既不保留新值,也不释放旧值,因为若引用不会使对象的引用计数器+1
- runtime怎么实现weak置nil
- runtime会对当前程序所注册的类进行布局,并且将所有weak对象放到hash表中。在运行时,调用weak对象的setter方法时,会将weak属性(假设weak属性为name)所赋值对象地址当作key,name的地址当作值存入哈希表中,当name所持有的对象释放时查找hash表,将name置nil
4. @property中有哪些属性关键字
- 原子性:
nonatomic
、atomic(默认)
在默认情况下,由编译器合成的方法(set和get方法)会通过锁定机制确保其原子性(atomicity)。如果属性具备 nonatomic 特质,则不使用自旋锁。 - 读写:
readwrite(读写)
、readonly (只读)
- 内存语义:
assign、strong、 weak、unsafe_unretained、copy、retain
- 方法名:
getter=
、setter=
@property (nonatomic, getter=isOn) BOOL on;
5. weak属性需要在dealloc中置nil吗
不需要:ARC下不需要在dealloc中置nil,ARC会自动置nil。
6. ARC下,不显式指定任何属性关键字时,默认的关键字都有哪些
- 基本数据类型
- atomic、readwrite、assign
- 对象类型
- atomic、readwrite、strong
7. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题
- 非集合类对象copy - NSString、NSMutableString
[immutableObject copy] // 浅复制 [immutableObject mutableCopy] //深复制 [mutableObject copy] //深复制 [mutableObject mutableCopy] //深复制
- 集合类对象
如果想要对集合对象进行深层次的复制调用如下方法// 单层的意思就是集合内部对象仍然是浅复制 [immutableObject copy] // 浅复制 [immutableObject mutableCopy] //单层深复制 [mutableObject copy] //单层深复制 [mutableObject mutableCopy] //单层深复制
// 方法含义是复制self.mutableCopy数组,并且向里面的对象都发送copy消息 // 这种方法也不能完全完成深层复制,该方法只会对mutableCopy对象内的对象发送一次copy,如果对象是多层嵌套的,就不能继续复制了 NSArray *a = [[NSArray alloc] initWithArray:self.mutableCopy copyItems:YES];
7. 一个objc对象如何进行内存布局?(考虑有父类的情况)
- 内部存储所有父类的成员变量和自己的成员变量
- isa指针
-
内存布局
-
对象继承关系
8. 使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放吗
无论在MRC下还是ARC下均不需要。
- 对象内存销毁时间表
根据以下时间表可以看出,在主对象销毁以后(1步)调用dealloc方法,如果主对象没有实现dealloc方法(ARC环境下
)那么逐层向上调用直到在NSObjcet类中找到dealloc方法,这个dealloc方法内部调用 Objective-C runtime 中的object_dispose()
方法去完成下面第4步。如果是MRC
下,主对象必须实现dealloc方法去释放自己的实例变量(iVars),即使主对象重写了dealloc方法,那么在这个方法中也会调用[super dealloc]去掉用NSObject中的dealloc去调用object_dispose()// 对象的内存销毁时间表 // 根据 WWDC 2011, Session 322 (36分22秒)中发布的内存销毁时间表 // 1. 调用 -release :引用计数变为零 * 对象正在被销毁,生命周期即将结束. * 不能再有新的 __weak 弱引用, 否则将指向 nil. * 调用 [self dealloc] 2. 子类 调用 -dealloc * 继承关系中最底层的子类 在调用 -dealloc * 如果是 MRC 代码 则会手动释放实例变量们(iVars) * 继承关系中每一层的父类 都在调用 -dealloc 3. NSObject 调 -dealloc * 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法 4. 调用 object_dispose() * 为 C++ 的实例变量们(iVars)调用 destructors * 为 ARC 状态下的 实例变量们(iVars) 调用 -release * 解除所有使用 runtime Associate方法关联的对象 * 解除所有 __weak 引用 * 调用 free()
9. 直接_objc_msgForward调用它将会发生什么
-
_objc_msgForward
是一个函数指针(和 IMP 的类型一样),是用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。 - 当调用
[receiver message]
编译器将之转化成objc_msgSend(receiver, @selector(message))
,objc_msgSend方法内部实现逻辑就是,receiver为nil时返回nil,否则去receiver->isa
中的cache和方法列表中查找@selector(message)的实现IMP,如果找到把找到的方法复制给IMP,没找到就把_objc_msgForward
函数指针复制给IMP,然后执行IMP - 如何调用_objc_msgForward
- typedef void (*voidIMP)(id, SEL, ...)
- id(方法所属对象)
- SEL(方法名)
- 可变参数类型(可变参数)
- 一旦调用_objc_msgForward方法则会跳过正常的查找self的SEL的IMP的过程,直接进入消息转发(转发3步),即使self已经实现了SEL那么也会告诉objc_msgSend,“我没有在这个对象里找到这个方法的实现”
- typedef void (*voidIMP)(id, SEL, ...)
- 使用场景
- 最常见的场景是:你想获取某方法所对应的NSInvocation对象
- JSPatch 就是直接调用_objc_msgForward来实现其核心功能的:
JSPatch 以小巧的体积做到了让JS调用/替换任意OC方法,让iOS APP具备热更新的能力。
10. 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
-
不能向编译后得到的类中增加实例变量
- 因为编译后的类已经注册在 runtime 中(已经把内存分配给了类),类结构体中的
objc_ivar_list
实例变量的链表 和instance_size
实例变量的内存大小已经确定,同时runtime 会调用class_setIvarLayout
或class_setWeakIvarLayout
来处理 strong和weak 引用。所以不能向存在的类中添加实例变量
- 因为编译后的类已经注册在 runtime 中(已经把内存分配给了类),类结构体中的
-
能向运行时创建的类中添加实例变量运调用
class_addIvar
函数运行时创建类过程分3步如下:
- 为"class pair"分配空间(
objc_allocateClassPair
) - 为创建的类添加方法和成员(
class_addMethod
、class_addIvar
,注添加成员变量和方法必须在1分配之后,2注册之前) - 注册你创建的这个类(
objc_registerClassPair
)
- 为"class pair"分配空间(
-
objc_allocateClassPair里面的pair指的是什么呢?
- objc_allocateClassPair只返回一个创建好的class,那么pair的另一半就是元类(
meta-class
)也就是说objc_allocateClassPair会创建两个类,一个类对象,一个元类对象
- objc_allocateClassPair只返回一个创建好的class,那么pair的另一半就是元类(
11. runloop的mode作用是什么?
- model 主要是用来指定事件在运行循环中的优先级的,分为:
-
NSDefaultRunLoopMode
(kCFRunLoopDefaultMode):默认,空闲状态 -
UITrackingRunLoopMode
:ScrollView滑动时 -
UIInitializationRunLoopMode
:启动时 -
NSRunLoopCommonModes
(kCFRunLoopCommonModes):Mode集合包括默认和滑动模式
-
12. dispatch_barrier_async的作用是什么?
- 保证任务按照一定的顺序执行,在dispatch_barrier_async之前的任务全部执行完毕以后在执行队列后面的任务
注意:使用 dispatch_barrier_async ,该函数只能搭配自定义并行队列dispatch_queue_t
使用。不能使用:dispatch_get_global_queue
,否则 dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。NSLog(@"begin"); // 使用 dispatch_barrier_async函数,队列一定是手动创建的并发队列,不能是系统的全局并发队列 dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"----1-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"----2-----%@", [NSThread currentThread]); sleep(3); }); NSLog(@"--------------------"); // 执行完1,2在执行barrier里面的block,然后执行3,4 dispatch_barrier_async(queue, ^{ NSLog(@"----barrier-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"----3-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"----4-----%@", [NSThread currentThread]); }); NSLog(@"end");
12. 以下代码运行结果如何?
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
以上代码执行后只输出1。因为主线程发生了死锁
原因:在主队列中以同步(sync)的方式派发一个任务会出现一下两种情况:这样就发生“你等我我等你的状况”谁也不执行(挂起状态),所以死锁
- 当前任务需要等待主队列中的任务执行完成以后才能执行(因为是穿行队列)
- 而当前执行的派发任务(dispatch_sync)要等block中的任务执行完成以后才能执行
13. addObserver:forKeyPath:options:context:各个参数的作用分别是什么,observer中需要实现哪个方法才能获得KVO回调?
// 添加键值观察
/*
1 观察者,负责处理监听事件的对象
2 观察的属性
3 观察的选项
4 上下文
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
- self需要实现以下方法
// 所有的 kvo 监听到事件,都会调用此方法
/*
1. 观察的属性
2. 观察的对象
3. change 属性变化字典(新/旧)
4. 上下文,与监听的时候传递的一致
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
- NSKeyValueObservingOptions的种类
-
NSKeyValueObservingOptionNew
:提供更改前的值(存储在change中) -
NSKeyValueObservingOptionOld
:提供更改后的值(存储在change中) -
NSKeyValueObservingOptionInitial
:观察最初的值(在注册观察服务时会调用一次触发方法) -
NSKeyValueObservingOptionPrior
:分别在值修改前后触发方法(即一次修改有两次触发)
-
14. 如何手动通知KVO
- 手动通知kvo:在设置kvo情况下,_student里面的age没有被改变的时候,也可以在观察者中调用observeValueForKeyPath(自己控制回调时机)
// 在控制器中,控制器是观察者 _student = [ManualKVO new]; [_student addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; NSLog(@"1"); // 自己控制回调时机,而“回调的调用时机”就是在你调用 didChangeValueForKey: 方法时。即使self.age没有被改变也会回调 [_student willChangeValueForKey:@"age"]; // “手动触发self.now的KVO”,必写。 NSLog(@"2"); // 在didChangeValueForKey方法里面执行observeValueForKeyPath回调 [_student didChangeValueForKey:@"age"]; // “手动触发self.now的KVO”,必写。 NSLog(@"4"); // 在观察者类:self - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary
*)change context: (void *)context { NSLog(@"3"); //NSLog(@"%@", change); } - kvo实现原理:
- 只要设置kvo的对象实现了setter方法,就能实现kvo
- property属性自动支持kvo
- 键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, observeValueForKey:ofObject:change:context: 会被调用,继而 didChangeValueForKey: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了。
[_student addObserver:self forKeyPath:@"age" // 实例变量 options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; // 执行完上面的方法,系统做了如下几件事 // 1. 生成student的子类NSNotification_Student,并重写student的setter(setAge:)方法 // 2. 将_student对象的isa指针内容改成只想NSNotification_Student类 // 3. 当调用_student的setAge方法的时候就会调用子类重写的setAge方法,并在该方法中添加通知observer的逻辑 NSNotification_Student内setter方法主要逻辑: - (void)setNow:(NSDate *)aDate { [self willChangeValueForKey:@"now"]; [super setValue:aDate forKey:@"now"]; [self didChangeValueForKey:@"now"]; }
- 禁止kvo的自动监听功能以后还能怎么实现观察(应用到手动通知kvo)
// Student.h
#import
@interface Student : NSObject
{
NSString *_age;
}
- (void)setAge:(NSString *)age;
- (NSString *)age;
@property (nonatomic, strong) NSString *name;
@end
// Student.m
#import "Student.h"
@implementation Student
@synthesize name = _name;
- (void)setName:(NSString *)name
{
_name = name;
}
- (NSString *)name
{
return _name;
}
// 手动设定KVO
- (void)setAge:(NSString *)age
{
// 最重要的就是下面两个方法(手动通知kvo)
[self willChangeValueForKey:@"age"];
_age = age;
// 在didChangeValueForKey中调用观察者的observerValueForKey
[self didChangeValueForKey:@"age"];
}
- (NSString *)age
{
return _age;
}
// 在注册kvo的时候(调用addObserver:)会调用下面方法,返回NO则禁用传入的key的kvo
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
// 如果监测到键值为age,则指定为非自动监听对象
if ([key isEqualToString:@"age"])
{
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
@end
15. IBOutlet连出来的视图属性为什么可以被设置成weak?
因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。
参考stackoverflow
不过这个回答漏了个重要知识,使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系
16. oc中protocol、category和继承的区别
- protocol的作用是为一些列类仅仅提供一套公用的接口,而完全没 有办法也没可能去提供具体的一些实现情况
@protocol ProcessDataDelegate
@required //必须实现的方法(默认) - (void) processSuccessful: (BOOL)success; @optional - (id) submitOrder: (NSNumber *) orderid; @end - category则是为一个已有的类提供一些额外的接口和具体实现,并且可以有声明,没实现,只要不调用就好了
// 声明 #import "SomeClass.h" @interface SomeClass (Hello) -(void)hello; @end // 实现 #import "SomeClass+Hello.h" @implementationSomeClass (Hello) -(void)hello{ NSLog (@"name:%@ ", @"Jacky"); } @end
- 而继承则基于两者之间,既可以想 protocol一样提供只是纯粹提供接口,也可以像Category一样提供完整的实现,而且继承还能对类以后的功能进行改写
16. Category和Protocol的使用场景,使用时应注意什么
- 使用场景
- Category:
- 当你在定义类的时候,在某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加方法。
- 一个类中包含了许多不同的方法需要实现,而这些方法需要不同团队的成员实现
- 当你在使用基础类库中的类时,你可能希望这些类实现一些你需要的方法。
- Protocol:
- 最常用的就是委托代理模式,Cocoa框架中大量采用了这种模式实现数据和UI的分离。例如UIView产生的所有事件,都是通过委托的方式交给Controller完成。
- Category:
- 注意事项
- Category:
- Category可以访问原始类的实例变量,但不能添加变量,如果想添加变量,可以考虑通过继承创建子类。
- Category可以重载原始类的方法,但不推荐这么做,这么做的 后果是你再也不能访问原来的方法。如果确实要重载,正确的选择是创建子类。
- 和普通接口有所区别的是,在分类的实现文件中可以不必实现所有声明的方法,只要你不去调用它。
- Protocol:
- Protocol本身是可以继承的
@protocol A -(void)methodA; @end @protocol B -(void)methodB; @end // 如果你要实现B,那么methodA和methodB都需要实现。
- Protocol是类无关的,任何类都可以实现定义好的Protocol。如果我们想知道某个类是否实现了某个Protocol,还可以使用conformsToProtocol进行判断
- Protocol本身是可以继承的
- Category:
17. 如何检测僵尸对象
18. gcd和NSOperation区别
- GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择(GCD更轻量,Operation更多元)
- NSOperation能方便的取消已经放入队列中的任务、设置任务依赖、设置NSOperation的priority优先级即:使同一个并发队列中的任务区分先后的执行,而在GCD中职能区分不同队列的优先级。而gcd要完成这些则需要编写大量代码。
- 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
- 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
总的来说当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。
19. __block和__weak区别
- __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型,__block修饰基本数据类型的作用就是扩大变量的生命周期(将变量的内存搬到了堆中)
- __weak只能在ARC模式下使用,并且只能修饰对象,不能修饰基本数据类型
- __block能够在MRC下解决循环引用问题,并且__block修饰的变量能够在block中修改,并且出了block访问外部变量,此时这个外部变量的值是修改过的值。__weak是在ARC下解决循环引用的问题(__weak是__unsafe_unretained的替代品,__unsafe_unretained不会自动置空)
- __weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
__weak MyObject *weakObj = obj; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __strong MyObject *strongObj = weakObj; if (strongObj) { // do something ... } });
20. mrc和arc下block什么时候会持有外部对象
- mrc
- 栈中的block不会持有外部对象,堆中的会持有
- arc
- 栈中(匿名block作为参数传递时在栈中)和堆中的block都会持有外部对象
21. oc调用js,js调用oc
- oc调用js
// 获取页面document的title属性,那么只需要: NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"]]; // 调用页面的一个叫test的函数 [webview stringByEvaluatingJavaScriptFromString:@"test()"];
- js调用oc
// iOS里面加载一个网页用的是UIWebView,而关于页面加载的情况是通过UIWebView的一个Delegate:[UIWebViewDelegate](https://developer.apple.com/library/ios/documentation/uikit/reference/UIWebViewDelegate_Protocol/Reference/Reference.html)来通知 对应的webview的。而每次点击页面上的链接(或者是加载本页面的地址时)都会在加载前调用UIWebViewDelegate的一个方法: // 如果这个方法的返回值是YES的话就继续加载这个请求,如果是NO的话就不加载了。 所以Javascript调用Objective C代码的秘诀就在这个方法里面了。 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType: (UIWebViewNavigationType)navigationType { if (request.URL.absoluteString match urlSchemePattern) { [self executeSomeObjectiveCCode]; return NO; } else { return YES; } } request.URL.absoluteString match urlSchemePattern 这里就是如果页面的url的格式是满足某种特定格式的话就不加载那个请求,而是执行Objective C的代码。