IOS面试整理

打印 上一主题 下一主题

主题 873|帖子 873|积分 2619

1、无序数组中的中位数


    1. //求一个无序数组的中位数
    2. int findMedian(int a[], int aLen)
    3. {
    4. int low = 0;
    5. int high = aLen - 1;
    6. int mid = (aLen - 1) / 2;
    7. int div = PartSort(a, low, high);
    8. while (div != mid)
    9. {
    10. if (mid < div)
    11. {
    12.   //左半区间找
    13.   div = PartSort(a, low, div - 1);
    14. }
    15. else
    16. {
    17.   //右半区间找
    18.   div = PartSort(a, div + 1, high);
    19. }
    20. }
    21. //找到了
    22. return a[mid];
    23. }
    复制代码
  1. int PartSort(int a[], int start, int end)
  2. {
  3.   int low = start;
  4.   int high = end;
  5.   //选取关键字
  6. int key = a[end];
  7. while (low < high)
  8. {
  9.   //左边找比key大的值
  10.   while (low < high && a[low] <= key)
  11.   {
  12.       ++low;
  13.   }
  14.    
  15.   //右边找比key小的值
  16.   while (low < high && a[high] >= key)
  17.   {
  18.       --high;
  19.   }
  20.    
  21.   if (low < high)
  22.   {
  23.       //找到之后交换左右的值
  24.       int temp = a[low];
  25.       a[low] = a[high];
  26.       a[high] = temp;
  27.   }
  28. }
  29. int temp = a[high];
  30. a[high] = a[end];
  31. a[end] = temp;
  32. return low;
  33. }
复制代码
2、id 和 instanceType 有什么区别?

雷同点

instancetype 和 id 都是万能指针,指向对象。
不同点:

1.id 在编译的时间不能判断对象的真实类型,instancetype 在编译的时间可以判断对象的真实类型。
2.id 可以用来界说变量,可以作为返回值类型,可以作为形参类型;instancetype 只能作为返回值类型。
3、[self class]和[super class]的区别

