趁着开年的空闲时间,找了一些面试题写写, 水平渣,仅做参考!,欢迎指导讨论,写的不对的地方望及时提出! 原题在这里html
中级篇请看这里: iOS常见面试题(block,runtime,runloop,类结构)附参考答案ios
runtime
运行时机制能够作到纯静态语言作不到的事情:例如动态地增长、删除、替换ivar
或者方法等weak
表示该对象并不持有该delegate
对象,delegate
对象的销毁由外部控制;若是用strong
则该对象强引用delegate
,外界不能销毁delegate
对象,会致使循环引用(Retain Cycles)
delegate
是委托的意思,在OC
中表示一个类委托另外一个类实现某个方法。当一个对象接受到某个事件或者通知的时候,会向它的delegate
对象查询它是否可以响应这个事件或者通知,若是能够这个对象就会给它的delegate
对象发送一个消息(执行一个方法调用)。 datasource
字面是数据源,通常和delegate
伴生,这时数据源处理的数据就是delegate
中发送委托的类中的数据,并经过datasource
发送给接受委托的类。 Instead of being delegated control of the user interface, a data source is delegated control of data.
官网上这句话解释的比较好,咱们能够发现,delegate
控制的是UI,是上层的东西;而datasource
控制的是数据。他们本质都是回调,只是回调的对象不一样。官网原文delegate
和block
均可以实现回调传值。block
写法简练,能够直接访问上下文,代码阅读性好,适合与状态无关的操做,更加面向结果,使用过程当中须要注意避免形成循环引用。delegate
更像一个生产流水线,每一个回调方法是生产线上的一个处理步骤,一个回调的变更可能会引发另外一个回调的变更,其更加面向过程,当有多个相关方法时建议使用delegate
。@"property" = ivar + getter + setter
nonatomic
和atomic
readwrite
和readonly
assign
:修饰简单数据类型。 weak
:弱引用,多数用来修饰delegate
和outlet
。 copy
:拷贝,多用于修饰NSString
、block
等,其做为属性修饰符时是将_property
先release (_property release)
,而后拷贝参数内容(_property copy)
,建立一块新的内存地址,最后_property = property
。strong:强引用,其做为属性修饰符时是将_property
先release (_property release)
,而后将参数retain(_property retain)
,最后_property = property
。retain
:MRC下特有,等同于strong
。unsafe_unretained
:和weak
同样,惟一的区别即是,对象即便被销毁,指针也不会自动置空, 此时指针指向的是一个无用的野地址。若是使用此指针,程序会抛出 BAD_ACCESS
的异常。@synthesize
让编译器自动生成getter/setter
方法。当有自定义的存取方法时,会覆盖该方法@dynamic
告诉编译器,不自动生成getter/setter
方法。由本身实现存取方法或存取方法在运行时动态建立绑定ARC下:面试
atomic,readwrite,assign
atomic,readwrite,strong
MRC下:设计模式
atomic,readwrite,assign
atomic,readwrite,retain
@interface Person : NSObject
@property(nonatomic ,copy)NSString *cpName;
@property(nonatomic ,strong)NSString *stName;
@end
@implementation Person
@end
main(){
NSMutableString *string = [NSMutableString stringWithFormat:@"name"];
Person *person = [[Person alloc]init];
person.cpName = string;
person.stName = string;
[string appendString:@"name"];
NSLog(@"cpName:%@ stName:%@",person.cpName,person.stName);
}
复制代码
打印结果以下:
cpName:name stName:namename复制代码
至于为何要用copy, 大概是为了防止mutableString被无心中修改。缓存
NSCopying
协议。若是自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying
与 NSMutableCopying
协议。//不可变字符串
NSString *string = @"string";
NSString *cpString1 = [string copy]; //指针拷贝
NSString *mcpString1 = [string mutableCopy]; //内容拷贝,生成可变对象
NSMutableString *mstr = [string mutableCopy]; //同上
NSLog(@"%p %p %p %p",string,cpString1,mcpString1,mstr);
打印结果以下:
0x100001070 0x100001070 0x10051bbc0 0x100600e10
//可变字符串
NSMutableString *mstr1 = [[NSMutableString alloc]initWithString:@"abcde"];
NSMutableString *mstr2 = [mstr1 copy]; //内容拷贝,生成不可变字符串
NSString *cpstring2 = [mstr1 copy]; //同上
NSMutableString *mstr3 = [mstr1 mutableCopy]; //内容拷贝
NSLog(@"%p %p %p %p",mstr1,mstr2, cpstring2,mstr3);
打印结果以下:
0x102063110 0x656463626155 0x656463626155 0x102063160复制代码
NS* NSMutable*
等集合类的copy、mutableCopy
同上述同样,须要注意的是,集合里面的元素并无内容拷贝!若集合层级不少,且须要彻底内容拷贝,能够利用NSKeyedArchiver
实现。安全
防止 更新:感谢各位指出错误!网络(Retain Cycles)
在storyboard或者xib中建立的UIView,自己会被它的superView
强引用,以UILable
为例子:多线程
UIViewController -> UIView -> subView -> UILable
架构
此时控件拖线会默认为weak
属性,由于UIlable
已经被UIView
拥有,当UIViewController释放的时候,UIView释放,UILable才能够释放,因此正常状况下UILable和UIView的生命周期是同样的。设置成strong
也没什么大问题, 可是当UILable从其父视图UIView上remove
掉,UIViewController对其还有一个strong
强引用,UILable没法释放,这时就比较尴尬了...并发
nonatomic
:表示非原子性,不安全,可是效率高。atomic
:表示原子性,安全,可是效率较低。atomic
:经过锁定机制来确保其原子性,但只是读/写安全,不能绝对保证线程的安全,当多线程同时访问的时候,会形成线程不安全。可使用线程锁来保证线程的安全。UICollectionViewLayout
是为向UICollectionView
提供布局信息的类,包括cell
的布局信息等。UICollectionView
的自定义布局能够分为三种方式:thread
performSelector
函数- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;复制代码
dispatch_async(otherQueue, ^{
// dosth
});
复制代码
[otherQueue addOperationWithBlock:^{
// dosth
}];复制代码
group
: 当全部任务都执行完成以后,才执行dispatch_group_notify
中的任务 dosthNotify。- (void)gcdGroup{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// dosth1;
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// dosth2;
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// dosthNotify;
});
}复制代码
barrier
: 栅栏方法,当栅栏前一组操做执行完以后,才能开始执行后一组方法- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// dosth1;
});
dispatch_async(queue, ^{
// dosth2;
});
dispatch_barrier_async(queue, ^{
// doBarrier;
});
dispatch_async(queue, ^{
// dosth4;
});
dispatch_async(queue, ^{
// dosth5;
});
}复制代码
dispatch_semaphore
。dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
// dosth1
// 使信号量+1并返回
dispatch_semaphore_signal(semaphore);
});
// 若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者通过输入的时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// dosth2 ,只有当dosth1执行完,信号量+1以后,才会执行这里
复制代码
// 当应用程序启动时(不包括已在后台的状况下转到前台),调用此回调。launchOptions是启动参数,假如用户经过点击push通知启动的应用,这个参数里会存储一些push通知的信息
– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions NS_AVAILABLE_IOS(3_0);
– (void)applicationDidBecomeActive:(UIApplication *)application;
//应用即将从前台状态转入后台
- (void)applicationWillResignActive:(UIApplication *)application;
– (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
//从后台到前台调用了:
– (void)applicationWillEnterForeground:(UIApplication *)application NS_AVAILABLE_IOS(4_0);
– (void)applicationDidBecomeActive:(UIApplication *)application;
复制代码
App启动过程以下:
NSObject *object = [[NSObject alloc] init];复制代码
类方法+ alloc
,其根据要建立的实例对象对应的类来分配足够的内存空间。除了分配内存空间,其实+ alloc
方法还作了其余事情,包括将对象的引用计数记为1,将对象的isa指针指向对应的运行时类对象,以及将对象的成员变量置为对应的0值(0、nil、NULL)。可是+ alloc
方法返回的对象仍是不可用的,在以后完成初始化方法的调用后,对象的建立工做才算完成。初始化方法会设置对象的成员变量为一个正确的合理的值,以及获取一些其余额外的资源。
全部对象都是要初始化的,并且不少状况下,对象在初始化时是须要接收额外的参数,这就可能会提供多个初始化方法。
- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;复制代码
根据规范,一般选择一个接收参数最多的初始化方法做为指定初始化方法,真正的数据分配和其余相关初始化操做在这个方法中完成。而其余的初始化方法则做为便捷初始化方法去调用这个指定初始化方法。这样当实现改变时,只要修改指定初始化方法就能够了。便捷初始化方法接收的参数更少,它会在内部调用指定初始化方法时,直接设置未接收参数的默认值。便捷初始化方法也能够不直接调用指定初始化方法,它能够调用其余便捷初始化方法,但无论调用几层,最终是要调用到指定初始化方法的,由于真正的实现操做是在指定初始化方法中完成的。全部初始化方法统一以- init开始。如上例代码所示,- initWithTimeIntervalSinceReferenceDate
方法是一个指定初始化方法,而其余初始化方法最终是要调用它的。
当子类继承父类后实现了新的指定初始化方法,此时若是调用父类中的指定初始化方法则没法调用到子类新实现的初始化逻辑,因此子类同时还要重写父类的指定初始化方法,将其变为一个便捷初始化方法,最终去调用子类本身的指定初始化方法。而为了保证父类初始化逻辑的执行,在子类指定初始化方法中,首先要经过关键字super调用父类的指定初始化方法。 详见正确编写Designated Initializer的几个原则
若是父类也实现了协议,首先要调用父类的- initWithCoder:方法,若是父类没有实现,则调用父类的指定初始化方法。
当在接口中指定初始化方法的后面加上该宏,编译器就会检查咱们实现的初始化调用链是否符合规则,并提示相应的警告。
- (instancetype)init NS_DESIGNATED_INITIALIZER;复制代码