题库整理(一)

1.atomic和nonatomic的区别?atomic一定是线程安全的吗?atomic如何实现atomic?

  • atomic是原子操作(原子性是指事务的一个完整操作,操作成功就提交,否则回滚),在iOS中getter/setter函数是原子操作,如果多线程同时调用,不会出现某一个线程执行完setter所有语句之前,其他线程开始执行setter,相当于给setter函数加了锁,确保原子性操作,但是这样做的弊端就是很消耗性能。

  • nonatomic是非原子操作,一般在不在并发的时候使用,这样做的好处是会提高性能。在oc中通常对对象类型都声明为非原子性。

  • 在多线程中,atomic只保证getter、setter方法安全,并不保证其它操作,例如字符串拼接,数组移除元素等,并没有执行getter和setter方法,顾不是绝对安全的。

2.内存区域的划分和分配,内存分配方式有哪些 ?
内存分配方式有三种:

  • 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  • 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

3.weak原理

  • Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

weak 的实现原理可以概括一下三步:

  • 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
  • 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
  • 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

4.谈谈category和extension区别

  • extension看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。(详见2
  • 但是category则完全不一样,它是在运行期决议的。就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而category是无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的)。
    5.系统如何底层实现category
    在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义),它包含了
  • 类的名字(name)
  • 类(cls)
  • category中所有给类添加的实例方法的列表(instanceMethods)
  • category中所有添加的类方法的列表(classMethods)
  • category实现的所有协议的列表(protocols)
  • category中添加的所有属性(instanceProperties)
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;
  • Objective-C的运行是依赖OC的runtime的,而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的。category被附加到类上面是在map_images的时候发生的,在new-ABI的标准下,_objc_init里面的调用的map_images最终会调用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的结尾:

1.把category的实例方法、协议以及属性添加到类上
2.把category的类方法和协议添加到类的metaclass上

  • addUnattachedCategoryForClass只是把类和category做一个关联映射,而remethodizeClass才是真正去处理添加事宜的功臣.而对于添加类的实例方法而言,又会去调用attachCategoryMethods这个方法,我们去看下attachCategoryMethods:attachCategoryMethods做的工作相对比较简单,它只是把所有category的实例方法列表拼成了一个大的实例方法列表,然后转交给了attachMethodLists方法.
    需要注意的有两点:

1、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

6.谈谈对自动释放池的理解

  • autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次release操作-

注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放

  • autorelease方法会返回对象本身,且调用完autorelease方法后,对象的计数器不变

7.autorelease的原理实质上是什么?多层自动释放池嵌套的对象在哪一层释放

  • autorelease实际上只是把对release的调用延迟了,对于每一个autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用release。
  • 对于多层自动释放池嵌套使用,由于自动释放池是以栈的形式存在,栈只有一个入口, 所以调用autorelease会将对象放到栈顶的自动释放池。
@autoreleasepool { // 栈底自动释放池
    @autoreleasepool {
        @autoreleasepool { // 栈顶自动释放池
            Person *p = [[[Person alloc] init] autorelease];
        }
        Person *p = [[[Person alloc] init] autorelease];
    }
}

8.对于block,理解,mrc和arc下有什么区别,使用注意事项

  • block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。

  • block对变量的捕获规则:

1.静态存储区的变量:例如全局变量、方法中的static变量引用,可修改。
2.block接受的参数
传值,可修改,和一般函数的参数相同。
3.栈变量 (被捕获的上下文变量)
const,不可修改。 当block被copy后,block会对 id类型的变量产生强引用。
每次执行block时,捕获到的变量都是最初的值。
4.栈变量 (有__block前缀)
引用,可以修改。如果时id类型则不会被block retain,必须手动处理其内存管理。
如果该类型是C类型变量,block被copy到heap后,该值也会被挪动到heap

  • 使用注意事项:

注意1.内存
Block_copy()和Block_release()必须一一匹配,否则会内存泄漏或crash。__block这个修饰词会将原本的简单类型转化为较大的struct,这会给内存、调用带来额外的开销,使用时需要注意。

注意2.ARC
在开启ARC后,block的内存会比较微妙。ARC会自动处理block的内存,不用手动copy/release。但是,和非ARC的情况有所不同:

void (^aBlock)(void);
aBlock = ^{ printf("ok"); };

block是对象,所以这个aBlock默认是有__strong修饰符的,即aBlock对该block有strong references。即aBlock在被赋值的那一刻,这个block会被copy。所以,ARC开启后,所能接触到的block基本都是在堆上的。

注意3.循环引用
当block被copy之后(如开启了ARC、或把block放入dispatch queue),该block对它捕获的对象产生strong references (非ARC下是retain),所以有时需要避免block copy后产生的循环引用。如果用self引用了block,block又捕获了self,这样就会有循环引用。因此,需要用weak来声明self。

9.简述下block的实现

  • block的常见类型有3种

_NSConcreteGlobalBlock(全局)
_NSConcreteStackBlock(栈)
_NSConcreteMallocBlock(堆)

void (^globalBlock)() = ^{
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^stackBlock1)() = ^{
        };
    }
    return 0;
}

对其进行编译转换后得到以下缩略代码:

// globalBlock
struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
...

// stackBlock
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
...
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    }
    return 0;
}

可以看出globalBlock的isa指向了_NSConcreteGlobalBlock,即在全局区域创建,编译时具体的代码就已经确定在上图中的代码段中了,block变量存储在全局数据存储区;stackBlock的isa指向了_NSConcreteStackBlock,即在栈区创建。

