iOS 转某博文阿里、头条面试

iOS 转某博文阿里、头条面试

链接附上
https://www.jianshu.com/p/e87e0be2281f

以下对每道题做出我的理解,如有不对的地方请各位指正,共同进步

结构模型

介绍下runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等)

在Objective-C中,任何类的定义都是对象。类和类的实例(对象)没有任何本质上的区别。任何对象都有isa指针。Class 是一个 objc_class 结构类型的指针, id是一个 objc_object 结构类型的指针。
每一个实例对象,其 isa 指针指向其类类对象,类对象的 isa 指针指向 metaclass,类对象的 superclass 指针指向其父类,一直指向根类对象,根类对象的 superclass 指针指向 nil;metaclass 的 isa 指针都指向 rootMetaclass, 而 rootMetaClass 的 isa 指针指向自己,其 superclass指针指向根类对象。依次来达到一个程序的闭合。实例对象存储的自身的值,而类对象存储的则是实例对象的方法列表、成员变量、协议等,根类对象存储的是类对象的方法。

为什么要设计metaclass

符合程序设计的原则,单一职责原则

class_copyIvarList & class_copyPropertyList区别

class_copyIvarList:可以获取类的属性列表以及成员变量列表
class_copyPropertyList:只能获取类的属性列表

class_rw_t 和 class_ro_t 的区别

class_rw_t结构体内有一个指向 class_ro_t结构体的指针。
每个类都对应有一个class_ro_t结构体和一个class_rw_t结构体。在编译期间,class_ro_t结构体就已经确定,objc_class中的bits的data部分存放着该结构体的地址。在runtime运行之后,具体说来是在运行runtime的realizeClass 方法时,会生成class_rw_t结构体,该结构体包含了class_ro_t,并且更新data部分,换成class_rw_t结构体的地址。
他们都存放着当前类的属性、实例变量、方法、协议等等。区别在于:class_ro_t存放的是编译期间就确定的;而class_rw_t是在runtime时才确定,它会先将class_ro_t的内容拷贝过去,然后再将当前类的分类的这些属性、方法等拷贝到其中。所以可以说class_rw_t是class_ro_t的超集,当然实际访问类的方法、属性等也都是访问的class_rw_t中的内容

category如何被加载的,两个category的load方法的加载顺序,两个category的同名方法的加载顺序

把分类的 实例方法、属性、协议 添加到类的实例对象中原本存储的 实例方法、属性、协议列表 的 前面 ;
把分类的类方法和协议添加到类的元类上。
这么做的目的就是保证分类方法 优先调用,注意,不是覆盖,而是共同存在在实例方法列表中,只是分类在前而已。
分类间的加载顺序取决于编译的顺序:编译在前则先加载,编译在后则后加载

category & extension区别,能给NSObject添加Extension吗,结果如何

  1. 分类
    运行时决议
    可以为系统类添加分类
    添加实例方法
    类方法
    协议
    属性 (只声明了 get 和 set 方法,但是却没有添加实例变量)
  2. 拓展
    编译时决议
    只以声明的形式存在
    不能为系统类添加拓展
    声明私有属性
    声明私有方法
    声明私有成员变量
    分类可以有声明,可以有实现

消息转发机制,消息转发机制和其他语言的消息机制优劣对比

https://juejin.im/post/5ae96e8c6fb9a07ac85a3860

在方法调用的时候,方法查询-> 动态解析-> 消息转发 之前做了什么

  • 首先检查这个selector是不是要忽略。比如Mac OS X开发,有了垃圾回收就不会理会retain,release这些函数。
  • 检测这个selector的target是不是nil,OC允许我们对一个nil对象执行任何方法不会Crash,因为运行时会被忽略掉。

IMP、SEL、Method的区别和使用场景

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
Objc.h
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;

在文档中,selector的定义都是这样声明,也就是说:selector是SEL的一个实例,只是在iOS中,selector的使用是如此的频繁,我们才会把他当成一个概念。
selector怎么理解呢?我们可以想想股票,比如市场上有如此多公司在纳斯达克上市,而且他们的名字又非常的长,或者有些公司的名称也是相似的,都是**有限公司。那当市场去指定一个股票的时候,效率会非常低,当你着急想买股票的时候,你会跟你的经纪人说:“hi,peter,给我买一百股Tuniu limited liability company的股票吗?”,也许等你说完,经纪人输入完,市场就变化了,所以纳斯达克通常用代码,比如“TOUR”.这里的selector有类似的作用,就是让我们能够快速找到对应的函数。