当使用[self class]时,这时的self是Son,在使用objc_msgSend时,第一个参数是receiver也就是self,也是 Son* son这个实例。第二个参数,要先找到class这个方法的selector,先从Son这个类开始找,没有,然后到Son的父类 Father中去找,也没有,再去Father的父类NSObject去找,一层一层向上找之后,在NSObject的类中发现这个class方法,而 NSObject的这个class方法,就是返回receiver的类别,所以这里输出Son。
当使用[super class]时,这时要转换成objc_msgSendSuper的方法。先构造objc_super的布局体吧,第一个成员变量就是self, 第二个成员变量是Father,然后要找class这个selector,先去superClass也就是Father中去找,没有,然后去Father 的父类中去找,结果还是在NSObject中找到了。然后内部使用函数objc_msgSend(objc_super->receiver, @selector(class)) 去调用,此时已经和我们用[self class]调用时雷同了,此时的receiver还是Son* son,所以这里返回的也是Son
4、self和super的区别



  • self调用自己方法,super调用父类方法

  • self是类,super是预编译指令

  • [self class] 和 [super class] 输出是一样的

  • self和super底层实现原理
    1.当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;
    而当使用 super 时,则从父类的方法列表中开始找,然后调用父类的这个方法
    2.当使用 self 调用时,会使用 objc_msgSend 函数:
    1. id objc_msgSend(id theReceiver, SEL theSelector, ...)
    复制代码
    第一个参数是消息接收者,第二个参数是调用的具体类方法的 selector,背面是 selector 方法的可变参数。以 [self setName:] 为例,编译器会更换成调用 objc_msgSend 的函数调用,其中 theReceiver 是 self,theSelector 是 @selector(setName,这个 selector 是从当前 self 的 class 的方法列表开始找的 setName,当找到后把对应的 selector 通报已往。
    3.当使用 super 调用时,会使用 objc_msgSendSuper 函数:
    1. id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
    复制代码
    第一个参数是个objc_super的布局体,第二个参数还是雷同上面的类方法的selector
    1. struct objc_super {
    2.         id receiver;
    3.         Class superClass;
    4. };
    复制代码
    5、struct和class的区别
         
    • 类: 引用类型(位于栈上面的指针(引用)和位于堆上的实体对象)
         
    • 布局:值类型(实例直接位于栈中)
        
    6、setNeedsDisplay 和 layoutIfNeeded 两者是什么关系?

    UIView的setNeedsDisplay和setNeedsLayout两个方法都是异步执行的。而setNeedsDisplay会自动调用drawRect方法,这样可以拿到UIGraphicsGetCurrentContext进行绘制;而setNeedsLayout会默认调用layoutSubViews,给当前的视图做了标志;layoutIfNeeded 查找是否有标志,如果有标志及立即革新。
    只有setNeedsLayout和layoutIfNeeded这二者合起来使用,才会起到立即革新的效果。

7、loadView的作用?

loadView方法会在每次访问UIViewController的view(比如controller.view、self.view)而且view为nil时会被调用,此方法主要用来负责创建UIViewController的view(重写loadView方法,并且不需要调用[super loadView])
这里要提一下 [super loadView],[super loadView]做了下面几件事。


  • 它会先去查找与UIViewController相关联的xib文件,通过加载xib文件来创建UIViewController的view,如果在初始化UIViewController指定了xib文件名,就会根据传入的xib文件名加载对应的xib文件,如果没有显着地传xib文件名,就会加载跟UIViewController同名的xib文件

  • 如果没有找到相关联的xib文件,就会创建一个空白的UIView,然后赋值给UIViewController的view属性
综上,在需要自界说UIViewController的view时,可以通过重写loadView方法且不需要调用[super loadView]方法。
8、keyWindow 和 delegate的window有何区别



  • delegate.window 程序启动时设置的window对象。

  • keyWindow 这个属性保存了[windows]数组中的[UIWindow]对象,该对象迩来被发送了[makeKeyAndVisible]消息
一样寻常环境下 delegate.window 和 keyWindow 是同一个对象,但不能保证keyWindow就是delegate.window,因为keyWindow会因为makeKeyAndVisible而变革,例如,程序中添加了一个悬浮窗口,这个时间keywindow就会变革。
9、说一下 JS 和 OC 互相调用的几种方式?



  • js调用oc的三种方式:
    根据网页重定向截取字符勾通过url scheme判断
    更换方法.context[@"copyText"]
    注入对象:服从协议JSExport,设置context[@

  • oc调用js代码两种方式
    通过webVIew调用 webView stringByEvaluatingJavaScriptFromString: 调用
    通过JSContext调用[context evaluateScript:];
10、如何让自己的类用copy修饰符?如何重写带copy关键字的setter?



  • 若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自界说的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
    具体步骤:
    需声明该类服从 NSCopying 协议
    实现 NSCopying 协议。该协议只有一个方法:
    1. - (id)copyWithZone:(NSZone *)zone;
    复制代码
    注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。

  • 重写带 copy 关键字的 setter,例如:
    1. - (void)setName:(NSString *)name {
    2.     //[_name release];
    3.     _name = [name copy];
    4. }
    复制代码
11、深拷贝与浅拷贝

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不光对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地点的指针。
当对象中存在指针成员时,除了在复制对象时需要考虑自界说拷贝构造函数,还应该考虑以下两种情况:


  • 当函数的参数为对象时,实参通报给形参的现实上是实参的一个拷贝对象,系统自动通过拷贝构造函数实现;

  • 当函数的返回值为一个对象时,该对象现实上是函数内对象的一个拷贝,用于返回函数调用处。
copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。
mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。
12、@property的本质是什么?ivar、getter、setter是如何天生并添加到这个类中的



  • @property 的本质是实例变量(ivar)+存取方法(access method = getter + setter),即 @property = ivar + getter + setter;
    “属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一样寻常通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。

  • ivar、getter、setter 是自动合成这个类中的
    完成属性界说后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译 器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了天生方法代码 getter、setter 之外,编译器还要自动向类中添加得当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会天生两个实例变量,其名称分别为 _firstName 与 _lastName。也可以在类的实今世码里通过 @synthesize 语法来指定实例变量的名字.
13、@protocol和category中如何使用@property



  • 在 protocol 中使用 property 只会天生 setter 和 getter 方法声明,我们使用属性的目的,是希望服从我协议的对象能实现该属性

  • category 使用 @property 也是只会天生 setter 和 getter 方法的声明,如果我们真的需要给 category 增加属性的实现,需要借助于运行时的两个函数:objc_setAssociatedObject和objc_getAssociatedObject
    简述 Category 的实现原理

    我们知道 Objective-C 通过 Runtime 运行时来实现动态语言这个特性,全部的类和对象,在 Runtime 中都是用布局体来表示的,Category 在 Runtime 中是用布局体 category_t 来表示的,下面是布局体 category_t 具体表示:
    1. typedef struct category_t {
    2.     const char *name;//类的名字 主类名字
    3.     classref_t cls;//类
    4.     struct method_list_t *instanceMethods;//实例方法的列表
    5.     struct method_list_t *classMethods;//类方法的列表
    6.     struct protocol_list_t *protocols;//所有协议的列表
    7.     struct property_list_t *instanceProperties;//添加的所有属性
    8. } category_t;
    复制代码
    通过布局体 category_t 可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。我们这里简述下 Category 的实现原理:
         
    • 在编译时期,会将分类中实现的方法天生一个布局体 method_list_t 、将声明的属性天生一个布局体 property_list_t ,然后通过这些布局体天生一个布局体 category_t 。
        


  • 然后将布局体 category_t 保存下来
         
    • 在运行时期,Runtime 会拿到编译时期我们保存下来的布局体 category_t
         
    • 然后将布局体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中
         
    • 将布局体 category_t 中的类方法列表、协议列表添加到主类的 metaClass 中
        
    这里需要注意的是:category_t 中的方法列表是插入到主类的方法列表前面(雷同利用链表中的 next 指针来进行插入),所以这里 Category 中实现的方法并不会真正的覆盖掉主类中的方法,只是将 Category 的方法插到方法列表的前面去了。运行时在查找方法的时间是顺着方法列表的次序查找的,它只要一找到对应名字的方法,就会克制查找,这里就会出现覆盖方法的这种假象了。

    1. // 这里大概就类似这样子插入
    2. newproperties->next = cls->data()->properties;
    3. cls->data()->properties = newproperties;,
    复制代码
    通过上面的简述,我们大概了解了 Category 的实现原理,就可以知道 Extension 跟 Category 是两种实现模式,一个是在编译时期实现的,一个是在运行时期决定的。

14、iOS内存分区环境



  • 栈区(Stack)
    由编译器自动分配开释,存放函数的参数,局部变量的值等
    栈是向低地点扩展的数据布局,是一块连续的内存地区

  • 堆区(Heap)
    由程序员分配开释
    是向高地点扩展的数据布局,是不连续的内存地区

  • 全局区
    全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块地区,未初始化的全局变量和未初始化的静态变量在相邻的另一块地区
    程序竣事后由系统开释

  • 常量区
    常量字符串就是放在这里的
    程序竣事后由系统开释

  • 代码区
    存放函数体的二进制代码

  • 注:
         
    • 在 iOS 中,堆区的内存是应用程序共享的,堆中的内存分配是系统负责的
         
    • 系统使用一个链表来维护全部已经分配的内存空间(系统仅仅记录,并不管理具体的内容)
         
    • 变量使用竣事后,需要开释内存,OC 中是判断引用计数是否为 0,如果是就说明没有任何变量使用该空间,那么系统将其回收
         
    • 当一个 app 启动后,代码区、常量区、全局区巨细就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变革的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区内里的内存时,一定要注意内存是否已经被开释,否则会产生程序崩溃(也便是野指针报错)
        

15、iOS内存管理方式



  • Tagged Pointer(小对象)
    Tagged Pointer 专门用来存储小的对象,例如 NSNumber 和 NSDate
    Tagged Pointer 指针的值不再是地点了,而是真正的值。所以,现实上它不再是一个对象了,它只是一个披着对象皮的平凡变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free
    在内存读取上有着 3 倍的效率,创建时比以前快 106 倍
    objc_msgSend 能辨认 Tagged Pointer,比如 NSNumber 的 intValue 方法,直接从指针提取数据
    使用 Tagged Pointer 后,指针内存储的数据变成了 Tag + Data,也就是将数据直接存储在了指针中

  • NONPOINTER_ISA (指针中存放与该对象内存相关的信息) 苹果将 isa 设计成了联合体,在 isa 中存储了与该对象相关的一些内存的信息,缘故原由也如上面所说,并不需要 64 个二进制位全部都用来存储指针。
    isa 的布局:
    1. // x86_64 架构
    2. struct {
    3.     uintptr_t nonpointer        : 1;  // 0:普通指针,1:优化过,使用位域存储更多信息
    4.     uintptr_t has_assoc         : 1;  // 对象是否含有或曾经含有关联引用
    5.     uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析构函数或OC的dealloc
    6.     uintptr_t shiftcls          : 44; // 存放着 Class、Meta-Class 对象的内存地址信息
    7.     uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
    8.     uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    9.     uintptr_t deallocating      : 1;  // 对象是否正在释放
    10.     uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数
    11.     uintptr_t extra_rc          : 8;  // 引用计数能够用 8 个二进制位存储时,直接存储在这里
    12. };
    13. // arm64 架构
    14. struct {
    15.     uintptr_t nonpointer        : 1;  // 0:普通指针,1:优化过,使用位域存储更多信息
    16.     uintptr_t has_assoc         : 1;  // 对象是否含有或曾经含有关联引用
    17.     uintptr_t has_cxx_dtor      : 1;  // 表示是否有C++析构函数或OC的dealloc
    18.     uintptr_t shiftcls          : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
    19.     uintptr_t magic             : 6;  // 用于在调试时分辨对象是否未完成初始化
    20.     uintptr_t weakly_referenced : 1;  // 是否被弱引用指向
    21.     uintptr_t deallocating      : 1;  // 对象是否正在释放
    22.     uintptr_t has_sidetable_rc  : 1;  // 是否需要使用 sidetable 来存储引用计数
    23.     uintptr_t extra_rc          : 19;  // 引用计数能够用 19 个二进制位存储时,直接存储在这里
    24. };
    复制代码
    这里的 has_sidetable_rc 和 extra_rc,has_sidetable_rc 表明该指针是否引用了 sidetable 散列表,之所以有这个选项,是因为少量的引用计数是不会直接存放在 SideTables 表中的,对象的引用计数会先存放在 extra_rc 中,当其被存满时,才会存入相应的 SideTables 散列表中,SideTables 中有许多张 SideTable,每个 SideTable 也都是一个散列表,而引用计数表就包含在 SideTable 之中。

  • 散列表(引用计数表、弱引用表)
    引用计数要么存放在 isa 的 extra_rc 中,要么存放在引用计数表中,而引用计数表包含在一个叫 SideTable 的布局中,它是一个散列表,也就是哈希表。而 SideTable 又包含在一个全局的 StripeMap 的哈希映射表中,这个表的名字叫 SideTables。
    当一个对象访问 SideTables 时:
         
    • 起首会取得对象的地点,将地点进行哈希运算,与 SideTables 中 SideTable 的个数取余,最后得到的结果就是该对象所要访问的 SideTable
         
    • 在取得的 SideTable 中的 RefcountMap 表中再进行一次哈希查找,找到该对象在引用计数表中对应的位置
         
    • 如果该位置存在对应的引用计数,则对其进行操纵,如果没有对应的引用计数,则创建一个对应的 size_t 对象,其实就是一个 uint 类型的无符号整型
        
    弱引用表也是一张哈希表的布局,其内部包含了每个对象对应的弱引用表 weak_entry_t,而 weak_entry_t 是一个布局体数组,其中包含的则是每一个对象弱引用的对象所对应的弱引用指针。

16、KVC实现原理



  • KVC,键-值编码,使用字符串直接访问对象的属性。

  • 底层实现,当一个对象调用setValue方法时,方法内部会做以下操纵:
    1.查抄是否存在相应key的set方法,如果存在,就调用set方法
    2.如果set方法不存在,就会查找与key雷同名称并且带下划线的成员属性,如果有,则直接给成员属性赋值
    3.如果没有找到_key,就会查找雷同名称的属性key,如果有就直接赋值
    4.如果还没找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法
17、KVO的实现原理



KVO-键值观察机制,原理如下:


  • 1.当给A类添加KVO的时间,runtime动态的天生了一个子类NSKVONotifying_A,让A类的isa指针指向NSKVONotifying_A类,重写class方法,隐藏对象真实类信息

  • 2.重写监听属性的setter方法,在setter方法内部调用了Foundation 的 _NSSetObjectValueAndNotify 函数

  • 3._NSSetObjectValueAndNotify函数内部
    a) 起首会调用 willChangeValueForKey
    b) 然后给属性赋值
    c) 最后调用 didChangeValueForKey
    d) 最后调用 observer 的 observeValueForKeyPath 去告诉监听器属性值发生了改变 .

  • 4.重写了dealloc做一些 KVO 内存开释