接下来是在堆中的block,堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。由于block的拷贝最终都会调用_Block_copy_internal函数,所以观察这个函数就可以知道堆中block是如何被创建的了:

函数通过memmove将栈中的block的内容拷贝到了堆中,并使isa指向了_NSConcreteMallocBlock。

  • 捕捉变量对block结构的影响

局部变量
全局变量
局部静态变量
__block修饰的变量
self隐式循环引用

局部变量只是一次值传递。并且当我们想在block中进行以下操作时,将会发生错误:
- (void)test
{
    int a;
    ^{a = 10;};//error: variable is not assignable(missing __block type specifier)
}

全局变量都是在静态数据存储区,在程序结束前不会被销毁,所以block直接访问了对应的变量

静态局部变量是存储在静态数据存储区域的,也就是和程序拥有一样的生命周期,也就是说在程序运行时,都能够保证block访问到一个有效的变量。但是其作用范围还是局限于定义它的函数中,所以只能在block通过静态局部变量的地址来进行访问。

__block修饰的变量
会编译成结构体,这个结构体中含有isa指针,所以也是一个对象,它是用来包装局部变量a的。当block被copy到堆中时block的拷贝辅助函数会将结构体拷贝至堆中,所以即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作。其中变量的结构体成员指针__forwarding用来指向它在堆中的拷贝,

self隐式循环引用

@implementation Person
{
    int _a;
    void (^_block)();
}
- (void)test
{
  void (^_block)() = ^{
        _a = 10;
    };
}

@end

如果在编译转换前,将_a改成self.a,能很明显地看出是产生了循环引用(self强引用block,block强引用self)。那么使用_a呢?经过编译转换后,依然可以在__Person__test_block_impl_0看见self的身影。可以看出,不管是用什么形式访问实例变量,最终都会转换成self+变量内存偏移的形式。

10.对于深拷贝和浅拷贝的理解

  • 浅拷贝:指针拷贝,复制一个新的指针,只想同一块内存区域。实际内存并没有发生拷贝
  • 深拷贝:内容拷贝,拷贝数据到一块新内存区域,指针指向拷贝的数据区

iOS中的深浅拷贝strong,copy,mutableCopy。
copy出来的字符串一定是不可变字符串,如果传入的是可变字符串,会发生深拷贝为不可变字符串,否则为浅拷贝。 2. mutablecopy,一定是深拷贝,拷贝出来的一定是可变字符串或者数组,即使传入的是不可变字符串或者数组。

11.消息转发机制实现

  • 进行一次发送消息会在相关的类对象中搜索方法列表,如果找不到则会沿着继承树向上一直搜索知道继承树根部(通常为NSObject),如果还是找不到并且消息转发都失败了就回执行doesNotRecognizeSelector:方法报unrecognized selector错。

下图就是消息转发的流程:


3132379-658e5bfa9f93dc9d.jpeg

第一次: 所属类动态方法解析
首先会调用+ (BOOL)resolveInstanceMethod:(SEL)sel这个方法,如果返回True就会再次执行相关方法.

第二次机会: 备援接收者
其次当对象所属类不能动态添加方法后,runtime就会询问当前的接受者是否有其他对象可以处理这个未知的selector,- (id)forwardingTargetForSelector:(SEL)aSelector;该方法的参数就是那个未知的selector,这是一个实例方法,因为是询问该实例对象是否有其他实例对象可以接收这个未知的selector,如果没有就返回nil

第三次机会: 消息重定向
当没有备援接收者时,就只剩下最后一次机会,那就是消息重定向。这个时候runtime会将未知消息的所有细节都封装为NSInvocation对象,然后调用下述方法:- (void)forwardInvocation: (NSInvocation*)invocation;调用这个方法如果不能处理就会调用父类的相关方法,一直到NSObject的这个方法,如果NSObject都无法处理就会调用doesNotRecognizeSelector:方法抛出异常。

12.@property的底层实现

  • 文件编译成cpp文件后,会看到@property会生成unsigned long类型的变量,代表实例变量在内存中存储的偏移量,通过这个值就能够在内存中定位到这个实例变量的位置。
  • get方法会使用类的内存地址加上OBJC_IVAR__Person_cjmName计算的的偏移量来计算出属性的位置并返回。
  • nsstring使用copy的set方法会首先声明objc_setProperty方法,然后使用类的内存地址加上OFFSETOFIVAR(TYPE, MEMBER)方法计算的偏移量,用objc_setProperty来设置实例变量的值。nsstring使用strong的set方法没有声明objc_setProperty方法也没有使用该方法,而是直接计算出实例变量的偏移量后将指针赋给实例变量。所以可以得出copy和strong在底层实现上是有区别的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,242评论 8 265
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,065评论 1 32
  • OC语言基础 1.类与对象 类方法 OC的类方法只有2种:静态方法和实例方法两种 在OC中,只要方法声明在@int...
    奇异果好补阅读 4,228评论 0 11
  • 终于按捺不住,在家里呆得太久!迫不及待地想要出去透口气。于是,我们一家四口浩浩荡荡地出发了,此行并不打算出...
    阡陌雨纷飞阅读 377评论 1 4
  • 年轻时的风风火火、跌跌撞撞已被岁月打磨的荡然无存。磨磨唧唧的起床,慢慢悠悠的生活,这不乏也是一种惬意、有情调的生活...
    红珍_5f7b阅读 169评论 0 1