一、语言基础
1、#import和#include,@class有什么区别?
import不会重复引入头文件
@class是向前声明,告诉编译器有这么一个类的定义,但是暂时不引入,保证编译可以通过,直到运行时采取查看类的实现文件。这样也可以避免重复引用甚至循环引用等问题。
2、#import<>和#import“”有什么区别?
import<>只会去系统目录下寻找
import“”会先去用户目录下寻找,如果找不到,会继续去系统目录下寻找
3、Objective-C中堆和栈有什么区别?
堆一帮用来存放oc对象,需要手动申请和释放内存,ARC环境下由编译器管理,不需要手动管理。
栈由系统自动分配,一般存放非oc对象的基本类型数据,例如int,float,不需要手动管理。
4、self和super有什么区别?为什么要使用[super init]?
self是一个类的隐藏参数,指向当前实例对象,super是编译器标识符,在运行时,self和super指向同一个实例对象。
区别在于:当self调用方法时,会优先在当前类的方法列表中寻找方法,当使用super调用方法时,会优先从父类的方法列表中寻找方法。
子类初始化时,调用[super init]方法,主要是为了避免造成未知的错误,如果父类初始化不成功,返回nil,可以根据父类初始化结果,做响应容错处理。
二、属性和实例变量
1、属性和实例变量的区别是什么?
使用实例变量的方式声明的变量,只能在类内部访问,类外无法访问,而且不能使用“.”语法访问变量,如果需要对外提供访问能力,需要手动实现set和get方法。
使用属性的方式声明的变量,编译器会自动生成set和get方法,也就可以使用“.”语法访问变量。如果属性是声明在h文件中,类内部和外部都可以访问这个变量,如果是声明在m文件中,则只能当前类内部访问,外部包括子类都无法访问。
2、修饰属性的关键字有哪些,分别有什么作用?
修饰属性的关键字有以下几个
(1)原子性:nonatomic,atomic
(2)读写控制:readonly,readwrite,getter,setter
(3)内存管理:assign,retain,weak,strong,copy,__unsafe_unretained
原子性(nonatomic,atomic)
在多线程中,同一个变量被多个线程同时访问,会造成数据污染,因此为了安全,Objective-C中默认属性为atomic,即对set方法加锁,保证多线程下数据安全。同样的也会为此承担一部分的资源开销。应用中不是特殊情况(多线程通信)一般属性声明为nonatomic,这样可以提高访问时的性能。
读写控制(readonly,readwrite,getter,setter)
readonly标识只读,编译器只提供get方法
readwrite标识读写,编译器提供set和get方法
getter和setter用来指定存取方法
内存管理(assign,retain,weak,strong,copy)
assign可以修饰oc对象和和oc对象的基础类型,标识简单赋值,指针弱引用,不会对引用计数+1
weak修饰弱引用,只能修饰oc对象,和assign相同,不同的是,weak修饰的变量在销毁后,自动将指针置为nil,避免野指针。
retain修饰oc对象,为了持有对象,声明强引用,引用计数+1。
strong和reatin类似,在ARC中,用strong代替retain。
copy建立一个和原有对象内容相同且引用计数为1的新的对象。
3、什么时候使用weak关键字?和assign有什么区别?
(1)ARC中为了避免循环引用,可以让其中一个对象使用weak修饰,常见“delegate,block”
(2)Interface Builder中IBOutlet修饰的控件一般也使用weak
区别:weak只能修饰oc对象,并且在销毁后自动将指针置为nil,避免野指针,而assign可以修饰oc对象和非oc对象的基础类型数据,当对象销毁后,不会将指针置为nil,形成野指针,再次调用时会导致崩溃。
4、nonatomic和atomic有什么区别?atomic是绝对的线程安全么?如果不是该如何实现?
区别:对属性的存取操作是否添加加锁操作,来保证多线程下数据存取的安全性。在执行效率上nonatomic比atomic存取效率更高。
绝对线程安全么?不是绝对安全,可以保证大部分情况下数据读取的一致性,比如在多线程下,两个线程都对属性进行循环+1操作,导致对属性的操作,变为读取,+1,存储的三个操作,而atomic只能保证读取和存储操作,无法保证+1操作时的原子性。
如何保证绝对线程的安全?其实只要给线程中执行的代码块加锁就能实现多线程访问的安全。
三、实例方法和类方法
1、什么是类工厂方法?
简单的说就是用来快速创建对象的的类方法,可以直接返回一个初始化好的对象。UIKit中最经典的就是UIButton类中的buttonWithType:类工厂方法。
特征:
(1)一定是类方法
(2)返回值一定是id/instancetype类型
(3)规范的类方法名,一般以小写类名为开头
2、OC中有方法重载么?
没有,因为函数语法定义的问题,OC编译器不允许定义函数名相同,参数个数相同,但是返回类型和参数类型不同的方法。
四、数据类型和运算符
1、OC中NSInteger和int基础数据类型有什么区别?
NSInteger是long 和 int 的别名,在预编译阶段,NSInteger会根据系统是32位的还是64位的来动态确定是int类型还是long类型,NSInteger也是官方推荐使用的基本数据类型。
2、instancetype和id有什么区别?
instancetype和id都可以指向任意OC对象,不同的是:
(1)id可以作为返回值,形参,变量,并将对象的确定延迟到运行时。
(2)instancetype只能作为返回值,并且在预编译时已经确定类型。
五、继承和多态
1、OC中有多继承么?
OC中没有多继承,但是可以通过组合,协议,分类实现类似多继承。
2、OC为什么不能实现多继承?
因为OC的消息机制,名字查找发生在运行时,而不是编译时,不能解决多个基类的二义性。
六、分类和扩展
1、什么是Category?作用什么?
Category是OC在不破坏已有类的情况下,为该类添加新方法的一种方式。
作用:
(1)对现有类添加方法
(2)在没有源代码的情况下,对类进行扩展
(3)将类中方法的实现分散到不同文件中,减小单个文件体积
(4)可以按需动态加载不同的Category
特性:
(1)重名的情况下,类别中的方法优先级高于原类中的方法
(2)不能直接添加成员变量(可以使用runtime实现,较为复杂)
(3)同一个类的不同类别声明了相同方法,调用时不确定
(4)可以添加属性,但是不会生产set和get 方法,需要通过关联对象实现
2、Category的实现原理是什么?为什么只能添加方法,不能添加属性?
(1)先看一下Category在runtime源码中,Categroy定义为一个结构体
//分类的定义,结构体
typedef struct category_t {
const char *name;//分类名
classref_t cls;//扩展的类
struct method_list_t *instanceMethods;//实例方法列表
struct method_list_t *classMethods;//类方法列表
struct protocol_list_t *protocols;//协议列表
struct property_list_t *instanceProperties;//属性列表
} category_t;
从Category的定义里可以看出,分类可以添加实例方法,类方法,实现协议,添加属性,而不能添加实例变量,因为没有存储实例变量列表的指针。
Category是如何加载的?
(1)_objc_init runtime入口函数,初始化
(2)map_images 加锁
(3)map_images_nolock 完成类的注册,初始化,及load方法加载
(4)_read_images 完成类的加载,协议的加载,类别的加载等工作
(5)remethodizeClass 这一步非常关键,它将类别绑定到目标类上
(6)attachCategories 这是最重要的一步,将类别中的方法,属性绑定到目标类
(7)attachLists 将目标类中的方法和分类中的方法放到一个列表中
_read_images具体作用:将类别和目标类绑定,并重建目标类的方法列表
attachCategories具体作用:分配一个新的列表空间,用来存放类别的实例方法,类方法,协议方法,交给attachLists处理
attachLists具体作用:创建一个新的列表,与类别中传过来的列表融合在一起,变成新的方法列表。
如果有重名的方法,类别中的方法位置更靠前,类方法位置靠后,也就解释了为什么类别的方法优先级要高于目标类的方法。
需要注意的是:尽管Category定义中有存放属性的变量,但是源码实现中,并不会为属性生成set和get方法,所以需要借助关联对象,来手动实现。
3、关联对象是怎么实现的?
翻一下runtime的源码,在objc-references.mm文件中有个方法_object_set_associative_reference:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
_class_setInstancesHaveAssociatedObjects(_object_getClass(object));
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
我们可以看到所有的关联对象都由AssociationsManager管理,而AssociationsManager定义如下:
class AssociationsManager {
static OSSpinLock _lock;
static AssociationsHashMap *_map; // associative references: object pointer -> PtrPtrHashMap.
public:
AssociationsManager() { OSSpinLockLock(&_lock); }
~AssociationsManager() { OSSpinLockUnlock(&_lock); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
而在对象的销毁逻辑里面,见objc-runtime-new.mm:
void *objc_destructInstance(id obj)
{
if (obj) {
Class isa_gen = _object_getClass(obj);
class_t *isa = newcls(isa_gen);
// Read all of the flags at once for performance.
bool cxx = hasCxxStructors(isa);
bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (!UseGC) objc_clear_deallocating(obj);
}
return obj;
}
runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。
4、Category中有+load方法么?什么时候调用的?load方法可以继承么?
load方法是不可以继承的,因为load方法不是通过消息传递(_objc_msgSend)方式调用的,是直接通过函数指针调用的。因此load方法不存在类的层级遍历。
Category中也有load方法,和类中load方法不同的是,它不是简单的继承或者覆盖,而是独立的load方法。和类中的load方法没有关系。
在runtime加载时调用load方法,调用顺序:父类,子类,分类。
六、Block
1、Block的原理是什么?使用的时候需要注意什么?
Block是闭包,可以作为参数,变量,返回值使用。在iOS中广泛应用,比如GCD,动画,循环。
通过下面一段代码来分析一下Block原理:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;//声明一个变量,存在栈上
void(^testBlock)(int i) = ^(int i){
NSLog(@"a : %d",a);
NSLog(@"i : %d",i);
};
a = 20;
testBlock(a);//调用block
}
return 0;
}
打印结果为:a = 10,i=20
通过结果可以发现,block具有保存变量瞬时值的特性,记录了a修改之前的值。
通过Clang来看一下底层C语言的实现:
clang -rewrite-objc main.m
以下为main函数C语言实现
//定义了block的结构体
struct __main_block_impl_0 {
struct __block_impl impl;//block的结构体
struct __main_block_desc_0* Desc;//block描述对象,
int a;//存放变量的a的值
//构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block大括号对应的实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
int a = __cself->a; // bound by copy//a的值使用的是结构体中指针指向的值,在构造时已经确定为10,而不是使用的是block外部变量a的地址,
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_3r7qfrfs39729zxpgrbp45z40000gp_T_main_b630e6_mi_0,a);//打印输出时使用的也是内部变量a,并不是外部变量a或者使用a的指针,所以为10.
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_3r7qfrfs39729zxpgrbp45z40000gp_T_main_b630e6_mi_1,i);//i的值为参数的值,这里为20
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void(*testBlock)(int i) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
a = 20;
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, a);
}
return 0;
}
这里可以看出,__main_block_impl_0定义有一个成员变量a,用来保存构造函数中传入的a的值,相当于构造时复制了一份a的值,block执行时,使用的是block内部成员变量a的值,而不是block外部的a的值,
仔细观察可以发现:struct __main_block_impl_0 其实是 对 struct __block_impl impl的封装
struct __block_impl impl的定义:
struct __block_impl {
void *isa; 类似对象的指针
int Flags;
int Reserved;
void *FuncPtr;
}
isa类似对象的指针,指向block保存的区域:
(1)_NSConcreteStackBlock:栈区存储的block
(2)_NSConcreteMallocBlock:堆区存储的block
(1)_NSConcreteGlobalBlock:全局区存储的block
栈block 在函数的作用域结束后,释放
堆block在retainCount为0时,释放
全局block和程序的生命周期相同
FuncPtr指针,指向block的执行函数,即执行大括号内的代码逻辑。
总结一下:Block底层是由结构体实现的,block的调用是由函数实现的
Block使用注意事项:
在block中使用自动变量,无法在block中修改自动变量的值,因为在构造过程中,a的值已经确定了。
如果要修改自动变量的值,需要在自动变量前加上__block修饰,全局变量和静态变量不需要加修饰也可以在block中修改他们。
__block 修饰的自动变量,为何能够修改?改动一下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;//声明一个变量,存在栈上
void(^testBlock)(int i) = ^(int i){
NSLog(@"a : %d",a);
NSLog(@"i : %d",i);
a++;
};
a = 20;
testBlock(a);//调用block
NSLog(@"a = %d",21);
}
return 0;
}
结果输出为:a = 20,i = 20 ,a = 21;
重复刚才的clang命令,会发现,block结构体的构造函数传入的是b的地址,也就是说不加修饰的话,是值传递,存在拷贝,而加了修饰的话,是指针传递,所以block内部修改变量的话,外部也会修改.
最后再说一下block从栈复制到堆的几种情况:
(1)手动调用block的copy方法;
(2)将block赋给__strong修饰的对象,同时block中还要引用外部变量时
(3)将block作为函数返回值时
(4)向Cocoa框架含有usingBlock的方法或者GCD的API传递block参数时
2、什么是Block循环引用?如何解决循环引用?
Block中直接使用外部强指针会导致循环引用。
解决办法:
(1)对当前对象弱引用
(2)使用完block后,手动将一方置为nil
(3)将外部对象作为参数传入block
(4)使用Weak-Strong Dance方式来解决(也是使用最多的一种方式)
其他
1、OC中load方法和initialize的方法有什么区别?
(1)load方法不能继承,是通过函数指针调用,runtime运行时调用,较早
(2)initialize方法可以继承,是通过消息传递调用的,第一次收到消息时调用,较晚
2、copy方法是深复制还是浅复制?
浅复制是复制对象的指针,深复制是复制对象内容,生成新的对象。
copy不管是深复制还是浅复制,复制出的对象都是不可变的。
mutablCopy复制的出的都是可变的。
按照容器和非容器类型,可变和不可变类型分。有如下几种情况。
(1)容器-不可变: NSArray (copy 浅拷贝,mutableCopy深拷贝)
(2)容器-可变 :NSMutableArray (copy 深拷贝,mutableCopy深拷贝)
(3)非容器-不可变 :NSString(copy 浅拷贝,mutableCopy深拷贝)
(4)非容器-可变:NSMutableString (copy 深拷贝,mutableCopy深拷贝)
copy对可变对象,为深复制,原对象引用计数不+1,对于不可变对象是浅复制,引用计数+1,始终返回不可变对象。
mutableCopy始终是深复制,原对象引用计数不+1,始终返回可变对象。
网友评论