1、引用计数是什么?
引用计数实际上是对内存地址的持有者的一个计数。
2、引用计数的工作原理是什么?
1)当我们创建(alloc)一个新的对象A时,它的引用计数从零变成1
2)当有一个指针指向对象A时,也就是某对象想通过引用保留(retain)该对象A时,引用计数+1
3)当某个指针或对象不指向该对象A时,也就是释放(release)该引用后,其引用计数-1
4)当对象A的引用计数变为0时,说明该对象不再被任何指针引用(指向),这个时候将对象A销毁,所占内存将被回收,且所有指向该对象的引用都将变成无效,系统也会将其占用的内存标记为“可重用(reuse)”
3、strong、weak、copy、assign的区别
strong表示指向并拥有该对象。其修饰的对象引用计数会增加1。该对象只要引用计数不为0则不会被销毁。当然强行将其设为nil可以销毁它。
weak表示指向但不拥有该对象。其修饰的对象引用计数不会增加。无需手动设置,该对象会自行在内存中销毁。
assign主要用于修饰基本数据类型,如NSInteger和CGFloat,这些数值主要存在于栈上。
weak 一般用来修饰对象,assign一般用来修饰基本数据类型。原因是assign修饰的对象被释放后,指针的地址依然存在,造成野指针,在堆上容易造成崩溃。而栈上的内存系统会自动处理,不会造成野指针。
copy与strong类似。不同之处是strong的复制是多个指针指向同一个地址,而copy的复制每次会在内存中拷贝一份对象,指针指向不同地址。copy一般用在修饰有可变对象类型的不可变对象上,如NSString, NSArray, NSDictionary。
Objective-C 中,基本数据类型的默认关键字是atomic, readwrite, assign;普通属性的默认关键字是atomic, readwrite, strong。
4、ARC下 __strong、__weak、__autorleaseing的修饰用法
__weak与weak基本相同。前者用于修饰变量(variable),后者用于修饰属性(property)。__weak主要用于防止block中的循环引用。
__block也用于修饰变量。它是引用修饰,所以其修饰的值是动态变化的,即可以被重新赋值的。__block用于修饰某些block内部将要修改的外部变量。
__weak和__block的使用场景几乎与block息息相关。而所谓block,就是Objective-C对于闭包的实现。闭包就是没有名字的函数,或者理解为指向函数的指针。
__autoreleaseing表示将对象放入自动释放池中,等价于MRC下的[obj autorelease]方法,在自动释放池里清理对象的时候将对象的引用计数减一,如果对象计数为零则废弃并调用对象的dealloc方法。总结:__autoreleaseing最终效果是延长了对象的生命周期,将对象的释放(对自动释放池里的对象调用[obj release])时机改为runloop结束时。主要用于函数返回对象和引用参数的传递。
5、categoty和extension的区别,categoty为什么可以添加方法不能添加变量
1)extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
但是category则完全不一样,它是在运行期决议的。就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
2)category是在运行期决定的,运行期,对象的内部布局已经确定,如果添加实例变量就会破坏类的内部布局(运行时可以为category添加实例变量)
6、main函数执行之前发生了什么
1)动态库链接库
2)ImageLoader加载可执行文件, 里边是被编译过的符号,代码等
3)runtime与+load
总结:整个事件由dyld主导,完成运行环境的初始化后,配合ImageLoader将二进制文件按格式加载到内存,动态链接依赖库,并由runtime负责加载成objc定义的结构,所有初始化工作结束后,dyld调用真正的main函数。
值得说明的是,这个过程远比写出来的要复杂,这里只提到了runtime这个分支,还有像GCD、XPC等重头的系统库初始化分支没有提及(当然,有缓存机制在,它们也不会玩命初始化),总结起来就是main函数执行之前,系统做了茫茫多的加载和初始化工作,但都被很好的隐藏了,我们无需关心。
7、运行时机制是什么?runtime哪里用到了,怎么用的?
1)运行时机制:Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数。对于OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
2)哪里用到了:
(1)获取类里面的所有成员变量。
(2)为类动态添加成员变量。(不确定能不能)
(3)动态改变类的方法实现。
(4)为类动态添加新的方法等。
3)怎么用的:
1). 使用时需要导入的头文件
2). Runtime 运行时机制,它是一套C语言库。
3). 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
// OC是动态语言,每个方法在运行时会被动态转为消息发送,即:objc_msgSend(receiver, selector)。
// [stu show]; 在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));
4). 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。
8、runloop是什么?系统哪里用到了?你自己开启的线程,怎么保护不退出?
从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
哪里用到:
1)使用runloop保证子线程的长期存活(例:在iOS开发过程中,有时候我们不希望一些花费时间比较长的操作阻塞主线程,导致界面卡顿,那么我们就会创建一个子线程,然后把这些花费时间比较长的操作放在子线程中来处理。可是当子线程中的任务执行完毕后,子线程就会被销毁掉。)
2) 定时器nstimer
例:1.我们经常会在应用中看到tableView 的header 上是一个横向ScrollView,一般我们使用NSTimer,每隔几秒切换一张图片。可是当我们滑动tableView的时候,顶部的scollView并不会切换图片,这可怎么办呢? 2.界面上除了有tableView,还有显示倒计时的Label,当我们在滑动tableView时,倒计时就停止了,这又该怎么办呢?
答:1、如果是在主线程中运行timer,想要timer在某界面有视图滚动时,依然能正常运转,那么将timer添加到RunLoop中时,就需要设置mode 为NSRunLoopCommonModes。
2、如果是在子线程中运行timer,那么将timer添加到RunLoop中后,Mode设置为NSDefaultRunLoopMode或NSRunLoopCommonModes均可,但是需要保证RunLoop在运行,且其中有任务。
网友评论