1、无序数组中的中位数
- //求一个无序数组的中位数
- int findMedian(int a[], int aLen)
- {
- int low = 0;
- int high = aLen - 1;
- int mid = (aLen - 1) / 2;
- int div = PartSort(a, low, high);
-
- while (div != mid)
- {
- if (mid < div)
- {
- //左半区间找
- div = PartSort(a, low, div - 1);
- }
- else
- {
- //右半区间找
- div = PartSort(a, div + 1, high);
- }
- }
- //找到了
- return a[mid];
- }
复制代码 - int PartSort(int a[], int start, int end)
- {
- int low = start;
- int high = end;
- //选取关键字
- int key = a[end];
-
- while (low < high)
- {
- //左边找比key大的值
- while (low < high && a[low] <= key)
- {
- ++low;
- }
-
- //右边找比key小的值
- while (low < high && a[high] >= key)
- {
- --high;
- }
-
- if (low < high)
- {
- //找到之后交换左右的值
- int temp = a[low];
- a[low] = a[high];
- a[high] = temp;
- }
- }
-
- int temp = a[high];
- a[high] = a[end];
- a[end] = temp;
-
- return low;
- }
复制代码 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 函数:
- 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 函数:
- id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
复制代码 第一个参数是个objc_super的布局体,第二个参数还是雷同上面的类方法的selector
- struct objc_super {
- id receiver;
- Class superClass;
- };
复制代码 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 协议。该协议只有一个方法:
- - (id)copyWithZone:(NSZone *)zone;
复制代码 注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
- 重写带 copy 关键字的 setter,例如:
- - (void)setName:(NSString *)name {
- //[_name release];
- _name = [name copy];
- }
复制代码 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 具体表示:
- 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_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 的方法插到方法列表的前面去了。运行时在查找方法的时间是顺着方法列表的次序查找的,它只要一找到对应名字的方法,就会克制查找,这里就会出现覆盖方法的这种假象了。
- // 这里大概就类似这样子插入
- newproperties->next = cls->data()->properties;
- 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 的布局:
- // x86_64 架构
- struct {
- uintptr_t nonpointer : 1; // 0:普通指针,1:优化过,使用位域存储更多信息
- uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用
- uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的dealloc
- uintptr_t shiftcls : 44; // 存放着 Class、Meta-Class 对象的内存地址信息
- uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
- uintptr_t weakly_referenced : 1; // 是否被弱引用指向
- uintptr_t deallocating : 1; // 对象是否正在释放
- uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 来存储引用计数
- uintptr_t extra_rc : 8; // 引用计数能够用 8 个二进制位存储时,直接存储在这里
- };
- // arm64 架构
- struct {
- uintptr_t nonpointer : 1; // 0:普通指针,1:优化过,使用位域存储更多信息
- uintptr_t has_assoc : 1; // 对象是否含有或曾经含有关联引用
- uintptr_t has_cxx_dtor : 1; // 表示是否有C++析构函数或OC的dealloc
- uintptr_t shiftcls : 33; // 存放着 Class、Meta-Class 对象的内存地址信息
- uintptr_t magic : 6; // 用于在调试时分辨对象是否未完成初始化
- uintptr_t weakly_referenced : 1; // 是否被弱引用指向
- uintptr_t deallocating : 1; // 对象是否正在释放
- uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 来存储引用计数
- uintptr_t extra_rc : 19; // 引用计数能够用 19 个二进制位存储时,直接存储在这里
- };
复制代码 这里的 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 会被调用,继而 observeValueForKey
fObject: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企服之家,中国第一个企服评测及商务社交产业平台。 |