/// A pointer to the function of a method implementation.  指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...); 
#endif

这个就比较好理解了,就是指向最终实现程序的内存地址的指针。
综上,在iOS的runtime中,Method通过selector和IMP两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。

https://njafei.github.io/2017/05/03/Method-SEL-IMP/

load、initialize方法的区别什么?在继承关系中他们有什么区别

  • 以main为分界,load方法在main函数之前执行,initialize在main函数之后执行
  • load和initialize会被自动调用,不能手动调用它们。
  • 子类实现了load和initialize的话,会隐式调用父类的load和initialize方法
  • load和initialize方法内部使用了锁,因此它们是线程安全的。
  • 子类中没有实现load方法的话,不会调用父类的load方法;而子类如果没有实现initialize方法的话,也会自动调用父类的initialize方法。
  • load方法是在类被装在进来的时候就会调用,initialize在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次,是懒加载模式,如果这个类一直没有使用,就不回调用到initialize方法。
load 调用顺序如下:
  • 父类load先于类添加到loadable_classes列表,通过call_class_loads,调用列表中的load方法,这样父类的load先于类的load执行
    当loadable_classes为空的时候,查看loadable_classes是否为空,如果不为空则调用call_category_loads加载分类中的load方法,这样分类的load在类之后执行
initialize调用顺序
  • initialize 只会在对应类的方法第一次被调用时,才会调用,initialize 方法是在 alloc 方法之前调用的,alloc 的调用导致了前者的执行。
  • initialize的调用栈中,直接调用其方法的其实是_class_initialize 这个C语言函数,在这个方法中,主要是向为初始化的类发送+initialize消息,不过会强制父类先发送。
  • 与 load 不同,initialize 方法调用时,所有的类都已经加载到了内存中。

说说消息转发机制的优劣

优点:
优雅的消息传递机制
动态特性
Category
缺点:
不支持命名空间
蹩脚的KVO
蹩脚的多态
Runtime 的各种黑魔法
引用计数的内存管理方式(会有循环引用)
鬼畜的布尔类型
轻量的面向对象特性 (由于支持C中的东西,装入OO的容器需要wrap)

内存管理

weak的实现原理?SideTable的结构是什么样的

Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

struct SideTable {
// 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
    // weak 引用全局 hash 表
    weak_table_t weak_table;
}

关联对象的应用?系统如何实现关联对象的

关键对象多用于在分类,因为分类中,不会存储实例变量,而我们要把实例变量关联进去,那么就需要关联对象技术了。具体代码如下:

-(void)setName:(NSString *)name {
// 设置 value,通过 key 和 value 建立映射,通过Policy策略将映射关系关联到设置的对象上
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSString *)name {
// 根据指定的 key,到 obj 中找到与当前 key 相对应的关联值
    return objc_getAssociatedObject(self, @"name");    
}

https://www.jianshu.com/p/32084d47963d

关联对象的如何进行内存管理的?关联对象如何实现weak属性

系统通过调用 objc_setAssociate的方法将对象包裹成一个 ObjcAssociation对象,将 value 和 policy,存进 ObjcAssociation对象中,然后将 ObjcAssociation 这个对象作为 value 存进 ObjcAssociationMap 中,在ObjcAssociationMap中有一个 selector,最终将ObjcAssociationMap存进AssociationsManagerHashMap 中,在AssociationsManagerHashMap中,有一个 disguise 指针指向我们的ObjcAssociationMap这样就完成对象的关联了。其内存管理也是通过引用计数进行管理的,释放的时候不需要我们手动释放,dealloc 方法内部会进行判断

Autoreleasepool的原理?所使用的的数据结构是什么

以栈为节点通过双向链表组合而成且和线程一一对应

// @autoreleasepool 经编译器改写会变成  
void *ctx = autoreleasepoolPush() 
{}
autoreleasepoolpop(ctx)
//  autoreleasepoolPush()  内部会调用 autoreleasepoolPage::Push
// autoreleasepoolPage 的数据结构是 
struct autoreleasePoolPage{
    id  *next;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    pthread_t const thread;
}

ARC的实现原理?ARC下对retain & release做了哪些优化