18、如何手动触发KVO方法



  • 手动调用willChangeValueForKey和didChangeValueForKey方法

  • 键值观察通知依赖于 NSObject 的两个方法: willChangeValueForKey: 和 didChangeValueForKey。在一个被观察属性发生改变之前, willChangeValueForKey: 一定会被调用,这就 会记录旧的值。而当改变发生后, didChangeValueForKey 会被调用,继而 observeValueForKeyfObject:change:context: 也会被调用。如果可以手动实现这些调用,就可以实现“手动触发”了 有人大概会问只调用didChangeValueForKey方法可以触发KVO方法,其实是不能的,因为willChangeValueForKey: 记录旧的值,如果不记录旧的值,那就没有改变一说了
19、block和delegate的区别



  • delegate运行本钱低,block的运行本钱高
    block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是加计数,使用完或者block置nil后才消除。delegate只是保存了一个对象指针,直接回调,没有额外斲丧。就像C的函数指针,只多做了一个查表动作。

  • delegate更适用于多个回调方法(3个以上),block则适用于1,2个回调时。
20、Http 和 Https 的区别?Https为什么更加安全?



  • 区别
    1.HTTPS 需要向机构申请 CA 证书,极少免费。
    2.HTTP 属于明文传输,HTTPS基于 SSL/TLS进行加密传输。
    3.HTTP 端口号为 80,HTTPS 端口号为 443 。
    4.HTTPS 是加密传输,有身份验证的环节,更加安全。

  • 安全
    SSL(安全套接层) TLS(传输层安全)
    以上两者在传输层之上,对网络连接进行加密处置处罚,保障数据的完整性,更加的安全。
