iOS面试题整理
autorelease
嵌套, 系统是怎么处理的?
在`NSAutoReleasePool`中会有一个array保存所有需要被`autorelease`的对象, 由于我们要确保在保存对象时array不会对该对象进行强引用(retainCount plus 1), 我们需要用CFMutableArrayRef
.
由于每一个线程都有自己的autoReleasePool
, 所以我们需要保存该线程中所创建的所有autoreleasepools,每创建一个autoreleasepool, 我们可以把它放入一个stack里,然后将这个stack保存起来 这里可以使用:
```objective-c
NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
NSString *key = @"com.my.thread-local.releasepool";
CFMutableArrayRef stack = threadDict[key];
if (!stack) {
stack = CFArrayCreateMutable(NULL, 0, NULL);
[threadDict setObject:(id)stack forKey:key];
}
```
当dealloc
的时候,首先遍历当前autoReleasePool里所有对象,发送release
, 然后release保存所有对象的CFMutableArray (CFRelease(_objects)
). 然后遍历保存在thread里的stack,由于我们将所有在该线程内创建的autoreleasepool都存入这个stack里,我们只需要release
所有在self
之后的出现的autoreleasepool, 然后把self
从stack里移除。由于每一个autoreleasepool都遵从这样的逻辑,这其实就是一个递归的调用。
- autorelease
对象执行autorelease方法时会将对象添加到自动释放池中
当自动释放池销毁时自动释放池中所有对象作release操作
对象执行autorelease方法后自身引用计数器不会改变,而且会返回对象本身
autorelease实际上只是把对象release的调用延迟了,对于对象的autorelease系统只是把当前对象放入了当前对应的autorelease pool中,当该pool被释放时([pool drain]),该pool中的所有对象会被调用Release,从而释放使用的内存。这个可以说是autorelease的优点,因为无需我们再关注他的引用计数,直接交给系统来做!
对于操作占用内存比较大的对象的时候不要随便使用,担心对象释放的时间太迟,造成内存高峰, 但是操作占用内存比较小的对象可以使用
- atureleasepool
自动释放池存储于内存中的栈中遵循”先进后出”原则
ARC 原理
ARC (Automatic Reference Counting), 在对象被创建时,ARC会保存一大堆关于该对象的信息:对象类型,所有的属性等等,当我们不再需要该对象的时候,ARC会帮助我们销毁该对象。
当我们alloc
init
一个对象实例时,编译器会在该实例使用完后插入objc_release
去销毁该对象。如果我们是在使用properties的时候,该property的getter会被写成:
```objective-c
- (Test *)test {
return objc_retainAutoreleaseReturnValue(_test);
}
```
可以看到,所有的properties都被retain/autorelease了, 当引用properties时,编译器还会将caller改写为:objc_retainAutoreleaseReturnValue([self test])
. 这里出现了两次的retain/autorelease,而编译器会优化的只使用一次。
###MRC 和 ARC怎么破循环引用(retain cycle)
使用__weak
或__unsafe_unretained
- weak 比 assign 多了一个功能就是当属性所指向的对象消失的时候(也就是内存引用计数为0)会自动赋值为 nil ,这样再向 weak 修饰的属性发送消息就不会导致野指针操作crash
__unsafe_unretained 和assign类似,也是会指向野指针
assign适用于基本数据类型,weak是适用于NSObject对象,并且是一个弱引用
assign其实也可以用来修饰对象。那么我们为什么不用它修饰对象呢?因为被assign修饰的对象(一般编译的时候会产生警告:Assigning retained object to unsafe property; object will be released after assignment)在释放之后,指针的地址还是存在的,也就是说指针并没有被置为nil,造成野指针。对象一般分配在堆上的某块内存,如果在后续的内存分配中,刚好分到了这块地址,程序就会崩溃掉
那为什么可以用assign修饰基本数据类型?因为基础数据类型一般分配在栈上,栈的内存会由系统自己自动处理,不会造成野指针。
- strong 与copy都会使引用计数加1,但strong是两个指针指向同一个内存地址,copy会在内存里拷贝一份对象,两个指针指向不同的内存地址
_block是用来修饰一个变量,这个变量就可以在block中被修改
-
__block:使用 __block修饰的变量在block代码块中会被retain(ARC下会retain,MRC下不会retain)
-
__weak:使用__weak修饰的变量不会在block代码块中被retain 同时,在ARC下,要避免block出现循环引用 __weak typedof(self)weakSelf = self;
-
atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。
atomic 设置成员变量的@property属性时,默认为atomic,提供多线程安全。 在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:
{lock}
if (property != newValue) {
[property release];
property = [newValue retain];
}
{unlock}
nonatomic
禁止多线程,变量保护,提高性能。
atomic是Objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iPhone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。
atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。如下列所示:
比如:@property(atomic,strong)NSMutableArray *arr;
如果一个线程循环的读数据,一个线程循环写数据,那么肯定会产生内存问题,因为这和setter、getter没有关系。如使用[self.arr objectAtIndex:index]就不是线程安全的。好的解决方案就是加锁。
据说,atomic要比nonatomic慢大约20倍。
指出访问器不是原子操作,而默认地,访问器是原子操作。这也就是说,在多线程环境下,解析的访问器提供一个对属性的安全访问,从获取器得到的返回值或者通过设置器设置的值可以一次完成,即便是别的线程也正在对其进行访问。如果你不指定 nonatomic ,在自己管理内存的环境中,解析的访问器保留并自动释放返回的值,如果指定了 nonatomic ,那么访问器只是简单地返回这个值。
- IOS-synthesize和dynamic的异同
一、SDK中描述是在声明property的时候,有2个选择
1:通过@synthesize 指令告诉编译器在编译期间产生getter/setter方法。
2:通过@dynamic指令,自己实现方法。
有些存取是在运行时动态创建的,如在CoreData的NSManagedObject类使用的某些。如果你想这些情况下,声明和使用属性,但要避免缺少方法在编译时的警告,你可以使用@dynamic动态指令,而不是@synthesize合成指令。例如
@interface Demo : NSManagedObject {
}
@property (retain) NSString* test;
@end
]
@implementation Demo
@dynamic test;
@end
###线程安全锁 线程加锁原理(信号量,临界区,自选锁)
互斥锁:用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。
临界区:每个进程中访问临界资源的那段程序称为临界区,每次只允许一个进程进入临界区,进入后不允许其他进程进入。
自旋锁:与互斥量类似,它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。用在以下情况:锁持有的时间短,而且线程并不希望在重新调度上花太多的成本。”原地打转”。
自旋锁与互斥锁的区别:线程在申请自旋锁的时候,线程不会被挂起,而是处于忙等的状态。
信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(1) 测试控制该资源的信号量。 (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。 (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。 (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
-
使用关键字:@synchronized
// 实例类person Person *person = [[Person alloc] init]; // 线程A dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(person) { [person personA]; [NSThread sleepForTimeInterval:3]; // 线程休眠3秒 } }); // 线程B dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(person) { [person personB]; } });
关键字@synchronized的使用,锁定的对象为锁的唯一标识,只有标识相同时,才满足互斥。如果线程B锁对象person改为self或其它标识,那么线程B将不会被阻塞。你是否看到@synchronized(self) ,也是对的。它可以锁任何对象,描述为@synchronized(anObj)。
-
dispatch_semaphore 信号量
dispatch_semaphore_t signal = dispatch_semaphore_create(1); dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(signal, overTime); NSLog(@"需要线程同步的操作1 开始"); sleep(2); NSLog(@"需要线程同步的操作1 结束"); dispatch_semaphore_signal(signal); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(signal, overTime); NSLog(@"需要线程同步的操作2"); dispatch_semaphore_signal(signal); });
dispatch_semaphore是GCD用来同步的一种方式,与他相关的共有三个函数,分别是dispatch_semaphore_create,dispatch_semaphore_signal,dispatch_semaphore_wait。
(1)dispatch_semaphore_create的声明为:
dispatch_semaphore_t dispatch_semaphore_create(long value);
传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
(2)dispatch_semaphore_signal的声明为:
long dispatch_semaphore_signal(dispatch_semaphore_t dsema)
这个函数会使传入的信号量dsema的值加1;
(3) dispatch_semaphore_wait的声明为:
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
这个函数会使传入的信号量dsema的值减1;这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
dispatch_semaphore 是信号量,但当信号总量设为 1 时也可以当作锁来。在没有等待情况出现时,它的性能比 pthread_mutex 还要高,但一旦有等待情况出现时,性能就会下降许多。相对于 OSSpinLock 来说,它的优势在于等待时不会消耗 CPU 资源。
如上的代码,如果超时时间overTime设置成>2,可完成同步操作。如果overTime<2的话,在线程1还没有执行完成的情况下,此时超时了,将自动执行下面的代码。
上面代码的执行结果为:
2016-06-29 20:47:52.324 SafeMultiThread[35945:579032] 需要线程同步的操作1 开始
2016-06-29 20:47:55.325 SafeMultiThread[35945:579032] 需要线程同步的操作1 结束
2016-06-29 20:47:55.326 SafeMultiThread[35945:579033] 需要线程同步的操作2
如果把超时时间设置为<2s的时候,执行的结果就是:
2016-06-30 18:53:24.049 SafeMultiThread[30834:434334] 需要线程同步的操作1 开始
2016-06-30 18:53:25.554 SafeMultiThread[30834:434332] 需要线程同步的操作2
2016-06-30 18:53:26.054 SafeMultiThread[30834:434334] 需要线程同步的操作1 结束
文/景铭巴巴(简书作者) 原文链接:http://www.jianshu.com/p/938d68ed832c 著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
-
Object-C语言 使用NSLock实现锁
// 实例类person Person *person = [[Person alloc] init]; // 创建锁 NSLock *myLock = [[NSLock alloc] init]; // 线程A dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [myLock lock]; [person personA]; [NSThread sleepForTimeInterval:5]; [myLock unlock]; }); // 线程B dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [myLock lock]; [person personB]; [myLock unlock]; });
程序运行结果:线程B会等待线程A解锁后,才会去执行线程B。如果线程B把lock和unlock方法去掉之后,则线程B不会被阻塞,这个和synchronized的一样,需要使用同样的锁对象才会互斥。
NSLock类还提供tryLock方法,意思是尝试锁定,当锁定失败时,不会阻塞进程,而是会返回NO。你也可以使用lockBeforeDate:方法,意思是在指定时间之前尝试锁定,如果在指定时间前都不能锁定,也是会返回NO。
注意:锁定(lock)和解锁(unLock)必须配对使用
-
使用NSRecursiveLock类实现递归锁
// 实例类person Person *person = [[Person alloc] init]; // 创建锁对象 NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init]; // 创建递归方法 static void (^testCode)(int); testCode = ^(int value) { [theLock tryLock]; if (value > 0) { [person personA]; [NSThread sleepForTimeInterval:1]; testCode(value - 1); } [theLock unlock]; }; //线程A dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ testCode(5); }); //线程B dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [theLock lock]; [person personB]; [theLock unlock]; });
如果我们把NSRecursiveLock类换成NSLock类,那么程序就会死锁。因为在此例子中,递归方法会造成锁被多次锁定(Lock),所以自己也被阻塞了。而使用NSRecursiveLock类,则可以避免这个问题。
- 使用NSConditionLock(条件锁)类实现锁:
使用此方法可以创建一个指定开锁的条件,只有满足条件,才能开锁。
// 实例类person
Person *person = [[Person alloc] init];
// 创建条件锁
NSConditionLock *conditionLock = [[NSConditionLock alloc] init];
// 线程A
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lock];
[person personA];
[NSThread sleepForTimeInterval:5];
[conditionLock unlockWithCondition:10];
});
// 线程B
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[conditionLock lockWhenCondition:10];
[person personB];
[conditionLock unlock];
});
线程A使用的是lock方法,因此会直接进行锁定,并且指定了只有满足10的情况下,才能成功解锁。
unlockWithCondition:方法,创建条件锁,参数传入“整型”。lockWhenCondition:方法,则为解锁,也是传入一个“整型”的参数。
NSOperation
可不可以停止
调用cancel
方法可以将NSOperationQueue
当前状态设为cancelled
,在operation运行中我们要不断的检查当前的operationQueue的状态,看isCancelled
返回是否为真,若为真,则立刻结束operation.
HTTP状态码,自己写http框架,缓存,异步,并发高性能的解决方案
根据我的经验,用过的HTTP状态码有: 200 OK, 201 Created, 302 Redirect, 304 Not Modified, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 408 Request Time out, 500 Internal Error, 503 Server Not Available。网上能找到全部的状态码,但个人觉得记住一些常用的就足够了,当碰到特殊情况要使用其他状态码,查资料就好了。
对于http框架,在ios中,通常通过封装NSURLSession来完成网络层的开发,主要注意的是网络层应独立,只完成与后台API的通信,把数据处理或其他与网络通信无关的内容分离开来。
缓存,除非有特殊要求,个人建议还是使用NSURLCache
或者NSCache
来完成。如果服务器支持的话,应该好好利用304状态码的特性,这样会节省流量,而且网络响应会快些。
异步这个我觉得不用多说了吧?NSURLSession
都是异步的。NSURLSession 采用的是 “异步阻塞” 模型,即所有请求在发出后都进入 2# 线程执行,在 2# 线程内部按照阻塞队列模式执行
对于并发高性能:需要知道的是,NSURLSession
在iOS中最多可以有4个tasks同时运行,所以应该复用所创建的NSURLSession
, 在它之上创建不同的tasks。如果像是tableview中加载图片,可以创建一个队列(queue), 如果tasks超过4个,把超过的放入这个队列中,当之前的任务完成时,检查队列中有没有等待的,如果有,把它们从队列中取出来,然后[task resume]
。
断点续传方案
可使用NSURLSession来完成. 主要通过以下的方法
```objective-c
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData;
```
如果不是使用NSURLSession, 则要麻烦一些,首先必需在暂停时记录下当前已接收的文件长度,在下一次开始时设置HTTP header里的Range
:
```objective-c
NSString *range = [NSString stringWithFormat:@"bytes=%lld-", self.receivedLength];
[request setValue:range forHTTPHeaderField:@"Range"];
```
UI, 动画优化, UITableViewCell优化
UI, 动画优化要根据实际情况以及profiling的结果来进行具体分析,
UITableViewCell的优化也是如此,但是一般的套路是profiling, 不要设置背景透明,尽量少加subviews, 减少off-screen rendering, 比如圆角图片,可以在下载图片时在后台直接画圆角在图片中。下载图片时尽量使用caching。在加载数据时,尽量减少数据处理的时间,尽量不要fetching core data 等等。
- UIView 与 CALayer CALayer是属于QuartzCore框架,而这个框架是一个跨平台的绘制框架,不处理点击事件,负责绘制,view相当于layer的容器,view上面可以有很多layer
简单的移动,旋转等动画可以用 view animation,复杂的绘图用CALayer
- 使用CALayer实现相比使用drawRect方法通常更具优势,原因1:CALayer的属性变化默认会有动画。原因2:CALayer的属性变化本身就是实时的,所以有些操作可以不必要调用setNeedsLayout方法。而使用drawRect方法实现的话,唯一的刷新方法就是通过setNeedsDisplay方法来重新刷新下自己。
本地数据库海量数据如何提高查询效率和存储效率
在ios中存储数据基本上就是plist, sqlite 和core data (NSUserDefault其实也是plist), plist建议用在存储简单而且数据量不大的情况,而且对于查询没有太多要求的。sqlite可以高效的查询和存储数据,但是缺点是:C API, 要自己做封装,而且每次都需要读写硬盘,对数据变化不敏感,要手动更新界面从而反应数据中的变化
在iOS开发中,除非有特殊需求,一般都建议使用Core Data.
如何提高查询效率:
1. 设置合适的index.
2. 优化predicate,对于string类型,尽量不要使用`==`
3. 使用`batchSize`
4. 合理使用`batchLimit`
5. 对于需要用到的relationship objects,可以使用`setRelationshipKeyPathsForPrefetching`来减少Faulting overhead.
6. 可以使用batchFaulting来减少Faulting overhead
如何提高存储效率:
1. 尽量避免在main thread中写数据
2. 不要在Core Data中保存图片,文件等数据
3. 对于删除,更新,尽量batch(批量处理)
4. 注意调用`[NSManagedObjectContext save:]`的时机,尽量是由在后台运行的NSManagedObjectContext来完成写入。
索引的缺点
- 需要空间储存索引
- 创建和维护索引需要耗费时间
- 当删除,插入和更新数据是,索引也需要进行更新,这样降低了写数据的速度。
category extension protocol
http://www.jianshu.com/p/9e827a1708c6
-
分类(Category)是OC中的特有语法,它是表示一个指向分类的结构体的指针。原则上它只能增加方法,不能增加成员(实例)变量。
Category Category 是表示一个指向分类的结构体的指针,其定义如下: typedef struct objc_category *Category; struct objc_category { char *category_name OBJC2_UNAVAILABLE; // 分类名 char *class_name OBJC2_UNAVAILABLE; // 分类所属的类 名 struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表 struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表 }
注意: 1.分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。所以< 原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性> ;
2.分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
3.可以在分类中访问原有类中.h中的属性;
4.如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法。所以同名方法调用的优先级为 分类 > 本类 > 父类。因此在开发中尽量不要覆盖原有类;
5.如果多个分类中都有和原有类中同名的方法, 那么调用该方法的时候执行谁由编译器决定;编译器会执行最后一个参与编译的分类中的方法。
分类格式:
@interface 待扩展的类(分类的名称)
@end
@implementation 待扩展的名称(分类的名称)
@end
实际代码如下:
// Programmer+Category.h文件 中
@interface Programmer (Category)
@property(nonatomic,copy) NSString *nameWithSetterGetter; //设置setter/getter方法的属性
@property(nonatomic,copy) NSString *nameWithoutSetterGetter; //不设置setter/getter方法的属性(注意是可以写在这,而且编译只会报警告,运行不报错)
- (void) programCategoryMethod; //分类方法
@end
// Programmer+Category.m文件中
那么问题来了:
为什么在分类中声明属性时,运行不会出错呢? 既然分类不让添加属性,那为什么我写了@property仍然还以编译通过呢? 接下来我们探究下分类不能添加属性的实质原因:
我们知道在一个类中用@property声明属性,编译器会自动帮我们生成_成员变量和setter/getter,但分类的指针结构体中,根本没有属性列表。所以在分类中用@property声明属性,既无法生成_成员变量也无法生成setter/getter。 因此结论是:我们可以用@property声明属性,编译和运行都会通过,只要不使用程序也不会崩溃。但如果调用了_成员变量和setter/getter方法,报错就在所难免了。
既然报错的根本原因是使用了系统没有生成的setter/getter方法,可不可以在手动添加setter/getter来避免崩溃,完成调用呢? 其实是可以的。由于OC是动态语言,方法真正的实现是通过runtime完成的,虽然系统不给我们生成setter/getter,但我们可以通过runtime手动添加setter/getter方法。那具体怎么实现呢?
#import <objc/runtime.h>
static NSString *nameWithSetterGetterKey = @"nameWithSetterGetterKey"; //定义一个key值
@implementation Programmer (Category)
//运行时实现setter方法
- (void)setNameWithSetterGetter:(NSString *)nameWithSetterGetter {
objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);
}
//运行时实现getter方法
- (NSString *)nameWithSetterGetter {
return objc_getAssociatedObject(self, &nameWithSetterGetterKey);
}
@end
但是注意,以上代码仅仅是手动实现了setter/getter方法,但调用_成员变量依然报错。
- 类扩展(Class Extension)
Extension是Category的一个特例。类扩展与分类相比只少了分类的名称,所以称之为“匿名分类”。 其实开发当中,我们几乎天天在使用。对于有些人来说像是最熟悉的陌生人。
类扩展格式:
@interface XXX ()
//私有属性
//私有方法(如果不实现,编译时会报警,Method definition for 'XXX' not found)
@end 作用: 为一个类添加额外的原来没有变量,方法和属性
一般的类扩展写到.m文件中
一般的私有属性写到.m文件中的类扩展中
类别与类扩展的区别:
①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已);
②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的( 用范围只能在自身类,而不是子类或其他地方);
③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。
- protocol
1.作用:
–定义了应该实现什么,但不关心具体怎么实现,使两个关系很远的类关联起来
•OC的协议是由@protocol声明的一组方法列表
–要求其它的类去实现,相当于@interface部分的声明
–@required标注的方法为必须实现方法
–@optional标注的方法为可以选择实现
•协议的实现又叫采用(遵守)协议
2.代码示例:
.h
@protocol NetWorkingRequestDelegate <NSObject>
@optional
//network网络请求成功
- (void)netWorkingRequest:(NetWorkingRequest *)netWorkingRequest successfulWithReceiveData:(NSData *)data;
//network网络请求失败
- (void)netWorkingRequest:(NetWorkingRequest *)netWorkingRequest didFailed:(NSError *)error;
//network下载进度
- (void)netWorkingRequest:(NetWorkingRequest *)netWorkingRequest downloadProgress:(CGFloat)progress;
@end
// 想要别做事的那个类中声明一个代理对象
@property (nonatomic,assign) id<NetWorkingRequestDelegate> delegate;
.m
if ([_delegate respondsToSelector:@selector(netWorkingRequest:downloadProgress:)]) {
[_delegate netWorkingRequest:self downloadProgress:progress];
[_delegate netWorkingRequest:self successfulWithReceiveData:_receiveData];
}
如何绘制一个三角形?
1.1 如何绘制大量三角形?
可以用core graphics 在drawrect中绘制,也可以用CALayer 绘制,推荐用CAShapLayer绘制,添加path
1.2 一定要重写drawRect吗?
不一定
1.3 如何刷新View界面? setNeedDisplay
1.4 Layer好在哪? 优点 : 灵活, 易用, 高效
CAShapeLayer相比CALayer有如下优点:
渲染快速。CAShapeLayer启用了硬件加速,绘制同一图形时会比用Core Graphics快很多。 高效利用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论多大,都不会占用太多内存。
不会被图层边界裁剪。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被裁减。
不会出现像素化。当CAShapeLayer做3D变换时,它不像一个有寄宿图的的普通Layer一样像素化。
线程和RunLoop的关系
每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。
主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
- runloop的mode作用是什么? model 主要是用来指定事件在运行循环中的优先级的,分为:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态 UITrackingRunLoopMode:ScrollView滑动时 UIInitializationRunLoopMode:启动时 NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合 苹果公开提供的 Mode 有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode) NSRunLoopCommonModes(kCFRunLoopCommonModes)
- 猜想runloop内部是如何实现的?
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑 是这样的:
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
} 或使用伪代码来展示下:
//
// http://weibo.com/luohanchenyilong/ (微博@iOS程序犭袁)
// https://github.com/ChenYilong
int main(int argc, char * argv[]) {
//程序一直运行状态
while (AppIsRunning) {
//睡眠状态,等待唤醒事件
id whoWakesMe = SleepForWakingUp();
//得到唤醒事件
id event = GetEvent(whoWakesMe);
//开始处理事件
HandleEvent(event);
}
return 0;
}
不手动指定autoreleasepool的前提下,一个autorealese对象在什么时刻释放?(比如在一个vc的viewDidLoad中创建)
分两种情况:手动干预释放时机、系统自动去释放。
手动干预释放时机–指定autoreleasepool 就是所谓的:当前作用域大括号结束时释放。 系统自动去释放–不手动指定autoreleasepool
Autorelease对象出了作用域之后,会被添加到最近一次创建的自动释放池中,并会在当前的 runloop 迭代结束时释放。
从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件。
我们都知道: 所有 autorelease 的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中。
但是如果每次都放进应用程序的 main.m 中的 autoreleasepool 中,迟早有被撑满的一刻。这个过程中必定有一个释放的动作。何时?
在一次完整的运行循环结束之前,会被销毁。
那什么时间会创建自动释放池?运行循环检测到事件并启动后,就会创建自动释放池。
子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。
自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如: 自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。
@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。
如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。
BAD_ACCESS在什么情况下出现?
访问了野指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环
NSTimer有什么需注意的以及和RunLoop的关系?
- 注意释放
- 子线程 需要加入 ruunloop,可以设置mode
NSString copy 和 NSString mutableCopy 的区别
https://www.zybuluo.com/MicroCai/note/50592
不管是集合类对象,还是非集合类对象,接收到copy和mutableCopy消息时,都遵循以下准则:
copy返回imutable对象;所以,如果对copy返回值使用mutable对象接口就会crash; mutableCopy返回mutable对象;
1、非集合类对象的copy与mutableCopy
系统非集合类对象指的是 NSString, NSNumber … 之类的对象。下面先看个非集合类immutable对象拷贝的例子
NSString *string = @"origin";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy]; 通过查看内存,可以看到 stringCopy 和 string 的地址是一样,进行了指针拷贝;而 stringMCopy 的地址和 string 不一样,进行了内容拷贝;
再看mutable对象拷贝例子
NSMutableString *string = [NSMutableString stringWithString: @"origin"];
//copy
NSString *stringCopy = [string copy];
NSMutableString *mStringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];
//change value
[mStringCopy appendString:@"mm"]; //crash
[string appendString:@" origion!"];
[stringMCopy appendString:@"!!"]; 运行以上代码,会在第7行crash,原因就是 copy 返回的对象是 immutable 对象。注释第7行后再运行,查看内存,发现 string、stringCopy、mStringCopy、stringMCopy 四个对象的内存地址都不一样,说明此时都是做内容拷贝。
综上两个例子,我们可以得出结论:
在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时内容复制;对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //深复制
[mutableObject copy] //深复制
[mutableObject mutableCopy] //深复制 2、集合类对象的copy与mutableCopy
集合类对象是指NSArray、NSDictionary、NSSet … 之类的对象。下面先看集合类immutable对象使用copy和mutableCopy的一个例子:
NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy]; 查看内容,可以看到copyArray和array的地址是一样的,而mCopyArray和array的地址是不同的。说明copy操作进行了指针拷贝,mutableCopy进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝array这个对象,array集合内部的元素仍然是指针拷贝。这和上面的非集合immutable对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看mutable对象拷贝的例子:
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy]; 查看内存,如我们所料,copyArray、mCopyArray和array的内存地址都不一样,说明copyArray、mCopyArray都对array进行了内容拷贝。同样,我们可以得出结论:
在集合类对象中,对immutable对象进行copy,是指针复制,mutableCopy是内容复制;对mutable对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制 这个代码结论和非集合类的非常相似。
###iOS7 - iOS9的区别 iOS7 - iOS9的区别
GCD指向了野指针了怎么办
会crash,不要滥用 __ block,如果我们滥用__block,可能会出现在block运行的时候,访问对象已经被释放,造成访问野指针错误。
UsersViewController* __weak weakSelf = self;
self.completion = ^(NSArray* users) {
UsersViewController* strongSelf = weakSelf;
if (strongSelf) {
strongSelf.users = users;
}
else {
// the view controller does not exist anymore
}
}
[usersViewController fetchUsers]; 使用__weak来表示对self的弱引用(代码里面不直接使用weakSelf的原因是为了在block里面保持对self的强引用,避免代码运行到一半self被释放)
用HTTP传数据,丢包严重怎么办
减小数据量
iOS中广播的种类
Notification机制的一些细节
1、在单个App中,我们通过NSNotificationCenter对象接收分发消息。若要再多个App间传递消息,则需要NSDistributedNotificationCenter对象。
2、利用NSNotificationCenter对象分发notification是同步的,即直到所有的Observer均接收到消息并处理完成后,调用消息分发的函数才会返回。若想异步处理消息分发,可以借助Notification Queues(一个为NSNotificationCenter对像配备的消息缓存)。
3、消息分发一般来说是不会跨线程的。对于NSNotificationCenter,消息只会在发出消息的线程中传递。对于NSDistributedNotificationCenter,消息只会传递到另一个App的主线程上。 若要实现消息的跨线程分发,一种方法是可以实现自定义的消息缓冲(不是系统的NSNotificationQueue对象),让其在正确的线程上接受消息,然后再像目标线程转发消息。
4、跨App的消息通过系统的转发中心转发,它会消耗大量系统资源,同时不能够保证消息的实时性。若系统中又过多的消息需要传递,IOS系统其实会丢弃一些跨App消息。同时,通过NSDistributedNotificationCenter传递的消息,其object参数仅能是NSString对象,在用户的自定义字典参数中,仅能够存储property list类型。 机制 广播 消息
runtime如何实现weak变量的自动置nil?
runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。
AFNetworking的内部实现原理?
如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 合并图片
});
UIKit的框架结构?
按位与、或、异或等运算方法
总结交换2个数的值不用临时变量的方法:
1、使用异或实现交换 我们知道异或的原理就是如果2个数中对应的位上相同为0,相异为1 即任何数异或上其本身结果不变
view plain
public void swap(int a,int b){
a=a^b;
b=b^a;
a=a^b;
}
2、使用最简单的加减法
view plain
public void swap(int a,int b){
a=a+b;
b=a-b;
a=a-b;
}
3、最想不到加法和乘法
view plain
public void swap(int a,int b){
a=b+(b=a)*0;
}