利用 llvm 和 runtime协同完成自动引用计数的管理。
这个问题太大,一时之间竟然想不出来怎么回答为好,所以还是从 关键字 strong、weak、autorelease 实现原理入手吧。
https://juejin.im/post/5ce2b7386fb9a07eff005b4c

ARC下哪些情况会造成内存泄漏

其他

Method Swizzle注意事项

  • 第一个风险是,需要在 +load 方法中进行方法交换。因为如果在其他时候进行方法交换,难以保证另外一个线程中不会同时调用被交换的方法,从而导致程序不能按预期执行。
  • 第二个风险是,被交换的方法必须是当前类的方法,不能是父类的方法,直接把父类的实现拷贝过来不会起作用。父类的方法必须在调用的时候使用,而不是方法交换时使用。
  • 第三个风险是,交换的方法如果依赖了 cmd,那么交换后,如果 cmd 发生了变化,就会出现各种奇怪问题,而且这些问题还很难排查。特别是交换了系统方法,你无法保证系统方法内部是否依赖了 cmd。
  • 第四个风险是,方法交换命名冲突。如果出现冲突,可能会导致方法交换失败。

属性修饰符atomic的内部实现是怎么样的?能保证线程安全吗

atomic 内部是通过自旋锁的方式实现的,在其 set、get 方法的内部加入了自旋锁,所以 atomic 仅仅只能保证在set、get 的线程安全。当然仅仅只限于当前线程,如果在多个线程同时操作 set 方法,那么在其他线程访问的该值就是已经更新过后的,所以不能保证线程安全。

iOS 中内省的几个方法有哪些?内部实现原理是什么

判断对象类型:
-(BOOL) isKindOfClass: 判断是否是这个类或者这个类的子类的实例
-(BOOL) isMemberOfClass: 判断是否是这个类的实例
判断对象or类是否有这个方法
-(BOOL) respondsToSelector: 判读实例是否有这样方法
+(BOOL) instancesRespondToSelector: 判断类是否有这个方法

实现原理:

- (BOOL)isMemberOfClass:(Class)cls {
   // 直接获取实例类对象并判断是否等于传入的类对象
    return [self class] == cls;
}

- (BOOL)isKindOfClass:(Class)cls {
   // 向上查询,如果找到父类对象等于传入的类对象则返回YES
   // 直到基类还不相等则返回NO
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

class、objc_getClass、object_getclass 方法有什么区别?

  • 1.当参数obj为Object实例对象
    object_getClass(obj)与[obj class]输出结果一直,均获得isa指针,即指向类对象的指针。
  • 2.当参数obj为Class类对象
    object_getClass(obj)返回类对象中的isa指针,即指向元类对象的指针;[obj class]返回的则是其本身。
  • 3.当参数obj为Metaclass类对象
    object_getClass(obj)返回元类对象中的isa指针,因为元类对象的isa指针指向根类,所有返回的是根类对象的地址指针;[obj class]返回的则是其本身。
  • 4.obj为Rootclass类对象
    object_getClass(obj)返回根类对象中的isa指针,因为跟类对象的isa指针指向Rootclass‘s metaclass(根元类),即返回的是根元类的地址指针;[obj class]返回的则是其本身。
  • 总结:
    经上面初步的探索得知,object_getClass(obj)返回的是obj中的isa指针;而[obj class]则分两种情况:一是当obj为实例对象时,[obj class]中class是实例方法:- (Class)class,返回的obj对象中的isa指针;二是当obj为类对象(包括元类和根类以及根元类)时,调用的是类方法:+ (Class)class,返回的结果为其本身。

NSNotification相关

苹果并没有开源相关代码,但是可以读下GNUStep的源码,基本上实现方式很具有参考性

1. 实现原理(结构设计、通知如何存储的、name&observer&SEL之间的关系等)

2. 通知的发送时同步的,还是异步的

3. NSNotificationCenter接受消息和发送消息是在一个线程里吗?如何异步发送消息

4. NSNotificationQueue是异步还是同步发送?在哪个线程响应

5. NSNotificationQueuerunloop的关系

6. 如何保证通知接收的线程在主线程

7. 页面销毁时不移除通知会崩溃吗

8. 多次添加同一个通知会是什么结果?多次移除通知呢

9. 下面的方式能接收到通知吗?为什么

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容