21、HTTPS的连接创建流程

HTTPS为了分身安全与效率,同时使用了对称加密和非对称加密。在传输的过程中会涉及到三个密钥:


  • 服务器端的公钥和私钥,用来进行非对称加密

  • 客户端天生的随机密钥,用来进行对称加密


如上图,HTTPS连接过程大致可分为八步:


  • 1、客户端访问HTTPS连接。
    客户端会把安全协议版本号、客户端支持的加密算法列表、随机数C发给服务端。

  • 2、服务端发送证书给客户端
    服务端接收密钥算法配件后,会和自己支持的加密算法列表进行比对,如果不符合,则断开连接。否则,服务端会在该算法列表中,选择一种对称算法(如AES)、一种公钥算法(如具有特定秘钥长度的RSA)和一种MAC算法发给客户端。
    服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
    在发送加密算法的同时还会把数字证书和随机数S发送给客户端

  • 3、客户端验证server证书
    会对server公钥进行查抄,验证其合法性,如果发现发现公钥有问题,那么HTTPS传输就无法继续。

  • 4、客户端组装会话秘钥
    如果公钥合格,那么客户端会用服务器公钥来天生一个前主秘钥(Pre-Master Secret,PMS),并通过该前主秘钥和随机数C、S来组装成会话秘钥

  • 5、客户端将前主秘钥加密发送给服务端
    是通过服务端的公钥来对前主秘钥进行非对称加密,发送给服务端

  • 6、服务端通过私钥解密得到前主秘钥
    服务端接收到加密信息后,用私钥解密得到前主秘钥。

  • 7、服务端组装会话秘钥
    服务端通过前主秘钥和随机数C、S来组装会话秘钥。
    至此,服务端和客户端都已经知道了用于此次会话的主秘钥。

  • 8、数据传输
    客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。
    同理,服务端收到客户端发送来的密文,用服务端密钥对其进行对称解密,得到客户端发送的数据。
22、TCP 和 UDP的区别



  • TCP:面向连接、传输可靠(保证数据准确性,保证数据次序)、用于传输大量数据(流模式)、速度慢,创建连接需要开销较多(时间,系统资源)。

  • UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。</

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表