一、说一下对自动释放池的理解
ARC中,主线程默认开启一个runloop,runloop自动创建一个autoreleasepool,autorelease对象会自动被加入autoreleasepool中,一次runloop后清空自动释放池,用__autoreleasing修饰符修饰,或类方法创建会自动加入autoreleasepool中,添加到最近的autoreleasepool中。autoreleasepool是被一个栈区管理,当需要释放一个autoreleasepool时,会根据其中的哨兵对象(一个nil指针),对这个对象所在的autoreleasepool中所有对象引用计数-1,清空所有对象的内存地址,然后next指针会指向一个合适的位置,next根据表示begin()和end()来约束autoreleasepool是否存满对象,是否需要创建新的autoreleasepool存储新的autorelease对象。
二、说一下都有那些修饰词并阐述作用
nonatomic、atomic、assign、weak、retain、strong、copy、readonly、readwrite
nonatomic、atomic区别:
atomic:设置成员变量的@property属性时,默认是atomic,提供多线程安全,但不是绝对安全。
nonatomic:禁止多线程,变量保护,提高安全性。
atomic是OC使用的一种线程保护技术,防止在一个线程上的任务未完成时,被其他线程读取,造成数据错误,这种机制是耗费资源的。一般不需要多线程间通讯编程,可以使用nonatomic。
Assign、 weak的区别
引用计数都不会➕1。
assign 修饰基本数据类型,例如int float,在MRC环境下不会自动置为nil。
weak 修饰的变量会在栈中自动清空,赋值为nil。
eg:delegate在MRC环境下,用assign修饰,是为了不造成循环引用,需要在-dealloc方法中手动设置self.delegate=nil;以免造成野指针。而在ARC环境下,直接用weak修饰就可以了。
原理:一个对象,所有指向它的weak指针都存放在一个hash表中,key是对象所占内存地址,value是一个weak指针数组,当对象被销毁的时候,系统会遍历这个数组中的所有weak指针,并将它们都会指向nil。
retain、strong、copy的区别
retain 是MRC下使用的修饰词,在ARC下可用strong替代。
当修饰不可变对象时,三者的作用是一样的,对象不会跟着原本的引用对象的改变而改变,因为是浅拷贝的指针,当源数值发生改变,就会产生新的指针地址。
当修饰可变对象的时候,retain和strong是一样的,对象会跟着原本引用对象的数值改变而改变,因为原对象是可变的,不会出现新的内存。而copy的对象不会跟着改变,因为copy是深拷贝的新的内容,开辟了新的存储空间和指针。
readonly、readwrite的区别
readonly 这个属性只生成了getter方法,没有setter方法,只能读。
readwrite 这个属性getter setter方法都有,可读可改。
三、ARC和MRC的区别
ARC下,内存交给系统自动管理,系统会在编译的时候自动插入retain/release,而MRC下,需要手动管理对象的引用计数,需要手动发送release或者autorelease消息。
四、简述category和extension的区别
category 运行时决定,只能添加方法,不能添加实例变量(可以添加属性)。
extension 编译时决定,不仅可以添加方法,还可以添加实例变量和属性,默认是私有的。但是extension没有独立的实现部分。
category细聊:
1)分类的方法名如果和原类中方法名冲突,优先分类。
2)Category的方法可以被子类继承。
3)利用runtime倒序遍历方法列表,可以调用被覆盖的原类的方法。
4)一个类的两个category,如果存在方法名相同的情况,根据buildPhases->Compile Sources里面的从上至下顺序编译情况去调用,即后编译的会被直接调用。
5)扩展一个类的方式,category比继承要好,继承需要定义子类,类目不需要定义子类来增加现有类的方法,且不会影响其他类和原有类的关系。
五、说一下对内存管理的理解
内存管理机制
1、引用计数:当我们创建一个对象时,其引用计数会➕1,系统会分配内存给到这个对象。每次引用这个对象,它的引用计数都会➕1。当指针不再指向这个对象,那么它的引用计数会➖1,直到引用计数为0,它的内存会释放掉,对象被销毁。在ARC环境下,对象会被系统释放,不需要手动释放,避免了内存泄漏和野指针的出现。
2、循环引用:当几个对象互相强引用时,会造成内存泄漏,导致程序崩溃。例如,delegate如果用strong修饰,那么就会造成两个类之间的互相强引用,此时引入弱引用概念,即用weak修饰delegate,不会使delegate的引用计数➕1,当delegate对象销毁时,指针会指向nil。
3、内存泄漏:没有及时释放掉内存,例如计时器没有释放。
实际开发中如何避免内存泄漏
1、防止循环引用,使用weak弱引用,例如修饰delegate。
2、及时释放对象内存,例如创建计时器NSTimer,在计时结束的时候,一定要调用invalidate方法,将timer置为nil。
3、使用懒加载的方式创建对象,在使用对象的时候再进行实例化,还可以同时加入一些处理逻辑,这样利于内存管理。
六、循环引用
举例:
1、UIViewController中创建UITableView,当实例一个tableView时,控制器的view对tableView进行了强引用,此时tableView的delegate又引用了控制器,这就形成了循环引用,此时需要用weak去修饰delegate。
2、block内部调用了外部变量(控制器本身或者控制器的属性)
此时需要用__weak typeof(self) weakSelf = self;来进行对控制器的弱引用,然后在block内部去调用。
3、关于定时器
使用定时器时,要主动调用invalidated,一旦和控制器形成循环引用,控制器不会被自动销毁,不会调用dealloc方法,如果invalidated放在dealloc中,是不会被执行的。那么我们可以通过拦截点击返回按钮的方法,在该方法中主动调用invalidated方法,或者写在ViewWillDisappear方法中。请记住,创建一个NSTimer对象,会形成一个runloop,不止控制器会对该timer持有,iOS系统也会对其强引用,所以不要以为设置timer=nil就可以释放timer了,只有调用invalidated才会让iOS系统释放对timer的强引用。所以,[timer invalidated];timer=nil;要同时书写,这是一种良好的代码习惯和规范。
七、关于block
1、block是一个OC的对象,它封装了一段代码,这段代码可以在任何时候执行。Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。可以嵌套定义,可以定义在方法内部和外部。
2、block的循环引用:在使用block的地方,self.block,self持有了block,在block代码块里处理逻辑,如果用到了self.xx,那么block又会持有self,这样当你离开界面的时候,并不会走self的dealloc析构函数,self不会释放,这是因为self和block的循环引用,导致了内存泄漏。为了避免这种情况,要使用__weak 在block外面修饰一下self,在block代码块里使用weakself处理逻辑,weakself只是指向了self,它在一个弱引用表里,self的引用计数没有+1。
3、按照上面的方式调用self的属性,有可能会失败,原因是,weakself会因为析构函数被释放,那么weakself.xx就会是nil。为了避免这种情况,在block代码块里,需要用__strong修饰一下weakself,那么相当于一个临时的strong指针指向了self,当逻辑处理完毕,strong销毁,不会产生内存泄漏问题。
八、关于WKWebview
1、相较于UIWebview,WKWebview占内存小,加载快,增加了加载进度条。
2、出现白屏的原因和解决办法:
① 网页内存过大会白屏,需要在webViewWebContentProcessDidTerminate方法中调用[webview reload];方法。
② url中有中文字符,需要后台配合修改;
③ h5中使用了第三方组件,加载失败,先是白屏。一般正式环境下会恢复正常。3、关于cookie
为了解决加载H5界面无法携带原生界面登录用户信息问题,除了在url上拼接用户uid等信息的方法,还可以在请求的时候,拦截请求头,将这些信息拼接起来,使用request的set value方法,将这些信息传递过去。4、原生界面和H5界面的交互:
JS调用OC方法:WKWebview中有一个WKScriptMessageHandler协议,想要实现交互,需要定义一个方法名,使用[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"methodname"]方法设置,并通过userContentController:didReceiveScriptMessage方法,获取对应名称的数据体进行解析。
OC调用JS:通过evaluateJavaScript方法调用JS方法,需要在HTML文件中定义方法名,在原生这里调用。5、获取网页高度
① 在webviewdidFinishNavigation中获取高度。
② 执行evaluateJavaScript方法获取网页宽高,计算比例,再根据手机界面设置的宽高去设置。
③ 在HTML标签中加入特殊字符去接收高度,OC这边通过evaluateJavaScript方法获取。
九、关于多线程
1、关于多线程都有哪些了解?
NSThread NSOperation GCD 当我们需要大量耗时操作时候,可以在多线程中去执行任务,避免主线程阻塞。2、NSThread使用:
两种方法:
① 创建实例对象:
NSThread*thread = [[NSThread alloc] initWithTarget:self selector:@selector(upload) object:@"参数"];
[thread start];
② 类方法创建:
[NSThread detachNewThreadSelector:@selector(upload) toTarget:@"" withObject:@""];
③ NSThread可以设置优先级 取值范围 0.0 ~ 1.0 之间 最高是1.0 默认优先级是0.5
举例:thread.threadPriority = 1.0;
④ 强制结束任务调用exit方法。3、CGD使用
CGD会把任务从队列中取出,自动放到对应的子线程中执行,不需要程序员去管理线程,遵循FIFO原则,先进先出,后进后出。
如何使用:
① 创建队列
//创建一个并行队列
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
//创建一个串行队列
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
//获取全局并发队列
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//获取主队列(一个串行队列,放在主队列的任务都会在主线程执行)
dispatch_queue_t queue4 = dispatch_get_main_queue();
② 执行任务
dispatch_async(queue, ^{ 异步操作 });
dispatch_sync(queue, ^{ 同步操作 });
总结:
同一串行队列中,同步+同步 死锁。同步+异步 正常。异步+同步 死锁。异步+异步 正常。
同一并行队列中,都正常。
例如,需要请求1结束后再执行请求2,需要用到dispatch_group_t。
① enter leave方法
② 信号量方法
4、NSOperation的使用
基于GCD的封装,更加面向对象。如果是简单的两个网络请求,请求A优先级大于请求B,就可以用NSOperation。使用addDependency方法,来设置先后顺序。如果同时下载多个图片,可以使用NSOperation,设置maxConcurrentOperationCount最大并发数,然后使用NSBlockOperation创建对象去执行任务。5、其他应用:
① 调用NSObject方法,[self perform.....]
② 延时处理 dispatch_after
③ 使用定时器
④ 创建单例,代码只会执行一次 dispatch_once_t onceToken;
⑤ 栅栏函数,前面的任务执行完毕后才会执行后面的任务。
dispatch_barrier_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
十、手势传递
1、当子view大小超出了父view,怎么获取并响应点击事件?
重新父view的hintTest方法,遍历父view上所有的子view,判断这个point是否在子view上,如果在,返回子view去响应事件,如果都没有,返回父view去响应事件。
2、扩大UIButton的点击范围
① 创建分类(Category)并重写UIButton的pointInside:withEvent:方法
②创建分类(Category)并重写UIButton的hitTest::withEvent:方法
十一、离屏渲染
设置了layer会有离屏渲染
①、②、③、④、⑤、⑥、⑦、⑧、⑨、⑩ ⑪、⑫、⑬、⑭、⑮、⑯、⑰、⑱、⑲、⑳