├── README.md ├── Swift ├── .keep ├── OpenStack存储设施----Swift.md ├── Swfit可空(Optional)类型基础.md ├── Swift、OC分别实现用_ _ _隔开数组且只显示一行的小功能.md ├── Swift中构造方法的解析.md ├── Swift学习总结.md ├── Swift学习笔记.md ├── Swift开发之泛型实例.md ├── Swift进阶之路(一)——单例模式、属性传值、代理传值、闭包传值.md ├── 【Swift】WKWebView与JS的交互使用.md └── 【Swift实现代码】iOS架构模式之MVP.md ├── iOS底层进阶 ├── .keep ├── 2021 iOS底层提升计划.md ├── iOS Appium框架原理简析.md ├── iOS Runloop.md ├── iOS 多线程 线程间的状态.md ├── iOS 应用实现 gRPC 调用.md ├── iOS 架构.md ├── iOS--MJRefresh.md ├── iOS-组件化(OC篇).md ├── iOS_XML与JSON解析.md ├── iOS内存管理.md ├── iOS多线程基础.md ├── iOS底层原理 - isa和superclass.md ├── iOS底层学习 - 深入RunLoop.md ├── iOS开发-动态和静态FrameWork.md ├── iOS进阶_KVC(&KVC赋值取值过程分析&KVC自定义&异常处理).md └── 性能优化.md ├── iOS进阶提升资源 ├── .keep ├── BAT面试题PDF版 │ ├── .keep │ ├── Animation面试题.pdf │ ├── Block面试题.pdf │ ├── OC底层面试题.pdf │ ├── Runloop面试题.pdf │ ├── Runtime面试题.pdf │ ├── UI相关面试题.pdf │ ├── 内存管理面试题.pdf │ ├── 多线程面试题.pdf │ ├── 性能优化面试题.pdf │ ├── 数据安全及加密.pdf │ ├── 数据结构与算法.pdf │ ├── 网络相关面试题.pdf │ └── 设计模式面试题.pdf ├── iOS 内存管理总结.md ├── iOS 各种UI控件属性设置.md ├── iOS安全:Mach-O Type.md ├── iOS开发---数据结构.md ├── iOS自动化布局-AutoLayout约束优先级.md └── iOS进阶导图.md ├── iOS逆向安防 ├── .keep ├── ARM汇编基础(iOS逆向).md ├── iOS符号表恢复&逆向支付宝.md └── 优化iOS小技巧.md └── iOS面试合集 ├── .keep ├── BAT面试分享——iOS开发高级工程师.md ├── Objective-C与Swift的贯通编程.md ├── iOS BAT面试对答题.md ├── iOS188面试题面试题整理,底层、技术亮点公司需要的这里都有.md ├── iOS多线程面试题分析.md ├── iOS常见基础面试题(附参考答案).md ├── iOS底层原理之部分面试题分析.md ├── iOS求职之OC面试题.md ├── iOS经典面试题.md ├── iOS面试十大要点.md ├── iOS面试反思总结.md ├── iOS面试题文案及答案附件.md ├── iOS面试题(十面埋伏).md ├── iOS面试高薪,进阶 .md ├── 【Objective-C】Objective-C语言的动态性.md ├── 【Objective-C】探索Category底层的实质.md ├── 【Objective-C】自定义UITextView.md ├── 关于 iOS 性能优化方面的面试题.md ├── 如何一举拿下大厂Offer面经(附面试题).md ├── 金三银四涨薪季,是否有把握住?.md └── 面试题 拓展:常用框架和第三方框架.md /README.md: -------------------------------------------------------------------------------- 1 | # iOS工程师飞升秘籍 2 | 3 | ## 这个栏目将持续更新--请iOS的小伙伴关注! 4 | ● 整理不易,如果你觉得还不错,麻烦 “Star”一下,谢谢你的支持 5 | #### 介绍 6 | 7 | 定位 8 | 9 | ● 初级iOS开发说明:作为一名初级的iOS开发,你需要具备以下技能 10 | 11 | ○ 必备技能(全部都会的情况下查看下一项)Xcode的使用 12 | ■ 第三方库的灵活使用AFN 13 | ● MJRefresh 14 | ■ 各种网站的使用 15 | ○ 如何判断是否可以升阶是否了解AFNetworking 的实现原理 16 | ■ 是否了解SDAutolayout/Masonry 一种布局库的原理 17 | ■ 是否能够处理基本的iOS崩溃原因/无法编译原因/无法上架原因? 18 | ■ 是否拥有了一定的工作效率,稳定的工作效率.(而不是说,上面派了一个活下来,忙都忙不完,天天加班,还一堆bug) 19 | ■ 是否能够处理第三方库引起的崩溃. 20 | ■ 是否可以很好的融入工作环境,完成每一阶段的工作指标,而不会让自己疲惫不堪. 21 | ○ 结论iOS中级开发说白了,就是你学会了基本的UI界面搭建,上架,沉淀一段时间,你觉得自己还适合这门行业,还适合,还能接受 这个所谓的iOS开发工程师的行业.你就可以说是一名中级iOS开发. 22 | ■ 这个沉淀时间 大约在1年的实际工作中,就可以完成. 23 | ■ 如果你觉得这门行业不适合你,请仔细结合自身情况,是否转另一门计算机语言,还是彻底转行. 24 | ● 中级iOS开发说明: 作为一名中级的iOS开发,你需要具备以下技能 25 | 26 | ○ 必备技能(全部都会的情况下查看下一项)应用的内存处理 27 | ■ 应用的推送处理 28 | ■ 应用的模块化/单元测试 29 | ■ 应用的第三方集成/集中化管理/稳定迭代 30 | ■ 阅读强大的第三方源码/拥有快速上手新的第三方库的能力. 31 | ■ 能够接受各种新功能的开发(这里是指,即使你没有做过,但是你仍然可以凭借着学习,解决任何业务需求:例如:蓝牙.AR.摄像头.硬件交互.等) 32 | ■ 清楚明白数据的传递方式,应用与后台如何交换数据,交换数据的过程,结果,格式. 33 | ■ 多线程的灵活使用. 34 | ■ 各种并发事件的处理/以及界面的合理性/流畅度 35 | ■ 设计模式的灵活使用. 36 | ○ 如何判断是否可以升阶 37 | ○ 结论 38 | 39 | ● 高级iOS开发说明:作为一名高级的iOS开发,你需要具备以下技能(我不是高级开发,所以这里只能给你们提供建议.) 40 | 41 | ○ 必备技能应用的组件化/架构分层 42 | ■ 数据结构,操作系统,计算机网络都有自己的了解和认知 43 | ■ Shell脚本/python/Ruby/JS 至少会一种. 44 | 规划 45 | 根据以上描述iOS开发的你现在处于哪个阶段?以下是给你定义的方向和规划! 46 | 47 | 1、 架构师 48 | 2、 安全攻防 49 | 3、 逆向编程 50 | 4、 iOS进阶 51 | 5、 底层开发 52 | 6、 音视频开发等等 53 | 54 | 此技术栏目将持续更新,如果对你有帮助,记得收藏一下; 55 | * 更多iOS中高级【技术资料+面试资料】获取加 iOS交流群:642363427 56 | 57 | ![](https://images.gitee.com/uploads/images/2021/0512/152657_e54f7b93_9027123.gif ) 58 | 59 | 60 | 61 | 62 | 63 | 64 | #### 特技 65 | 66 | 1. 视频秘籍:https://www.bilibili.com/video/BV1v54y1h7M7 67 | 68 | ## 欢迎关注 69 | 一个有温度的微信公众号,期待与你共同进步,分享美文,分享各种iOS学习资源 70 | 71 | ![](https://images.gitee.com/uploads/images/2021/0512/152536_2929948d_9027123.gif) 72 | -------------------------------------------------------------------------------- /Swift/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/Swift/.keep -------------------------------------------------------------------------------- /Swift/OpenStack存储设施----Swift.md: -------------------------------------------------------------------------------- 1 | Swift为OpenStack提供一种分布式、持续虚拟对象存储,它类似于Amazon Web Service的S3简单存储服务。Swift具有跨节点百级对象的存储能力。Swift内建冗余和失效备援管理,也能够处理归档和媒体流,特别是对大数据(千兆字节)和大容量(多对象数量)的测度非常高效。 2 | 3 | # [更多分享专研](https://docs.qq.com/doc/DS0FCdlZORUpieGFh) 4 | 5 | swift功能及特点:   6 | 海量对象存储 7 | 大文件(对象)存储 8 | 数据冗余管理 9 | 归档能力-----处理大数据集 10 | 为虚拟机和云应用提供数据容器 11 | 处理流媒体 12 | 对象安全存储 13 | 备份与归档 14 | 良好的可伸缩性 15 | 16 | Swift组件: 17 | --Swift账户 18 | --Swift容器 19 | --Swift对象 20 | --Swift代理 21 | --Swift RING 22 |    23 | Swift代理服务器   24 | --用户都是通过Swift-API与代理服务器进行交互,代理服务器正是接收外界请求的门卫,它检测合法的实体位置并路由它们的请求。 25 | --此外,代理服务器也同时处理实体失效而转移时,故障切换的实体重复路由请求。 26 | 27 | Swift对象服务器 28 | --对象服务器是一种二进制存储,它负责处理本地存储中的对象数据的存储、检索和删除。对象都是文件系统中存放的典型的二进制文件,具有扩展文件属性的元数据(xattr)。 29 | --注意:xattr格式被Linux中的ext3/4,XFS,Btrfs,JFS和ReiserFS所支持,但是并没有有效测试证明在XFS,JFS,ReiserFS,Reiser4和ZFS下也同样能运行良好。不过,XFS被认为是当前最好的选择。 30 | 31 | Swift容器服务器 32 | --容器服务器将列出一个容器中的所有对象,默认对象列表将存储为SQLite文件(译者注:也可以修改为MySQL,安装中就是以MySQL为例)。容器服务器也会统计容器中包含的对象数量及容器的存储空间耗费。 33 | 34 | Swift账户服务器 35 | --账户服务器与容器服务器类似,将列出容器中的对象。 36 | 37 | Ring(索引环) 38 | --Ring容器记录着Swift中物理存储对象的位置信息,它是真实物理存储位置的实体名的虚拟映射,类似于查找及定位不同集群的实体真实物理位置的索引服务。这里所谓的实体指账户、容器、对象,它们都拥有属于自己的不同的Rings。 39 | -------------------------------------------------------------------------------- /Swift/Swfit可空(Optional)类型基础.md: -------------------------------------------------------------------------------- 1 | 可空类型,对于熟悉C#的同学一定不会陌生。在C#里面值类型都是不能为空的,比如int类型默认为0,bool默认为false。但是我们给int加上?后,就是一个可空类型了。 2 | 3 | 那么Swfit里面呢。Swift里面没有区分值类型,引用类型(或许有,可能我还没看到这方面的资料)。那这个可空是怎么回事呢。原来在Swfit里变量默认是不为空的。看代码: 4 | 5 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0602/153439_6b3d6f76_9027123.png "op1.png") 6 | 7 | 你给一个String类型的变量付空值nil是会报错的。 8 | 9 | 那怎么让一个变量能为空呢,做法跟C#一样,加一个?。看代码: 10 | 11 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0602/153454_02a2a76f_9027123.png "op2.png") 12 | 13 | 那怎么取可空变量的值呢。这里就得拆解(unwarp)的概念了。比如你直接使用上面的name赋值给另外一个name变量的话是会报错的。要使用!取出其中的值。 14 | 15 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0602/153507_eba86690_9027123.png "op3.png") 16 | 17 | 既然变量是可空,那么我们使用的时候就免不了判空。判空也跟C#一样。 18 | 19 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0602/153520_65cf2963_9027123.png "op4.png") 20 | 21 | 这里有2个问题要提一下。 22 | 就是当你用 !=去判断不为nil的时候,一定要注意了。一定要在!=两边加上空格。不然是会报错的。因为如果不加空格的话,编译器没法区分是对变量进行拆解还是取非运算。 23 | 24 | 还有一个,对于非空类型的变量,是直接不能进行判空的。因为上面说了,变量默认是不能为空的,所以非空类型的变量去判空是没有意义的。这里对于习惯C#/JAVA的同学就会比较坑了。因为我们已经养成了有null风险的地方进行null检查。如下: 25 | 26 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0602/153546_8127937a_9027123.png "op5.png") 27 | 28 | 一点思考: 29 | 30 | 这里还是跟C#进行一下比较。 31 | 32 | .NET C#里类型分值类型,引用类型。 33 | 34 | 引用类型的变量全部可以为空。值类型不能为空。 35 | 如果想要为空,那就是用可空类型。 36 | 37 | 那我们开发的时候,可以发现其实使用引用类型的时候比使用值类型的时候多多了。 38 | 因为除了FCL的Class,我们自定义的各种Class也都是引用类型。 39 | 但是Swift却正好相反,Swift里变量默认是不能为空的。 40 | 也就是跟C#里的值类型一样,都要给默认值。 41 | 需要空的时候就使用可空来处理。 42 | 这里也可以看出来2门语言的设计者不同的两种思路。 43 | 44 | C#觉得对象的属性/变量大部分时候是存在可空需求的。 45 | 而Swift觉得对象的属性大部分时候是不存在可空需求的。 46 | 47 | C#里变量想空就空,比较灵活。 48 | 但是这样也造成在使用的时候不得不小心翼翼,所以我们到处都是if(obj!=null)这种代码。 49 | 50 | Swfit的话就不用到处Check null,但是不够灵活。 51 | 比如当我发现某个属性有可能为空的时候,还要去修改声明,修改完声明,前面使用到这个变量的地方的代码全都要改,要判空,要拆解。 52 | 这两种方案可以说各有利弊,不过个人比较偏向C#。 53 | -------------------------------------------------------------------------------- /Swift/Swift、OC分别实现用_ _ _隔开数组且只显示一行的小功能.md: -------------------------------------------------------------------------------- 1 | **直接上代码** 2 | 3 | **一、Swift实现** 4 | 5 | **1、让 标签的字体显示灰色的实现** 6 | 7 | ``` 8 | let str = getStr(of: object) 9 | let attrStr = NSMutableAttributedString.init(string: str) 10 | 11 | for i in 0.. String { 29 | 30 | guard let madeAry = product else { return "" } 31 | if product.count == 1 { 32 | return product[0] as? String ?? "" 33 | } 34 | 35 | var str = "" 36 | var str_limit = "" 37 | for (index, item) in madeAry.enumerated() { 38 | if let a = item as? String { 39 | var l = "" 40 | if index == madeAry.count - 1 { 41 | l = "" 42 | } else { 43 | l = " | " 44 | } 45 | str_limit = str 46 | str = str + a + l 47 | let nsStr: NSString = str as NSString 48 | let item_width = nsStr.width(for: UIFont.nn_font(ofSize: 12)) 49 | if item_width > ScreenWidth - 121 { 50 | var end_str = l 51 | if index == madeAry.count - 1,index > 0 { 52 | end_str = " | " 53 | } 54 | str = String(str_limit.prefix(str_limit.count - end_str.count)) 55 | if str.count == 0 { 56 | str = product[0] as? String ?? "" 57 | } 58 | break 59 | } 60 | } 61 | } 62 | 63 | return str 64 | } 65 | ``` 66 | 67 | **3、实现效果** 68 | 69 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/153638_2482d7f2_9027123.png "小1.png") 70 | 71 | **二、OC实现** 72 | 73 | **1、让 标签的字体显示灰色的实现** 74 | 75 | ``` 76 | NSString * str = [self getStr:object]; 77 | NSMutableAttributedString * attrStr = [[NSMutableAttributedString alloc] initWithString:str]; 78 | for (int i = 0; i < str.length; i++) { 79 | NSString * temp = [str substringWithRange:NSMakeRange(i, 1)]; 80 | if ([temp isEqualToString:@"|"]) { 81 | [attrStr addAttribute:NSForegroundColorAttributeName value: RGBA_COLOR_HEX(0xDBDBDB,1) range:NSMakeRange(i, 1)]; 82 | } 83 | } 84 | 85 | self.testLable.text = nil; 86 | self.testLable.attributedText = attrStr; 87 | ``` 88 | 89 | **2、把数组用" | "隔开且最多只显示一行的算法getStr** 90 | 91 | ``` 92 | - (NSString *)getMadeinStr:(NSArray *)object { 93 | if (object..count == 0){ 94 | return @""; 95 | } else if (object.count == 1) { 96 | return object[0]; 97 | } 98 | 99 | NSString * str = @""; 100 | NSString * str_limit = @""; 101 | for (int i = 0; i < object.count; i++) { 102 | NSString * l = @""; 103 | NSString * a = object[i]; 104 | if (i == object.count - 1) { 105 | l = @""; 106 | } else { 107 | l = @" | "; 108 | } 109 | CGFloat width = (kScreenWidth-31)/2 - 20 ; 110 | str_limit = str; 111 | str = [NSString stringWithFormat:@"%@%@%@",str,a,l]; 112 | 113 | CGFloat str_width = [str widthForFont: [UIFont systemFontOfSize:12]]; 114 | 115 | CGFloat item_width = str_width; 116 | if (item_width > width){ 117 | 118 | NSString * endStr = l; 119 | if ((i == object.count - 1) && i > 0 ) { 120 | endStr = @" | "; 121 | } 122 | 123 | str = [str_limit substringWithRange:NSMakeRange(0, str_limit.length - endStr.length)]; 124 | if (str.length == 0) { 125 | str = object[0]; 126 | } 127 | 128 | break; 129 | } 130 | } 131 | 132 | return str; 133 | 134 | } 135 | 136 | //NSString计算宽度的分类 137 | - (CGFloat)widthForFont:(UIFont *)font { 138 | CGSize size = [self sizeForFont:font size:CGSizeMake(HUGE, HUGE) mode:NSLineBreakByWordWrapping]; 139 | return size.width; 140 | } 141 | 142 | - (CGSize)sizeForFont:(UIFont *)font size:(CGSize)size mode:(NSLineBreakMode)lineBreakMode { 143 | CGSize result; 144 | if (!font) font = [UIFont systemFontOfSize:12]; 145 | if ([self respondsToSelector:@selector(boundingRectWithSize:options:attributes:context:)]) { 146 | NSMutableDictionary *attr = [NSMutableDictionary new]; 147 | attr[NSFontAttributeName] = font; 148 | if (lineBreakMode != NSLineBreakByWordWrapping) { 149 | NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new]; 150 | paragraphStyle.lineBreakMode = lineBreakMode; 151 | attr[NSParagraphStyleAttributeName] = paragraphStyle; 152 | } 153 | CGRect rect = [self boundingRectWithSize:size 154 | options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading 155 | attributes:attr context:nil]; 156 | result = rect.size; 157 | } else { 158 | #pragma clang diagnostic push 159 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 160 | result = [self sizeWithFont:font constrainedToSize:size lineBreakMode:lineBreakMode]; 161 | #pragma clang diagnostic pop 162 | } 163 | return result; 164 | } 165 | 166 | ``` 167 | 168 | 169 | 170 | -------------------------------------------------------------------------------- /Swift/Swift中构造方法的解析.md: -------------------------------------------------------------------------------- 1 | ### 一、引言 2 | 3 |       构造方法是一个类创建对象最先也是必须调用的方法,在Objective-C中,开发者更习惯称这类方法为初始化方法。在Objective-C中的初始化方法与普通函数相比除了要以init抬头外并无太严格的分界,而在Swift语言体系中,构造方法与普通的方法分界十分严格,从格式写法上就有不同,普通方法函数要以func声明,构造方法统一为init命名,不需要func关键字声明,不同的构造方法采用方法重载的方式创建。 4 | 5 | ### 二、构造方法的复写与重载 6 | 7 |      在Objective-C中,不同的初始化方法就是不同的函数,这便不存在方法重载的概念。Swift中要创建自定义的构造方法,需要开发者对init构造方法进行重载操作。任何一个自定义的类,只要其有父类,除了可以继承下来父类已有的构造方法外,还可以复写父类的构造方法,使其适用于自身。和Objective-C类似,复写父类的构造方法时,要在其中调用父类的构造方法,重载可以理解为一种特殊的复写父类构造方法,因此在重载的构造方法中也要调用父类的构造方法。 8 | 9 | 创建一个继承于NSObject的类,复写构造方法,代码示例如下: 10 | 11 | class ClassOne: NSObject { 12 | 13 | //声明一个本类特有的常量 14 | 15 | var tip:Int 16 | 17 | //复写父类的构造方法 需要用override关键字 18 | 19 | override init() { 20 | 21 | //构造方法中要对所有成员常量完成创建 22 | 23 | tip = 1; 24 | 25 | //在创建完所有成员常量后 调用父类构造方法 26 | 27 | super.init() 28 | 29 | } 30 | 31 | //重载构造方法1 32 | 33 | init(one:Int){ 34 | 35 | tip=one 36 | 37 | super.init() 38 | 39 | } 40 | 41 | //重载构造方法2 使用convenience关键字进行修饰 42 | 43 | convenience init(two:String) { 44 | 45 | //使用convenience关键字进行修饰的构造方法要调用本类的构造方法进行 46 | 47 | self.init(one: two.characters.count) 48 | 49 | } 50 | 51 | //重载构造方法3 使用required关键字进行修饰 使用required关键字进行修饰的构造方法子类必须继承或复写 52 | 53 | required init(three:Float) { 54 | 55 | tip=10 56 | 57 | super.init() 58 | 59 | } 60 | 61 | 上面示例代码中,不带参数的init()方法为复写父类的方法,因此需要使用关键字override来修饰。重载构造方法1带一个Int类型的 参数,父类中并没有这个构造方法,但是在其实现中,依然需要调用父类中的某个构造方法完成。构造方法2是一个带String类型参数的构造方法,其用convenience关键字为构造方法的一个修饰关键字,后面会介绍。构造方法3为一个带Float类型参数的构造方法,但其使用required关键字进行了修饰,使用required关键字进行修饰的构造方法子类必须继承或者复写。构造方法1,2,3都是对init()构造方法的一种重载,但却是3中类型全然不同的构造方法。 62 | 63 | ### 三、Designated构造方法与Convenience构造方法 64 | 65 |       Swift中的构造方法分为Designated构造方法与Convenience构造方法两类,Designated构造方法也被称为指定构造方法,Convenience构造方法也被称为方便构造方法。Designated构造方法不加任何修饰关键字,Convenience构造方法需要使用Convenience关键字进行修饰。可以这样理解,Convenience类型的构造方法是为了方便使用从Designated构造方法中分支出来的构造方法,官方文档中有如下描述: 66 | 67 | 1.子类Designated构造方法中必须调用父类的Designated构造方法。 68 | 69 | 2.Convenience构造方法中必须调用当前类的构造方法。 70 | 71 | 3.Convenience构造方法归根结底要调用到Designated构造方法。 72 | 73 | 官方文档的一张图可以清晰的描述上述关系: 74 | 75 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/154454_daf82ba2_9027123.png "借.png") 76 | 77 | 78 | ### 四、构造方法的继承关系 79 | 80 | 关于子类继承父类的构造方法有这样几个特性: 81 | 82 | 1.如果子类没有复写任何父类的构造方法,则默认子类将继承所有父类的构造方法,包括Designated构造方法与Convenience构造方法。 83 | 84 | 2.如果子类复写了父类某一构造方法,则子类默认不在继承所有父类的构造方法,对于Designated类型的构造方法,子类复写了哪些,哪些才能够被使用,对于Convenienve类型的构造方法,子类复写的其调用的Designated构造方法后会被自动继承。 85 | 86 | 3.如果父类中的构造方法是required修饰的,则子类必须进行继承或复写。 87 | 88 |       曾经有朋友和我抱怨,Objective-C中的继承是一种十分不人性,它强制子类继承所有父类的方法与属性无论子类是否需要,分析上面的一些规则可以发现,Swift与Objective-C相比,在构造方法方面语法会更加严格,这样做在编程上更加安全。在Objective-C中,子类将被强制继承所有父类的初始化方法,这样开发者在使用时常常会出现疑惑,有时一个子类往往有特定的初始化方法,仅仅通过父类的初始化方法不能够正确的完成初始化,在编程时,往往需要特殊注释来提示开发者。Swift设定的这些构造方法原则可以将无关的父类构造方法剔除在外,在编程时更加严格安全,减少疑惑与不可控因素。 89 | 90 | ### 五、构造方法的实现原则 91 | 92 |       无论Designated类型的构造方法还是Convenience类型的构造方法,只要其有父类,最终都要实现父类的Designated构造方法。Swift语言要求,在构造方法中要完成所有成员常量或者变量的构造或赋值(optional值除外)。在对成员常量或变量进行构造赋值时,要在调用父类的初始化方法之前,这里还有一点需要注意,父类的成员属性也会被子类继承,如果要在子类复写的父类方法中对继承来的父类成员属性进行重新构造或赋值,则必须在调用父类构造方法之后,例如创建ClassTwo类继承于ClassOne,复写方法如下: 93 | 94 | class ClassTwo: ClassOne { 95 | 96 | //子类自己的属性 97 | 98 | let tipTwo:Int 99 | 100 | override init() { 101 | 102 | //调用父类构造方法前进行自己属性的构造 103 | 104 | tipTwo = 1 105 | 106 | //调用父类构造方法 107 | 108 | super.init() 109 | 110 | //对从父类继承来的属性进行重构造 111 | 112 | tip = 1000; 113 | 114 | } 115 | 116 | 117 | 118 | required init(three: Float) { 119 | 120 | fatalError("init(three:) has not been implemented") 121 | 122 | } 123 | 124 | 125 | 126 | } 127 | 128 | Swift语言这种强制化得构造规则,能够保证一个类在完成构造时,其内部的所有属性都构造完成。在使用Objective-C进行开发时,很多初学者都可能会遇到这样一种情况,完成了某个类的初始化,但向类的属性进行赋值时却没有成功,因为Objective-C中并没有这样的语法,在类初始化成功后,其属性是否初始化了完全取决于开发者,Swift优化了这一设计。 129 | 130 |     综上可以了解,Swift语言虽然更加严格,却将更多本来需要开发者注意的地方交由了编译器,实际上是减轻了开发者的负担。 131 | -------------------------------------------------------------------------------- /Swift/Swift开发之泛型实例.md: -------------------------------------------------------------------------------- 1 | ## 一、Swift泛型 2 | 3 |   泛型能够让开发者编写自定义需求已经任意类型的灵活可用的的函数和类型。能够让我们避免重复的代码。用一种清晰和抽象的方式来表达代码的意图。 4 | ``` 5 | func swapTwoStrings(_ a: inout String, _ b: inout String) { let temporaryA = a a = b b = temporaryA} func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { let temporaryA = a a = b b = temporaryA} 6 | ``` 7 |   从以上代码来看,它们功能代码是相同的,只是类型上不一样,这时我们可以使用泛型,从而避免重复编写代码。 8 | 9 |   泛型使用了占位类型名(在这里用字母 T 来表示)来代替实际类型名(例如 Int、String 或 Double)。 10 | 11 | ``` 12 | func swapTwoValues(_ a: inout T, _ b: inout T) 13 | ``` 14 | 15 |   **备注:这个函数功能是用来交换两个同样类型的值,但是这个函数用T占位符来代替实际的类型。并没有指定具体的类型,但是传入的a,b必须是同一个类型T。在调用这个函数d的时候才能指定T是哪种具体的类型。还有函数名后面跟的那个是函数定义的一个占位符类型名,并不会查找T的具体类型。使用该函数只要保证传入的两个参数是同一个类型,就不用根据传入参数的类型不同,而写不同的方法。** 16 | 17 |   看泛型在代码用,是如何使用的。 18 | 19 | ``` 20 | override func viewDidLoad() { super.viewDidLoad() //swift泛型 var num1 = 100 var num2 = 200 print("交换前数据: \(num1) 和 \(num2)") swapTwoValue(&num1, &num2) print("交换后数据: \(num1) 和 \(num2)") var str1 = "ABC" var str2 = "abc" print("交换前数据: \(str1) 和 \(str2)") swapTwoValue(&str1, &str2) print("交换后数据: \(str1) 和 \(str2)") } func swapTwoValue(_ a : inout T,_ b : inout T) { let temp = a a = b b = temp } 21 | ``` 22 | 23 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152553_066878dc_9027123.png "Swift1.png") 24 | 25 | 26 | ## 二、类型约束 27 | 28 |   泛型约束大致分为以下几种: 29 | 30 | **  (1)继承约束,泛型类型必须是某个类的子类型** 31 | 32 | **  (2)协议约束,泛型类型必须遵循某些协议** 33 | 34 | **(3)条件约束,泛型类型必须满足某种条件** 35 | 36 | ``` 37 | func someFunction(someT: T, someU: U) { // 这里是泛型函数的函数体部分} 38 | ``` 39 | 40 | ###    1、继承约束 41 | 42 | ``` 43 | //定义一个父类,动物类class Animal{ //动物都会跑 func run(){ print("Animal run") }}//定义狗类,继承动物类class Dog: Animal { override func run(){//重写父类方法 print("Dog run") }}//定义猫类,继承动物类class Cat: Animal { override func run(){//重写父类方法 print("Cat run") }}//定义泛型函数,接受一个泛型参数,要求该泛型类型必须继承Animalfunc AnimalRunPint(animal:T){ animal.run() //继承了Animal类的子类都有run方法可以调用}AnimalRunPint(animal: Dog())AnimalRunPint(animal: Cat()) 44 | ``` 45 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152603_596d98e3_9027123.png "Swift2.png") 46 | 47 | ###   2、协议约束 48 | 49 | ``` 50 | //定义泛型函数,为泛型添加协议约束,泛型类型必须遵循Equatable协议func findIndex(array: [T], valueToFind: T) -> Int? { var index = 0 for value in array { if value == valueToFind {//因为遵循了Equatable协议,所以可以进行相等比较 return index } else { index += 1 } } return nil}//在浮点型数组中进行查找,Double默认遵循了Equatable协议let doubleIndex = findIndex(array: [3.14159, 0.1, 0.25], valueToFind: 9.3)if let index = doubleIndex { print("在浮点型数组中寻找到9.3,寻找索引为\(index)")} else { print("在浮点型数组中寻找不到9.3")}//在字符串数组中进行查找,String默认遵循了Equatable协议let stringIndex = findIndex(array: ["Mike", "Malcolm", "Andrea"], valueToFind: "Andrea")if let index = stringIndex { print("在字符串数组中寻找到Andrea,寻找索引为\(index)")} else { print("在字符串数组中寻找不到Andrea")} 51 | ``` 52 | 53 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152703_a52974fc_9027123.png "Swift3.png") 54 | 55 | ###   3、条件约束 56 | 57 | ``` 58 | //添加泛型条件约束,C1和C2必须遵循Stackable协议,而且C1和C2包含的泛型类型要一致func pushItemOneToTwo( stackOne: inout C1, stackTwo: inout C2) where C1.ItemType == C2.ItemType{//因为C1和C2都遵循了Stackable协议,才有ItemType属性可以调用 let item = stackOne.pop() stackTwo.push(item: item)}//定义另外一个结构体类型,同样实现Stackable协议,实际上里面的实现和Stack一样struct StackOther: Stackable{ var store = [T]() mutating func push(item:T){//实现协议的push方法要求 store.append(item) } mutating func pop() -> T {//实现协议的pop方法要求 return store.removeLast() }}//创建StackOther结构体,泛型类型为Stringvar stackTwo = StackOther()stackTwo.push(item: "where")//虽然stackOne和stackTwo类型不一样,但泛型类型一样,也同样遵循了Stackable协议pushItemOneToTwo(stackOne: &stackOne, stackTwo: &stackTwo )print("stackOne = \(stackOne.store), stackTwo = \(stackTwo.store)")//打印:stackOne = ["hello"], stackTwo = ["where", "swift"] 59 | ``` 60 | 61 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152727_2aa230ee_9027123.png "Swift4.png") 62 | 63 | -------------------------------------------------------------------------------- /Swift/Swift进阶之路(一)——单例模式、属性传值、代理传值、闭包传值.md: -------------------------------------------------------------------------------- 1 | ## 一、单例模式 2 | 3 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/153418_fe0cb814_9027123.png "进阶.png") 4 | 5 |   单例模式是设计模式中最简单的一种,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象。 6 | 7 |   关于单例,有三个重要的准则需要牢记: 8 | 9 |   1\. 单例必须是唯一的(要不怎么叫单例?) 在程序生命周期中只能存在一个这样的实例。单例的存在使我们可以全局访问状态。例如:NSNotificationCenter, UIApplication和NSUserDefaults。 10 | 11 |   2\. 为保证单例的唯一性,单例类的初始化方法必须是私有的。这样就可以避免其他对象通过单例类创建额外的实例。 12 | 13 |   3\. 考虑到规则1,为保证在整个程序的生命周期中值有一个实例被创建,单例必须是线程安全的。并发有时候确实挺复杂,简单说来,如果单例的代码不正确,如果有两个线程同时实例化一个单例对象,就可能会创建出两个单例对象。也就是说,必须保证单例的线程安全性,才可以保证其唯一性。 14 | 15 | 16 | 17 | ###   实例:创建单例的两种方式 18 | 19 | ``` 20 | import UIKit 21 | //final修饰符:可以防止类被继承,还可以防止子类重写父类的属性、方法以及下标。该修饰符不能修饰结构体和枚举。 22 | final class SingleClass: NSObject 23 | { 24 | //使用static修饰符,定义一个静态常量。静态常量在实例调用结束后不会消失,并且保留原值,即其内存不会被释放。当下次调用实例时,仍然使用常量原有的值。 25 | static let shared = SingleClass() 26 | //为了保持一个单例的唯一性,单例的构造器必须是private的。以防止其他对象也能创建出单例类的实例 27 | private override init() {} 28 | 29 | func say() 30 | { 31 | print("Hello, CoolKeTang!") 32 | } 33 | } 34 | SingleClass.shared.say() 35 | 36 | //第二种 37 | final class SecondSingletonClass: NSObject 38 | { 39 | //使用static修饰符,定义一个静态变量。 40 | static var shared: SecondSingletonClass 41 | { 42 | //借助结构体来存储类型变量(class var),并使用let修饰符来保证线程的安全 43 | struct Static 44 | { 45 | static let instance: SecondSingletonClass = SecondSingletonClass() 46 | } 47 | return Static.instance 48 | } 49 | //为了保持一个单例的唯一性,单例的构造器必须是私有的,以防止其它对象也能创建出单例类的实例 50 | private override init() {} 51 | 52 | func say() 53 | { 54 | print("Hello, CoolKeTang!") 55 | } 56 | } 57 | ``` 58 | 二、Swift的三种传值方式 59 |   第一种:属性传值 60 |   属性传值很简单,适用于 从第一级传入第二级(正向传递) 61 | ``` 62 | //在要进入的控制器定义属性 63 | class SecViewController: UIViewController { 64 | var labT = "" 65 | } 66 | 67 | //在一级控制器中给二级控制器赋值 68 | let secVC = SecViewController() 69 | secVC.labT = "属性传值" 70 | ``` 71 | 第二种:代理传值 72 | (适用于逆向传值)二级到一级 73 | ``` 74 | //在二级控制器定义代理协议 75 | protocol SecViewControllerDelegate{ 76 | 77 | func SecDelegateSendValue(str:String) 78 | } 79 | 80 | class SecViewController: UIViewController { 81 | var labT = "" 82 | // 设置代理属性 83 | var delegetes:SecViewControllerDelegate! = nil 84 | //调用代理方法 85 | if (self.delegetes != nil) { 86 | self.delegetes.SecDelegateSendValue(str: "代理传值") 87 | } 88 | //在一级控制器遵循协议 89 | class ViewController: UIViewController,SecViewControllerDelegate 90 | //实现协议方法 91 | func SecDelegateSendValue(str:String) 92 | { 93 | prolab.text = str 94 | } 95 | //成为代理者 96 | secVC.delegetes = self 97 | ``` 98 | 第三种:闭包传值 99 | (本质是OC中的代码块传值) 100 | ``` 101 | // 定义闭包 102 | typealias callBackFunc = (_ text:String) ->Void 103 | //创建闭包对象 104 | var selCallBack:callBackFunc? 105 | //调用闭包 106 | if (selCallBack != nil) { 107 | selCallBack!("代码块传值") 108 | } 109 | //在一级界面 获取闭包传的值 110 | let secVC = SecViewController() 111 | secVC.labT = "属性传值" 112 | 113 | secVC.selCallBack = {(str:String) in 114 | 115 | self.Blocklab.text = str 116 | } 117 | ``` 118 | 119 | -------------------------------------------------------------------------------- /Swift/【Swift实现代码】iOS架构模式之MVP.md: -------------------------------------------------------------------------------- 1 | 1.什么是MVP? 2 | 3 | MVP是模型(Model)、视图(View)、主持人(Presenter)的缩写,分别代表项目中3个不同的模块。  4 | 5 | 1.1 模型 (Model):负责处理数据的加载或存储 6 | 7 | 1.2 视图 (View):负责界面数据的展示与用户交互 8 | 9 | 1.3 主持人(Presenter):是Model和View之间的桥梁,将两者进行链接。 10 | 11 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152852_2a841d05_9027123.jpeg "mbp.jpg") 12 | 13 | 14 | 整个交互流程看起来大致是这样的: 15 | 16 | 用户交互->View获得交互事件->View将事件转发给Presenter->Presenter调用Model获取新数据->Presenter将数据推送给View进行展示 17 | 18 | 案例1: 19 | 20 | 这里我们用app开发中常用的登录功能为例,用mvp来实现一个登录逻辑(功能)。既然用MVP 那么我们得新建三个类即:LoginModel,LoginPresenter,LoginView 21 | 22 | ``` 23 | class loginPresenter: NSObject { 24 | //声明V和M2个属性,其中的V中写了代理,待优化 25 | private var loginViewDelegate:LoginViewDelegate? 26 | private var loginModel:LoginModel? 27 | 28 | //实例化 29 | override init() { 30 | //model实例化 31 | self.loginModel = LoginModel() 32 | } 33 | 34 | //V层调用这个login方法,这个方法再调用M层的login方法 35 | func login(usrName: String, pwd: String) { 36 | self.loginModel?.login(usrName: usrName, pwd: pwd, callback: { (result) in 37 | //从m层的的回调,回调到v层去,同样还是通过一个代理实现 38 | self.loginViewDelegate?.onLoginResult(result: result) 39 | }) 40 | } 41 | 42 | //绑定V和P 43 | func attachView(viewDelegate:LoginViewDelegate) { 44 | self.loginViewDelegate = viewDelegate 45 | } 46 | 47 | //解除绑定,假如网络请求是,viewController已经释放,则无需再回调更新UI 48 | func detachView() { 49 | self.loginViewDelegate = nil 50 | } 51 | 52 | } 53 | ``` 54 | 55 | Presenter 56 | 57 | ``` 58 | import Foundation 59 | //M层 60 | class LoginModel: NSObject { 61 | //登陆的方法,P层调用这个方法来发起登陆请求 62 | func login(usrName:String,pwd:String,callback:((String)->Void)) { 63 | //发起网络请求 处理方法要封装,不能耦合 64 | print("进入model") 65 | //调用网络模块方法 66 | HttpUtils.post(usrName: usrName, pwd: pwd) { (result) in 67 | //1,处理网络返回的情况,如:登录成功要缓存个人信息 68 | //...... 69 | //2,完成登录数据处理,回调给P层,这里不与UI部分耦合 70 | callback(result) 71 | } 72 | 73 | } 74 | 75 | } 76 | ``` 77 | 78 | Model 79 | 80 | ``` 81 | import UIKit 82 | //遵循LoginViewDelegate协议 83 | class ViewController: UIViewController,LoginViewDelegate { 84 | 85 | //定义一个presenter,实例化 86 | private let presenter = loginPresenter() 87 | 88 | override func viewDidLoad() { 89 | super.viewDidLoad() 90 | //添加v和p2层的绑定 91 | self.presenter.attachView(viewDelegate: self) 92 | //UI层交互操作,发起登录请求 93 | self.presenter.login(usrName: "ZZB", pwd: "123456") 94 | 95 | } 96 | 97 | //依据P层的回调数据进行V层UI更新 98 | func onLoginResult(result: String) { 99 | print("处理P层返回的数据: \(result),更新UI") 100 | } 101 | 102 | override func didReceiveMemoryWarning() { 103 | super.didReceiveMemoryWarning() 104 | //页面注销的时候解除绑定 105 | self.presenter.detachView() 106 | } 107 | 108 | } 109 | ``` 110 | 111 | ViewController 112 | 113 | 案例2: 114 | 115 | ``` 116 | class ViewController: UIViewController { 117 | 118 | fileprivate lazy var presenter : ViewPresenster = { 119 | return ViewPresenster(presenter: self) 120 | }() 121 | 122 | override func viewDidLoad() { 123 | super.viewDidLoad() 124 | } 125 | 126 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 127 | presenter.getData() 128 | } 129 | } 130 | 131 | // MARK:- 获取数据// 132 | extension ViewController:ViewPresensterProtocol{ 133 | 134 | func showPost(_ resulet: [DCModel]) { 135 | print(resulet) 136 | } 137 | } 138 | ``` 139 | 140 | ViewController 141 | 142 | ``` 143 | import UIKit 144 | 145 | protocol ViewPresensterProtocol { 146 | func showPost(_ resulet: [DCModel]) 147 | } 148 | 149 | class ViewPresenster: NSObject { 150 | var presenter: ViewPresensterProtocol! 151 | lazy var model:[DCModel] = [DCModel]() 152 | init(presenter:ViewPresensterProtocol) { 153 | self.presenter = presenter; 154 | } 155 | 156 | func getData(){ 157 | let dict = [ 158 | ["user_id":"1","user_name":"zhaodacai1"], 159 | ["user_id":"2","user_name":"zhaodacai2"], 160 | ["user_id":"3","user_name":"zhaodacai3"], 161 | ["user_id":"4","user_name":"zhaodacai4"], 162 | ["user_id":"5","user_name":"zhaodacai5"], 163 | ["user_id":"6","user_name":"zhaodacai6"], 164 | ["user_id":"7","user_name":"zhaodacai7"] 165 | ] 166 | 167 | for item in dict { 168 | model.append(DCModel(dict: item)) 169 | } 170 | 171 | self.presenter.showPost(model) 172 | } 173 | 174 | } 175 | ``` 176 | 177 | Presenter 178 | 179 | ``` 180 | import UIKit 181 | 182 | class DCModel: NSObject { 183 | 184 | // 用户ID 185 | var user_id : String = "" 186 | 187 | // 用户名字 188 | var user_name : String = "" 189 | 190 | init(dict : [String : Any]) { 191 | super.init() 192 | setValuesForKeys(dict) 193 | } 194 | 195 | override func setValue(_ value: Any?, forUndefinedKey key: String) {} 196 | } 197 | ``` 198 | 199 | -------------------------------------------------------------------------------- /iOS底层进阶/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS底层进阶/.keep -------------------------------------------------------------------------------- /iOS底层进阶/2021 iOS底层提升计划.md: -------------------------------------------------------------------------------- 1 | ### iOS底层提升方案 2 | 3 | 下方学习大纲大家可以参考学习《OC底层、核心编程探索》专栏的索引。 4 | 5 | OC底层探索 6 | 7 | * ## OC对象占用内存原理 8 | 9 | * OC对象 最少占用 16 个字节内存. 10 | 11 | 当对象中包含属性, 会按属性占用内存开辟空间. 每一行 16 个字节中, 剩余内存如果可以放下剩余其中一个属性 (参考倒数第二张图) , 则会在行末存储 (注意: 并非一定是按照定义顺序来开辟空间, 放不下就开辟这样). 放不下时会重新开辟一行存储. 12 | 最终满足 16 字节对齐标准. 13 | 14 | Runtime & 环境搭建 15 | 16 | * ## objective C语言把能在编译期做的事情就推迟到运行期再决定。这就意味着,Objective C不仅需要一个编译器,而且需要一个运行期环境。这个运行期环境就是Runtime。 17 | 18 | * ## runtime源码目录结构 19 | 20 | * `include` 文件夹是我们引入的项目需要的依赖文件 21 | 22 | * `Private Headers` 从字面意思了解,是私有的一些方法 23 | 24 | * `Project Headers` runtime项目中会用到的头文件 25 | 26 | * `Obsolete Headers` 一些`孤立`的文件,大部分可删,只有`hashtable2.h`的文件会被其他文件使用到。 27 | 28 | * `Obsolete Source` 无实质用处,可全删 29 | 30 | * `Source`目录,是runtime的实现文件集合,后面的文章主要是研究这个目录。 31 | 32 | ### 核心编程探索 33 | 34 | [底层进阶Block](https://www.bilibili.com/video/BV13y4y1U7rM) 35 | 36 | [iOS--多线程](https://www.bilibili.com/video/BV1oi4y1j7sP) 37 | 38 | [核心优化](https://www.bilibili.com/video/BV1dT4y1T7Qs) 39 | 40 | [iOS开发底层进阶面试合集](https://www.bilibili.com/video/BV1v54y1h7M7) 41 | 42 | **收录地址:**[mp.weixin.qq.com/s/b0dBzh-Wi…](https://mp.weixin.qq.com/s/b0dBzh-WiO1vMff6U9UxIA) 43 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS Appium框架原理简析.md: -------------------------------------------------------------------------------- 1 | > Appium是目前比较好用的跨平台自动化测试框架,在iOS端采用WebDriverAgent作为webdriver驱动,实现了自动化脚本编写到运行的全流程覆盖。 2 | 3 | 在Xcode 8之前,基于UI Automation的自动化测试方案是比较好用且非常流行的。但在Xcode 8之后,苹果在instruments工具集中直接废除了Automation组件,转而支持使用UI Testing。 4 | 5 | ### UI Testing 6 | 7 | 从Xcode 7开始,苹果提供了UI Testing框架,也就是我们在APP test工程中使用的XCTest的那一套东西。UI Testing包含几个重要的类,分别是XCUIApplication、XCUIElement、XCUIElementQuery。 8 | 9 | * XCUIApplication 10 | 11 | 代表正在测试的应用程序的实例,可以对APP进行启动、终止、传入参数等操作。 12 | 13 | ``` 14 | - (void)launch; 15 | - (void)activate; 16 | - (void)terminate; 17 | @property (nonatomic, copy) NSArray *launchArguments; 18 | @property (nonatomic, copy) NSDictionary *launchEnvironment; 19 | ``` 20 | 21 | XCUIApplication在iOS上提供了两个初始化接口: 22 | 23 | ``` 24 | //Returns a proxy for the application specified by the "Target Application" target setting. 25 | - (instancetype)init NS_DESIGNATED_INITIALIZER; 26 | 27 | //Returns a proxy for an application associated with the specified bundle identifier. 28 | - (instancetype)initWithBundleIdentifier:(NSString *)bundleIdentifier NS_DESIGNATED_INITIALIZER; 29 | 30 | ``` 31 | 32 | 其中initWithBundleIdentifier接口允许传入一个bundle id来操作指定APP。这个技术点是iOS APP能够自动化测试的关键所在。 33 | 34 | * XCUIElement 35 | 36 | 表示界面上显示的UI元素。 37 | 38 | * XCUIElementQuery 39 | 40 | 用于定位UI元素的查询对象。 41 | 42 | 上述几个模块就是一个UI测试框架的核心能力,后面在写Appium的自动化脚本时也是一样的套路:启动APP->定位UI元素->触发操作。 43 | 44 | ### WebDriverAgent 45 | 46 | [WebDriverAgent](https://github.com/facebookarchive/WebDriverAgent)是Facebook开发的基于XCTest.framework的开源项目,实现了在[iOS](https://easeapi.com/blog/tags/iOS.html)上支持WebDriver协议的服务,可以用来启动/终止APP,点击/滑动页面。 47 | 48 | [webdriver协议](https://w3c.github.io/webdriver/)是一套基于HTTP协议的JSON格式规范,协议规定了不同操作对应的格式。之所以需要这层协议,是因为iOS、Android、浏览器等都有自己的UI交互方式,通过这层”驱动层“屏蔽各平台的差异,就可以通过相同的方式进行自动化的UI操作,做网络爬虫常用的selenium是浏览器上实现webdriver的驱动,而WebDriverAgent则是iOS上实现webdriver的驱动。 49 | 50 | 使用Xcode打开WebDriverAgent项目,连接上iPhone设备之后,选中WebDriverAgentRunner->Product->Test,则会在iPhone上安装一个名为WebDriverAgentRunner的APP,这个APP实际上是一个后台应用,直接点击ICON打开的话会退出。 51 | 52 | 具体到代码层面,WebDriverAgentRunner的入口在UITestingUITests.m文件 53 | 54 | ``` 55 | - (void)testRunner 56 | { 57 | FBWebServer *webServer = [[FBWebServer alloc] init]; 58 | webServer.delegate = self; 59 | [webServer startServing]; 60 | } 61 | ``` 62 | 63 | 会在手机上6100端口启动一个HTTP server,startServing方法内部就是一个死循环,监听网络传输过来的webdriver协议的数据,解析并处理点击事件。 64 | 65 | 有意思的是,WebDriverAgent并没有使用XCUIApplication的initWithBundleIdentifier方法,而是使用了initPrivateWithPath的私有方法。测试发现两者效果上没什么区别,这里暂时不清楚为什么不直接使用initWithBundleIdentifier。 66 | 67 | #### WebDriverAgent如何处理点击事件的? 68 | 69 | 从WebDriverAgent的源码可以清晰的看到,在Commands目录,是支持的操作类集合。每一个操作都通过routes类方法注册对应的路由和处理该路由的函数。手势处理在FBTouchActionCommands.m中, 70 | 71 | ``` 72 | + (NSArray *)routes 73 | { 74 | return 75 | @[ 76 | [[FBRoute POST:@"/wda/touch/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)], 77 | [[FBRoute POST:@"/wda/touch/multi/perform"] respondWithTarget:self action:@selector(handlePerformAppiumTouchActions:)], 78 | [[FBRoute POST:@"/actions"] respondWithTarget:self action:@selector(handlePerformW3CTouchActions:)], 79 | ]; 80 | } 81 | ``` 82 | 83 | 可以看到/wda/touch/perform、/wda/touch/multi/perform、/actions路由负责处理不同的点击事件。那么当一个点击的url请求过来时,如何转化为iOS的UIEvent事件呢?跟踪代码发现核心代码是: 84 | 85 | ``` 86 | [[XCUIDevice.sharedDevice eventSynthesizer] synthesizeEvent:record completion:(id)^(BOOL result, NSError *invokeError) { 87 | handlerBlock(record, invokeError); 88 | }]; 89 | ``` 90 | 91 | XCUIDevice的eventSynthesizer是私有方法,通过synthesizeEvent发送XCSynthesizedEventRecord(也是私有类)事件。到这里WebDriverAgent的流程就很清除了。实际上由于使用了很多私有方法,WebDriverAgent并非仅能自动化当前APP,也是可以操作手机屏幕以及任意APP的。 92 | 93 | ### Appium 94 | 95 | 上面介绍的UI Testing和WebDriverAgent都是iOS上的内容。实际上,在更多的时候我们需要考虑跨平台,从笔者调研来看,现阶段比较好的方案是Appium。Appium是一个开源测试自动化框架,可用于iOS、Android和web应用程序测试,支持python、JAVA、[php](https://easeapi.com/blog/tags/PHP.html)等多种语言编写测试用例。Appium包含客户端和服务端等模块。 96 | 97 | #### Appium客户端 98 | 99 | 在iOS上的客户端实际上就是使用了WebDriverAgent,作为实现webdriver协议的驱动层。 100 | 101 | #### Appium服务端 102 | 103 | Appium的服务端是一个桌面应用,用于和客户端通信,启动Appium的服务端之后,会在电脑上启动一个默认端口号是4723的HTTP服务。当我们编写完脚本执行时,脚本代码会被转换为webdriver协议的JSON数据,通过HTTP请求发送到电脑的4723端口。Appium服务端将脚本的执行请求下发给客户端(请求客户端的6100端口),客户端同样使用webdriver协议响应。 104 | 105 | ### Appium的部署 106 | 107 | #### 安装各种驱动 108 | 109 | ``` 110 | brew install libimobiledevice 111 | npm install -g ios-deploy 112 | #appium-doctor可检查依赖是否安装成功 113 | npm install -g appium-doctor 114 | #检查依赖项 115 | appium-doctor -ios 116 | ``` 117 | 118 | #### 安装[appium-desktop](https://github.com/appium/appium-desktop/releases) 119 | 120 | 下载安装之后,需要对WebDriverAgent修改下证书信息,使其可以真机调试。 121 | 122 | ``` 123 | cd /Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-youiengine-driver/node_modules/appium-webdriveragent 124 | ./Scripts/bootstrap.sh -d 125 | ``` 126 | 127 | 连接真机后,打开WebDriverAgent.xcodeproj,Product->Test运行WebDriverAgentRunner(需要配置证书)。终端会显示一个地址 `http://169.254.138.98:8100` 访问 `http://169.254.138.98:8100/status`打开有json信息则表示服务连接成功。 128 | 129 | 需要注意的是,Facebook原始的WebDriverAgent功能上似乎有些问题,appium-desktop自带的WebDriverAgent版本据说是经过优化修改过的,运行正常。 130 | 131 | 部署完成之后打开Appium,Start Server后即可编写脚本了。一个典型的monkey脚本如下: 132 | 133 | ``` 134 | #!/usr/bin/python 135 | # coding=utf-8 136 | import unittest 137 | import os 138 | from appium import webdriver 139 | from time import sleep 140 | from appium.webdriver.common.touch_action import TouchAction 141 | import random 142 | 143 | class AppTest(unittest.TestCase): 144 | 145 | def setUp(self): 146 | print("setUp") 147 | udid = "c9d719e41f6a9ae00353db126a6561fdf032cc23" 148 | bundleId = "com.easeapi" 149 | 150 | if False: 151 | #new app 152 | app = os.path.abspath('/Users/easeapi.app') 153 | self.driver = webdriver.Remote(command_executor = 'http://127.0.0.1:4723/wd/hub', desired_capabilities = {'app':app,'platformName': 'iOS', 'platformVersion': '12.4.3', 'deviceName': 'EaseapiPhone', 'bundleId': bundleId, 'udid': udid}) 154 | else: 155 | #exist app 156 | self.driver = webdriver.Remote(command_executor = 'http://127.0.0.1:4723/wd/hub', desired_capabilities = {'platformName': 'iOS', 'platformVersion': '12.4.3', 'deviceName': 'EaseapiPhone', 'bundleId': bundleId, 'udid': udid}) 157 | 158 | def tearDown(self): 159 | #self.driver.quit() 160 | 161 | def test_monkey(self): 162 | __size = self.driver.find_element_by_xpath('//UIAApplication[1]').size 163 | window_width = __size['width'] 164 | window_height = __size['height'] 165 | while True: 166 | sleep(1.0) 167 | _x = random.uniform(0, window_width) 168 | _y = random.uniform(0, window_height) 169 | print('x:%f y:%f') % (_x, _y) 170 | TouchAction(self.driver).tap(x=_x, y=_y).perform() 171 | 172 | if __name__ == '__main__': 173 | suite = unittest.TestLoader().loadTestsFromTestCase(AppTest) 174 | unittest.TextTestRunner(verbosity=2).run(suite) 175 | ``` 176 | 177 | 执行该脚本,即可对bundle id为com.easeapi的app进行monkey测试。如果想自定义事件也比较简单: 178 | 179 | ``` 180 | button = self.driver.find_element_by_accessibility_id("按钮") 181 | button.click() 182 | ``` 183 | 184 | 很多时候,编写脚本是一件很繁琐无趣的工作,好在Appium提供了录制的功能。我们上面仅仅启动了Appium的服务。实际上通过Appium也是可以直接在电脑上操作手机的。打开Appium,点击搜索按钮,在JSON Representation区域填写如下模板内容: 185 | 186 | ``` 187 | { 188 | "platformName": "ios", 189 | "platformVersion": "12.4.3", 190 | "udid": "c9d719e41f6a9ae00353db126a6561fdf032cc23", 191 | "deviceName": "EaseapiPhone", 192 | "automationName": "XCUITest", 193 | "bundleId": "com.easeapi", 194 | "xcodeSigningId": "iPhone Developer" 195 | } 196 | 197 | ``` 198 | 199 | 完成后点击Start Session,即可在电脑上直接操作iPhone手机,使用录制功能可以将每一步的点击操作转化为脚本文件,大大较少了开发工作量。 200 | 201 | ### 最后 202 | 203 | 以上就是Appium的实现原理和简单使用介绍,总体来看Appium是一个比较优秀的自动化测试方案,值得推广使用。最后简单说下其他方案的调研结果: 204 | 205 | * fastmonkey:在Xcode11上已无法运行。 206 | * swiftmonkey:使用Swift3.4开发,最新Xcode已经不支持这个版本的Swift。 207 | 208 | 209 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS 多线程 线程间的状态.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0527/155301_0738ceff_9027123.jpeg "24396273-390e25407174b567.jpg") 2 | 3 | ``` 4 | // 5 | // ViewController.m 6 | // 004-NSThread状态 7 | // 8 | // Created by mac on 2018/4/27. 9 | // Copyright © 2018年 mac. All rights reserved. 10 | // 11 | 12 | #import "ViewController.h" 13 | 14 | @interface ViewController () 15 | 16 | @end 17 | 18 | @implementation ViewController 19 | 20 | - (void)viewDidLoad { 21 | [super viewDidLoad]; 22 | 23 | } 24 | 25 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { 26 | 27 | [self threadDemo]; 28 | //杀主线程 29 | NSLog(@"走!!"); 30 | //注意:exit 方法没法杀掉主线程!但是会让主线程阻塞!! 31 | // [NSThread exit]; 32 | // [[NSThread currentThread] start]; 33 | // exitDemo 34 | // [self exitDemo]; 35 | 36 | } 37 | 38 | -(void)exitDemo{ 39 | [self performSelectorInBackground:@selector(starMainThread) withObject:nil]; 40 | //注意!!!exit会让杀掉主线程!但是APP不会挂掉 41 | [NSThread exit]; 42 | } 43 | 44 | -(void)starMainThread{ 45 | [NSThread sleepForTimeInterval:1.0]; 46 | //开启主线程 47 | [[NSThread mainThread] start]; 48 | } 49 | 50 | -(void)threadDemo{ 51 | //创建线程 52 | NSThread * t =[[NSThread alloc]initWithTarget:self selector:@selector(threadStatus) object:nil]; 53 | //线程就绪(CPU翻牌) 54 | [t start]; 55 | } 56 | -(void)threadStatus{ 57 | for (int i = 0; i<20; i++) { 58 | //阻塞,当运行满足某个条件,会让线程“睡一会” 59 | //提示:sleep 方法是类方法,会直接休眠当前线程!! 60 | if(i == 8){ 61 | NSLog(@"睡一会,2秒"); 62 | [NSThread sleepForTimeInterval:2.0]; 63 | } 64 | NSLog(@"%@ %d",[NSThread currentThread],i); 65 | 66 | //当线程满足某一个条件时,可以强行终止的 67 | //exit 类方法,终止当前线程!! 68 | if(i ==15){ 69 | //一旦强行终止线程,后续的所有代码都不会被执行 70 | //注意:在终止线程之前,应该要释放之前分配的对象 71 | [NSThread exit]; 72 | } 73 | } 74 | NSLog(@"能来吗??答案:来不了"); 75 | } 76 | @end 77 | ``` 78 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS 应用实现 gRPC 调用.md: -------------------------------------------------------------------------------- 1 | > **问题** 2 | > 3 | > 在手机应用的开发中,通常会将复杂的业务逻辑层实现放在服务端,客户端仅负责表现层。但是对于某些手机应用而言,业务逻辑的实现位于服务端反而是不安全的或是不合理的,而是需要将其逻辑直接在手机端实现。 4 | > 5 | > **目的** 6 | > 7 | > 面对不同系统的手机客户端,单独重复实现相同的业务逻辑,并非最佳实践。如何通过第三方语言 Go 语言将业务逻辑封装成库的形式,并以静态打包的方式提供给不同系统的手机客户端使用,是本次调研的目的。 8 | 9 | 理想目标图: 10 | 11 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0602/154959_a1e6feb6_9027123.png "rpc.png") 12 | 13 | 14 | 具体调研内容包括: 15 | 16 | * [ ] iOS 应用实现 gRPC 调用 17 | * [ ] Android 应用实现 gRPC 调用 18 | * [ ] GoMobile SDK 在 iOS & Android 上的集成 19 | * [ ] GoMobile SDK 在 iOS & Android 上的边界 20 | * [ ] C/S 架构 or 静态库 21 | 22 | 其中关于 gRPC 在 iOS 与 Android 的实现,本身官方就已经提供了样例。本次调研会用到相关内容,所以将其作为调研的一部分记录下来,方便后来者阅读。 23 | 24 | ## 1\. 环境安装 25 | 26 | 其实很多东西没那么难,只是需要开始而已。 为了完成目标调研,开始第一部分的调研工作。以文字形式记录下来,方便后来者。 27 | 28 | ### 1.1 XCode 安装 29 | 30 | 没什么好说的,直接 AppStore 下载安装。有点慢,一边下载一边准备其它环境。 31 | 32 | ### 1.2 Cocoapod 安装 33 | 34 | 类似与其它语言的第三方库管理工具。也没什么好说的,登录官网,按说明安装。 35 | 36 | ``` 37 | $: sudo gem install cocoapods 38 | ``` 39 | 40 | ### 1.3 protoc 命令安装 41 | 42 | 因为 gRPC 的广泛使用, ProtoBuf 协议被广泛用于字节编码与解码的协议, 其具体指南参考[官网]()。话不多说,安装: 43 | 44 | ``` 45 | $: curl -LOk https://github.com/protocolbuffers/protobuf/releases/download/v3.5.1/protoc-3.9.0-rc-1-osx-x86_64.zip 46 | $: unzip protoc-3.9.0-rc-1-osx-x86_64.zip -d proto_buffer && cd proto_buffer 47 | $: sudo cp bin/protoc /usr/local/bin 48 | $: sudo cp -R include/google/protobuf/ /usr/local/include/google/protobuf 49 | $: protoc --version 50 | ``` 51 | 52 | ### 1.4 protoc 插件安装 53 | 54 | protoc 主要是通过解析 `.proto` 格式的文件, 再根据具体插件生成相应语言代码。 55 | 考虑到需要同时实现客户端与服务端的代码,所以必须安装以下三个插件: 56 | 57 | * swift 58 | * swiftgrpc 59 | * go 主要生成 go 代码, 用于服务端实现 60 | 61 | swift 插件安装: 62 | 63 | ``` 64 | $: git clone https://github.com/grpc/grpc-swift.git 65 | $: cd grpc-swift 66 | $: git checkout tags/0.5.1 67 | $: make 68 | $: sudo cp protoc-gen-swift protoc-gen-swiftgrpc /usr/local/bin 69 | ``` 70 | 71 | go 插件安装: 72 | 73 | 前提是需要安装 Go 语言的开发环境, 可参考官网。`protoc-gen-go`安装详细[指南](https://github.com/golang/protobuf). 74 | 75 | ``` 76 | $: go get -u github.com/golang/protobuf/protoc-gen-go 77 | ``` 78 | 79 | ## 2 定义 proto 接口 80 | 81 | 既然是最简单的调研,就用最简单的 Hello 服务。创建项目路径并定义: 82 | 83 | ``` 84 | $: mkdir grpc-apps 85 | $: cd grpc-apps 86 | $: mkdir proto 87 | $: cat < proto/hello.proto 88 | syntax = "proto3"; 89 | 90 | option java_multiple_files = true; 91 | option java_package = "com.gitdig.helloworld"; 92 | option java_outer_classname = "HelloWorldProto"; 93 | 94 | package helloworld; 95 | 96 | service Greeter { 97 | rpc SayHello (HelloRequest) returns (HelloReply) {} 98 | } 99 | 100 | message HelloRequest { 101 | string name = 1; 102 | } 103 | 104 | message HelloReply { 105 | string message = 1; 106 | } 107 | EOF 108 | ``` 109 | 110 | ## 3\. 服务端实现 111 | 112 | 在项目目录中创建服务端目录与proto生成目录,同时编写一个简单的服务端: 113 | 114 | ``` 115 | $: cd grpc-apps 116 | $: mkdir go go/client go/server go/hello 117 | # 生成 Go 代码到 go/hello 文件夹 118 | $: protoc -I proto proto/hello.proto --go_out=plugins=grpc:./go/hello/ 119 | ``` 120 | 121 | 分别编辑 Go 版本 client 与 server 实现。确认服务正常运行。 122 | 123 | ### 3.1 Go 服务端 124 | 125 | 编辑 `server/server.go` 文件: 126 | 127 | ``` 128 | package main 129 | 130 | import ( 131 | pb "github.com/liujianping/grpc-apps/go/helloworld" 132 | ) 133 | 134 | import ( 135 | "context" 136 | "fmt" 137 | "log" 138 | "net" 139 | 140 | "google.golang.org/grpc" 141 | ) 142 | 143 | type HelloServer struct{} 144 | 145 | // SayHello says 'hi' to the user. 146 | func (hs *HelloServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { 147 | // create response 148 | res := &pb.HelloReply{ 149 | Message: fmt.Sprintf("hello %s from go", req.Name), 150 | } 151 | 152 | return res, nil 153 | } 154 | 155 | func main() { 156 | var err error 157 | 158 | // create socket listener 159 | l, err := net.Listen("tcp", ":50051") 160 | if err != nil { 161 | log.Fatalf("error: %v\n", err) 162 | } 163 | 164 | // create server 165 | helloServer := &HelloServer{} 166 | 167 | // register server with grpc 168 | s := grpc.NewServer() 169 | pb.RegisterGreeterServer(s, helloServer) 170 | 171 | log.Println("server serving at: :50051") 172 | // run 173 | s.Serve(l) 174 | } 175 | 176 | ``` 177 | 178 | 运行服务端程序: 179 | 180 | ``` 181 | $: cd grpc-apps/go 182 | $: go run server/server.go 183 | 2019/07/03 20:31:06 server serving at: :50051 184 | ``` 185 | 186 | ### 3.2 Go 客户端 187 | 188 | 编辑 `client/client.go` 文件: 189 | 190 | ``` 191 | package main 192 | 193 | import ( 194 | pb "github.com/liujianping/grpc-apps/go/helloworld" 195 | ) 196 | 197 | import ( 198 | "context" 199 | "fmt" 200 | "log" 201 | 202 | "google.golang.org/grpc" 203 | ) 204 | 205 | func main() { 206 | var err error 207 | 208 | // connect to server 209 | conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) 210 | if err != nil { 211 | log.Fatalf("error: %v\n", err) 212 | } 213 | defer conn.Close() 214 | 215 | // create client 216 | client := pb.NewGreeterClient(conn) 217 | 218 | // create request 219 | req := &pb.HelloRequest{Name: "JayL"} 220 | 221 | // call method 222 | res, err := client.SayHello(context.Background(), req) 223 | if err != nil { 224 | log.Fatalf("error: %v\n", err) 225 | } 226 | 227 | // handle response 228 | fmt.Printf("Received: \"%s\"\n", res.Message) 229 | } 230 | 231 | ``` 232 | 233 | 执行客户端程序: 234 | 235 | ``` 236 | $: cd grpc-apps/go 237 | $: go run client/client.go 238 | Received: "hello JayL from go" 239 | ``` 240 | 241 | Go 客户端/服务端通信成功。 242 | 243 | ## 4\. iOS 项目 244 | 245 | ### 4.1 创建一个最简单的单视图项目 246 | 247 | 创建一个名为 iosDemo 的单视图项目,选择 swift 语言, 存储路径放在 `grpc-apps` 下。完成创建后,正常运行,退出程序。 248 | 249 | ### 4.2 初始化项目 Pod 250 | 251 | 在命令行执行初始化: 252 | 253 | ``` 254 | $: cd grpc-apps/iosDemo 255 | # 初始化 256 | $: pod init 257 | $: vim Podfile 258 | ``` 259 | 260 | 编辑 Podfile 如下: 261 | 262 | ``` 263 | # Uncomment the next line to define a global platform for your project 264 | # platform :ios, '9.0' 265 | 266 | target 'iosDemo' do 267 | # Comment the next line if you don't want to use dynamic frameworks 268 | use_frameworks! 269 | 270 | # Pods for iosDemo 271 | pod 'SwiftGRPC' 272 | end 273 | ``` 274 | 275 | 完成编辑后保存,执行安装命令: 276 | 277 | ``` 278 | $: pod install 279 | ``` 280 | 281 | 安装完成后,项目目录发生以下变更: 282 | 283 | ``` 284 | $: git status 285 | On branch master 286 | Changes not staged for commit: 287 | (use "git add ..." to update what will be committed) 288 | (use "git checkout -- ..." to discard changes in working directory) 289 | 290 | modified: iosDemo.xcodeproj/project.pbxproj 291 | 292 | Untracked files: 293 | (use "git add ..." to include in what will be committed) 294 | 295 | Podfile 296 | Podfile.lock 297 | Pods/ 298 | iosDemo.xcworkspace/ 299 | 300 | no changes added to commit (use "git add" and/or "git commit -a") 301 | ``` 302 | 303 | 通过命令行 `open iosDemo.xcworkspace` 打开项目,对项目中的info.list的以下设置进行修改: 304 | 305 | ![](https://upload-images.jianshu.io/upload_images/24396273-bc291910462ca980.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 306 | 307 | 308 | 通过设置,开启非安全的HTTP访问方式。 309 | 310 | ### 4.3 生成 gRPC swift 代码 311 | 312 | 类似 Go 代码生成,现在生成 swift 代码: 313 | 314 | ``` 315 | $: cd grpc-apps 316 | # 创建生成文件存放目录 317 | $: mkdir swift 318 | # 生成 swift 文件 319 | $: protoc -I proto proto/hello.proto \ 320 | --swift_out=./swift/ \ 321 | --swiftgrpc_out=Client=true,Server=false:./swift/ 322 | # 生成文件查看 323 | $: tree swift 324 | swift 325 | ├── hello.grpc.swift 326 | └── hello.pb.swift 327 | ``` 328 | 329 | ### 4.4 将生成代码集成到 iOS 项目 330 | 331 | XCode中添加生成代码需要通过拖拽的方式,对于后端开发而言,确实有点不可理喻。不过既然必须这样就按照规则: 332 | 333 | 现在在 iOS 的视图加载函数增加 gRPC 调用过程: 334 | 335 | ``` 336 | class ViewController: UIViewController { 337 | 338 | override func viewDidLoad() { 339 | super.viewDidLoad() 340 | // Do any additional setup after loading the view. 341 | let client = Helloworld_GreeterServiceClient(address: ":50051", secure: false) 342 | var req = Helloworld_HelloRequest() 343 | req.name = "JayL" 344 | do { 345 | let resp = try client.sayHello(req) 346 | print("resp: \(resp.message)") 347 | } catch { 348 | print("error: \(error.localizedDescription)") 349 | } 350 | } 351 | } 352 | ``` 353 | 354 | 查看日志输出`resp: hello iOS from go`, iOS 应用调用 gRPC 服务成功。 355 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS-组件化(OC篇).md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 网上关于组件化的理论很多而且已经比较成熟,理论方面请参看这篇集合文章iOS组件化。 4 | 5 | ### 一、组件化的初衷。 6 | 7 | * 有利于代码模块的封装和复用。 8 | * 对不同的业务模块可以进行物理隔离(通过git私有 仓库权限控制),进一步提升代码的稳定性和安全性。 9 | * 项目整体结构层次分明,便于后期维护。 10 | * 便于项目功能细分,颗粒划分更细,分配工作更合理,项目时间节点更容易掌控,便于进行敏捷开发。 11 | * 便于进行单元测试。 12 | 13 | ### 二、组件化开发过程。 14 | 15 | ### 1、要组件化必须进行解耦。 16 | 17 | 我们谈解耦,并不是完全解除代码之间的耦合,通过学习和实践这是不合理也不可能的。我们解耦的目的其实是为了解除代码模块相互间的依赖,或者说我们的目的就是让代码模块变得单向依赖,像一个插头一样可以自由拔插。 18 | (合理 不合理的 图) 19 | 20 | ### 2、模块化与解耦理论模块化与解耦 21 | 22 | 因为个人精力有限,此篇主要是记录组件化架构的实践,故这里只以功能模块划分组件,模块化可以根据自身项目自己封装对应模块。 23 | 24 | ### 3、组件化架构设计 25 | 26 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0527/160500_976f125d_9027123.jpeg "组件化1.jpg") 27 | 28 | ### 三、组件化架构实现。 29 | 30 | ### 1、目前业界常见的模块间通讯方案大致如下几种: 31 | 32 | 基于路由 URL 的 UI 页面统跳管理(url-block)。 33 | 基于反射的远程接口调用封装(target-action)。 34 | 基于面向协议思想的服务注册方案(protocol-class)。 35 | 基于通知的广播方案(NSNotification)。 36 | 37 | 可以使用一种或几种,我这里选用了基于反射的远程接口调用封装(target-action)和基于路由 URL 的 UI 页面统跳管理(url-block)来进行封装。 38 | 39 | ### 2、路由的实现: 40 | 41 | 网上成熟方案很多JLRoute就是一个非常好的路由框架,但是个人感觉JLRoute依然比较庞大,功能也不够单一。因此自己实现了一个简单的路由框架PTRouter。 42 | 43 | > PTRouter,支持了注册scheme。注册scheme这一特性,可以更方便的调用诸如第方分享,或是统计SDK集成使用。PTRouter,为了实现功能单一性,只扩充了controller跳转的功能。数据的传入通过参数传递(底层是通过依赖注入实现)。通过block只返回跳转是否成功,比如打开设置页: 44 | 45 | ``` 46 | [PTRouter openURL:@"InnerJump://setting/browse?userID=007" callback:^(BOOL result) { 47 | if (!result) { 48 | [SVProgressHUD showInfoWithStatus:@"打开失败"]; 49 | } 50 | }]; 51 | ``` 52 | 53 | > PTRouter 支持同步 & 异步获取返回值,其中异步转同步内部通过semaphore实现 54 | 55 | ``` 56 | + (void)openURL:(NSString *)url callback:(void (^)(BOOL result))callback; 57 | + (BOOL)openURL:(NSString *)url; 58 | ``` 59 | 60 | > 另外openURL除了支持url中带参数,也支持参数放在字典中 61 | 62 | ``` 63 | + (void)openURL:(NSString *)url param:(NSDictionary * __nullable)param callback:(void (^)(BOOL result))callback; 64 | + (BOOL)openURL:(NSString *)url param:(NSDictionary * __nullable)param; 65 | ``` 66 | 67 | ### 3、公共组件例如:第三方分享,数据统计,Bug分析等需要在app进入时就注册的,个人觉得应该放在工程主干中进行对应注册管理: 68 | 69 | > PTAppLaunchHelper APP启动时触发自动注册组件 70 | 71 | ``` 72 | [PTAppLaunchHelper.shared autoInitModule];//根据AutoInitialize.plist 自动初始化组件 73 | ``` 74 | 75 | 对应注册信息集中写在plist中,这样做一目了然,便于维护管理。个人使用runtime来动态注册组件。 PTAppLaunchHelper有两个函数: 76 | 77 | * autoInitModule 用来初始化组件。该函数会读取AutoInitialize.plist中的classes,通过runtime自动初始化协议完成初始化: 78 | 79 | ``` 80 | //init modules with AutoInitialize 81 | - (void)autoInitModule; 82 | ``` 83 | 84 | [图片上传失败...(image-81bf26-1598413859499)] 85 | 86 | * autoRegistURL 87 | 用来自动注册路由,该函数会读取AutoRegistURL.plist完成路由注册。其中controller代表类名,params代表默认参数,如果openURL传的参数与默认参数不符合,路由会报错 88 | 89 | ``` 90 | //init url with AutoRegistURL 91 | - (void)autoRegistURL; 92 | ``` 93 | 94 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0527/160509_eb3374d4_9027123.jpeg "组件化2.jpg") 95 | 96 | > PTAppEventBus 生命周期监听组件 PTAppEventBus通过接收系统通知来获取app生命周期事件,收到生命周期事件后改变对应属性的值。默认提供了didEnterBackground等八个属性,可以使用响应式函数来监听。 97 | 98 | ``` 99 | - (void)observeWithBlock:(PTObservingBlock)block { 100 | 101 | if (self.owner && self.keyPath) { 102 | [self.owner addObserver:self.owner forKey:self.keyPath withBlock:block]; 103 | } 104 | else { 105 | NSLog(@"owner = %@, keypath = %@",self.owner,self.keyPath); 106 | NSString *reason = [NSString stringWithFormat:@"Object does not set owner or keypath"]; 107 | @throw [NSException exceptionWithName:NSInvalidArgumentException 108 | reason:reason 109 | userInfo:nil]; 110 | 111 | return; 112 | } 113 | } 114 | ``` 115 | 116 | > PTAppEventBus使用前需要调用 117 | 118 | ``` 119 | - (void)start; 120 | ``` 121 | 122 | > 如果这些不够,需要监听更多的事件(例如app横竖屏状态),可以通过分类给PTAppEventBus的添加对应属性属性,操作如下: 123 | 124 | ``` 125 | NSMutableDictionary *defaultMap = [NSMutableDictionary dictionaryWithDictionary:[PTAppEventBus defaultNotificationMap]]; 126 | [defaultMap setObject:KDidChangeStatusBarOrientation forKey:UIApplicationWillChangeStatusBarOrientationNotification]; 127 | [PTAppEventBus.shared startWithNotificationMap:defaultMap];//开启EventBus,开启后组件可收到App生命周期事件 128 | ``` 129 | 130 | ### 总结: 131 | 132 | 本文的组件通过pod 私有库进行集成。解藕部分还有待改进,后续有时间会继续修改。 133 | 134 | [原文地址](https://zhuanlan.zhihu.com/p/183809375) -------------------------------------------------------------------------------- /iOS底层进阶/iOS内存管理.md: -------------------------------------------------------------------------------- 1 | #### 1 似乎每个人在学习 iOS 过程中都考虑过的问题 2 | 3 | * alloc retain release delloc 做了什么? 4 | * autoreleasepool 是怎样实现的? 5 | * __unsafe_unretained 是什么? 6 | * Block 是怎样实现的 7 | * 什么时候会引起循环引用,什么时候不会引起循环引用? 8 | 9 | 所以我将在本篇博文中详细的从 ARC 解释到 iOS 的内存管理,以及 Block 相关的原理、源码。 10 | 11 | #### 2 从 ARC 说起 12 | 13 | 说 iOS 的内存管理,就不得不从 ARC(Automatic Reference Counting / 自动引用计数) 说起, ARC 是 WWDC2011 和 iOS5 引入的变化。ARC 是 LLVM 3.0 编译器的特性,用来自动管理内存。 14 | 15 | 与 Java 中 GC 不同,ARC 是编译器特性,而不是基于运行时的,所以 ARC 其实是在编译阶段自动帮开发者插入了管理内存的代码,而不是实时监控与回收内存。 16 | 17 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0527/160711_62db4717_9027123.jpeg "内存管理1.jpg") 18 | 19 | ARC 的内存管理规则可以简述为: 20 | 21 | * 每个对象都有一个『被引用计数』 22 | * 对象被持有,『被引用计数』+1 23 | * 对象被放弃持有,『被引用计数』-1 24 | * 『引用计数』=0,释放对象 25 | 26 | #### 3 你需要知道 27 | 28 | * 包含 NSObject 类的 Foundation 框架并没有公开 29 | * Core Foundation 框架源代码,以及通过 NSObject 进行内存管理的部分源代码是公开的。 30 | * GNUstep 是 Foundation 框架的互换框架 31 | 32 | GNUstep 也是 GNU 计划之一。将 Cocoa Objective-C 软件库以自由软件方式重新实现 33 | 34 | 某种意义上,GNUstep 和 Foundation 框架的实现是相似的 35 | 36 | 通过 GNUstep 的源码来分析 Foundation 的内存管理 37 | 38 | #### 4 alloc retain release dealloc 的实现 39 | 40 | 4.1 GNU – alloc 41 | 42 | 查看 GNUStep 中的 alloc 函数。 43 | 44 | GNUstep/modules/core/base/Source/NSObject.m alloc: 45 | ``` 46 | + (id) alloc  47 | 48 | {  49 | 50 | return [self allocWithZone: NSDefaultMallocZone()];  51 | 52 | }   53 | 54 | + (id) allocWithZone: (NSZone*)z  55 | 56 | {  57 | 58 | return NSAllocateObject (self, 0, z);  59 | 60 | }   61 | ``` 62 | 63 | GNUstep/modules/core/base/Source/NSObject.m NSAllocateObject: 64 | 65 | ``` 66 | struct obj_layout {  67 | 68 | NSUInteger retained;  69 | 70 | };  71 | 72 | NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)  73 | 74 | {  75 | 76 | int size = 计算容纳对象所需内存大小;  77 | 78 | id new = NSZoneCalloc(zone, 1, size);  79 | 80 | memset (new, 0, size);  81 | 82 | new = (id)&((obj)new)[1];  83 | 84 | }   85 | ``` 86 | NSAllocateObject 函数通过调用 NSZoneCalloc 函数来分配存放对象所需的空间,之后将该内存空间置为 nil,最后返回作为对象而使用的指针。 87 | 88 | 我们将上面的代码做简化整理: 89 | 90 | GNUstep/modules/core/base/Source/NSObject.m alloc 简化版本: 91 | 92 | ``` 93 | struct obj_layout {  94 | 95 | NSUInteger retained;  96 | 97 | };  98 | 99 | + (id) alloc  100 | 101 | {  102 | 103 | int size = sizeof(struct obj_layout) + 对象大小;  104 | 105 | struct obj_layout *p = (struct obj_layout *)calloc(1, size);  106 | 107 | return (id)(p+1)  108 | 109 | return [self allocWithZone: NSDefaultMallocZone()];  110 | 111 | }   112 | ``` 113 | 114 | alloc 类方法用 struct obj_layout 中的 retained 整数来保存引用计数,并将其写入对象的内存头部,该对象内存块全部置为 0 后返回。 115 | 116 | 一个对象的表示便如下图: 117 | 118 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0527/160722_b7e8022a_9027123.png "内存管理2.png") 119 | 120 | 121 | 4.2 GNU – retain 122 | 123 | GNUstep/modules/core/base/Source/NSObject.m retainCount: 124 | 125 | 126 | ``` 127 | - (NSUInteger) retainCount  128 | 129 | {  130 | 131 | return NSExtraRefCount(self) + 1;  132 | 133 | }  134 | 135 | inline NSUInteger  136 | 137 | NSExtraRefCount(id anObject)  138 | 139 | {  140 | 141 | return ((obj_layout)anObject)[-1].retained;  142 | 143 | }   144 | ``` 145 | 146 | GNUstep/modules/core/base/Source/NSObject.m retain: 147 | 148 | ``` 149 | - (id) retain  150 | 151 | {  152 | 153 | NSIncrementExtraRefCount(self);  154 | 155 | return self;  156 | 157 | }  158 | 159 | inline void  160 | 161 | NSIncrementExtraRefCount(id anObject)  162 | 163 | {  164 | 165 | if (((obj)anObject)[-1].retained == UINT_MAX - 1)  166 | 167 | [NSException raise: NSInternalInconsistencyException  168 | 169 | format: @"NSIncrementExtraRefCount() asked to increment too far”];  170 | 171 | ((obj_layout)anObject)[-1].retained++;  172 | 173 | }   174 | ``` 175 | 176 | 以上代码中, NSIncrementExtraRefCount 方法首先写入了当 retained 变量超出最大值时发生异常的代码(因为 retained 是 NSUInteger 变量),然后进行 retain ++ 代码。 177 | 178 | 4.3 GNU – release 179 | 180 | 和 retain 相应的,release 方法做的就是 retain --。 181 | 182 | GNUstep/modules/core/base/Source/NSObject.m release 183 | 184 | ``` 185 | - (oneway void) release  186 | 187 | {  188 | 189 | if (NSDecrementExtraRefCountWasZero(self))  190 | 191 | {  192 | 193 | [self dealloc];  194 | 195 | }  196 | 197 | }  198 | 199 | BOOL  200 | 201 | NSDecrementExtraRefCountWasZero(id anObject)  202 | 203 | {  204 | 205 | if (((obj)anObject)[-1].retained == 0)  206 | 207 | {  208 | 209 | return YES;  210 | 211 | }  212 | 213 | ((obj)anObject)[-1].retained--;  214 | 215 | return NO;  216 | 217 | }   218 | ``` 219 | 220 | 4.4 GNU – dealloc 221 | 222 | dealloc 将会对对象进行释放。 223 | 224 | GNUstep/modules/core/base/Source/NSObject.m dealloc: 225 | 226 | ``` 227 | - (void) dealloc  228 | 229 | {  230 | 231 | NSDeallocateObject (self);  232 | 233 | }  234 | 235 | inline void  236 | 237 | NSDeallocateObject(id anObject)  238 | 239 | {  240 | 241 | obj_layout o = &((obj_layout)anObject)[-1];  242 | 243 | free(o);  244 | 245 | }   246 | ``` 247 | 248 | 4.5 Apple 实现 249 | 250 | 在 Xcode 中 设置 Debug -> Debug Workflow -> Always Show Disassenbly 打开。这样在打断点后,可以看到更详细的方法调用。 251 | 252 | 通过在 NSObject 类的 alloc 等方法上设置断点追踪可以看到几个方法内部分别调用了: 253 | 254 | retainCount 255 | 256 | ``` 257 | __CFdoExternRefOperation  258 | CFBasicHashGetCountOfKey   259 | ``` 260 | 261 | retain 262 | 263 | ``` 264 | __CFdoExternRefOperation  265 | CFBasicHashAddValue   266 | ``` 267 | 268 | release 269 | 270 | ``` 271 | __CFdoExternRefOperation  272 | CFBasicHashRemoveValue   273 | ``` 274 | 275 | 可以看到他们都调用了一个共同的 __CFdoExternRefOperation 方法。 276 | 277 | 该方法从前缀可以看到是包含在 Core Foundation,在 CFRuntime.c 中可以找到,做简化后列出源码: 278 | 279 | CFRuntime.c __CFDoExternRefOperation: 280 | 281 | ``` 282 | int __CFDoExternRefOperation(uintptr_t op, id obj) {  283 | 284 | CFBasicHashRef table = 取得对象的散列表(obj);  285 | 286 | int count;  287 | 288 | switch (op) {  289 | 290 | case OPERATION_retainCount:  291 | 292 | count = CFBasicHashGetCountOfKey(table, obj);  293 | 294 | return count;  295 | 296 | break;  297 | 298 | case OPERATION_retain:  299 | 300 | count = CFBasicHashAddValue(table, obj);  301 | 302 | return obj;  303 | 304 | case OPERATION_release:  305 | 306 | count = CFBasicHashRemoveValue(table, obj);  307 | 308 | return 0 == count;  309 | 310 | }  311 | 312 | }   313 | ``` 314 | 315 | 所以 __CFDoExternRefOperation 是针对不同的操作,进行具体的方法调用,如果 op 是 OPERATION_retain,就去掉用具体实现 retain 的方法。 316 | 317 | 从 BasicHash 这样的方法名可以看出,其实引用计数表就是散列表。 318 | 319 | key 为 hash(对象的地址) value 为 引用计数。 320 | 321 | 下图是 Apple 和 GNU 的实现对比: 322 | 323 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/133453_df73e1d3_9027123.jpeg "内存管理3.jpg") 324 | #### 5 autorelease 和 autorelaesepool 325 | 326 | 在苹果对于 NSAutoreleasePool 的文档中表示: 327 | 328 | 每个线程(包括主线程),都维护了一个管理 NSAutoreleasePool 的栈。当创先新的 Pool 时,他们会被添加到栈顶。当 Pool 被销毁时,他们会被从栈中移除。 329 | 330 | autorelease 的对象会被添加到当前线程的栈顶的 Pool 中。当 Pool 被销毁,其中的对象也会被释放。 331 | 332 | 当线程结束时,所有的 Pool 被销毁释放。 333 | 334 | 对 NSAutoreleasePool 类方法和 autorelease 方法打断点,查看其运行过程,可以看到调用了以下函数: 335 | 336 | ``` 337 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  338 | 339 | // 等同于 objc_autoreleasePoolPush  340 | 341 | id obj = [[NSObject alloc] init];  342 | 343 | [obj autorelease];  344 | 345 | // 等同于 objc_autorelease(obj)  346 | 347 | [NSAutoreleasePool showPools];  348 | 349 | // 查看 NSAutoreleasePool 状况  350 | 351 | [pool drain];  352 | 353 | // 等同于 objc_autoreleasePoolPop(pool)   354 | ``` 355 | 356 | [NSAutoreleasePool showPools] 可以看到当前线程所有 pool 的情况: 357 | 358 | ``` 359 | objc[21536]: ##############  360 | 361 | objc[21536]: AUTORELEASE POOLS for thread 0x10011e3c0  362 | 363 | objc[21536]: 2 releases pending.  364 | 365 | objc[21536]: [0x101802000] ................ PAGE (hot) (cold)  366 | 367 | objc[21536]: [0x101802038] ################ POOL 0x101802038  368 | 369 | objc[21536]: [0x101802040] 0x1003062e0 NSObject  370 | 371 | objc[21536]: ##############  372 | 373 | Program ended with exit code: 0   374 | ``` 375 | 376 | 在 objc4 中可以查看到 AutoreleasePoolPage: 377 | 378 | ``` 379 | objc4/NSObject.mm AutoreleasePoolPage  380 | 381 | class AutoreleasePoolPage  382 | 383 | {  384 | 385 | static inline void *push()  386 | 387 | {  388 | 389 | 生成或者持有 NSAutoreleasePool 类对象  390 | 391 | }  392 | 393 | static inline void pop(void *token)  394 | 395 | {  396 | 397 | 废弃 NSAutoreleasePool 类对象  398 | 399 | releaseAll();  400 | 401 | }  402 | 403 | static inline id autorelease(id obj)  404 | 405 | {  406 | 407 | 相当于 NSAutoreleasePool 类的 addObject 类方法  408 | 409 | AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例;  410 | 411 | }  412 | 413 | id *add(id obj)  414 | 415 | {  416 | 417 | 将对象追加到内部数组  418 | 419 | }  420 | 421 | void releaseAll()  422 | 423 | {  424 | 425 | 调用内部数组中对象的 release 方法  426 | 427 | }  428 | 429 | };  430 | 431 | void *  432 | 433 | objc_autoreleasePoolPush(void)  434 | 435 | {  436 | 437 | if (UseGC) return nil;  438 | 439 | return AutoreleasePoolPage::push();  440 | 441 | }  442 | 443 | void  444 | 445 | objc_autoreleasePoolPop(void *ctxt)  446 | 447 | {  448 | 449 | if (UseGC) return;  450 | 451 | AutoreleasePoolPage::pop(ctxt);  452 | 453 | }   454 | ``` 455 | 456 | AutoreleasePoolPage 以双向链表的形式组合而成(分别对应结构中的 parent 指针和 child 指针)。 457 | 458 | thread 指针指向当前线程。 459 | 460 | 每个 AutoreleasePoolPage 对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。 461 | 462 | next 指针指向下一个 add 进来的 autorelease 的对象即将存放的位置。 463 | 464 | 一个 Page 的空间被占满时,会新建一个 AutoreleasePoolPage 对象,连接链表。 465 | 466 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/133505_ee269dff_9027123.jpeg "内存管理4.jpg") 467 | 468 | #### 6 __unsafe_unretained 469 | 470 | 有时候我们除了 __weak 和 __strong 之外也会用到 __unsafe_unretained 这个修饰符,那么我们对 __unsafe_unretained 了解多少? 471 | 472 | __unsafe_unretained 是不安全的所有权修饰符,尽管 ARC 的内存管理是编译器的工作,但附有 __unsafe_unretained 修饰符的变量不属于编译器的内存管理对象。赋值时即不获得强引用也不获得弱引用。 473 | 474 | 来运行一段代码: 475 | 476 | ``` 477 | id __unsafe_unretained obj1 = nil;  478 | 479 | {  480 | 481 | id __strong obj0 = [[NSObject alloc] init];   482 | 483 | obj1 = obj0;   484 | 485 | NSLog(@"A: %@", obj1);  486 | 487 | }   488 | 489 | NSLog(@"B: %@", obj1);   490 | ``` 491 | 492 | 493 | 494 | 对代码进行详细分析: 495 | ``` 496 | id __unsafe_unretained obj1 = nil;  497 | 498 | {  499 | 500 | // 自己生成并持有对象  501 | 502 | id __strong obj0 = [[NSObject alloc] init];  503 | 504 | // 因为 obj0 变量为强引用,  505 | 506 | // 所以自己持有对象  507 | 508 | obj1 = obj0;  509 | 510 | // 虽然 obj0 变量赋值给 obj1  511 | 512 | // 但是 obj1 变量既不持有对象的强引用,也不持有对象的弱引用  513 | 514 | NSLog(@"A: %@", obj1);  515 | 516 | // 输出 obj1 变量所表示的对象  517 | 518 | }  519 | 520 | NSLog(@"B: %@", obj1);  521 | 522 | // 输出 obj1 变量所表示的对象  523 | 524 | // obj1 变量表示的对象已经被废弃  525 | 526 | // 所以此时获得的是悬垂指针  527 | 528 | // 错误访问   529 | ``` 530 | 531 | 所以,最后的 NSLog 只是碰巧正常运行,如果错误访问,会造成 crash 532 | 533 | 在使用 __unsafe_unretained 修饰符时,赋值给附有 __strong 修饰符变量时,要确保对象确实存在 534 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS多线程基础.md: -------------------------------------------------------------------------------- 1 | #### 什么是进程? 2 | * 进程是指在系统中正在运行的一个应用程序,每个进程之间是相互独立的,系统会给每个进程分配属于自己的内存空间 3 | 4 | #### 什么是线程? 5 | 6 | * 一个进程想要执行任务,必须得有线程,每个进程至少要有一个线程,进程的所有任务都在线程中执行,线程中的任务执行是串行的,同一时间内一个线程只能执行1个任务。 7 | 8 | #### 什么是多线程? 9 | 10 | * 多线程就是指一个进程中可以开启多条线程,可以同时执行不同的任务。多线程可以提高程序的执行效率。 11 | 12 | #### 多线程在iOS中的使用 13 | 14 | * 我们启动一个iOSAPP的时候,系统默认会开启一个线程,我们称为主线程或者UI线程,它主要的作用就是刷新页面处理和用户交互,但是如果我们开发中如果将一些比较耗时的操作放在主线程中,这种容易使我们的应用非常卡顿,影响用户体验。所以平时我们开发人员在开发的过程中,会将一些耗时的操作放在子线程中执行,例如网络请求、加载大图片等操作。 15 | * 开发中我们常用的三种开始多线程的方法: 16 | (1) **NSThread**:苹果中NS开头的类肯定是面向对象的,使用简单,可直接操作对象。 17 | (2)**GCD**:这个是非常强大的,苹果推荐使用,充分利用设备多核,平时开发中使用也最多,理解使用步骤,操作起来也是很简单的。之后会详细介绍使用的方式。 18 | (3)**NSOperation**:这个是基于GCD,更加面向对象了,比GCD更加简单了。 19 | 20 | #### 基本原理我们都了解了,下面直接上代码,学习如何使用NSThread开启线程。 21 | 22 | * 创建线程,然后启动线程。只需2步就OK了 23 | 24 | * NSThread其他方式开始线程 25 | 26 | ``` 27 | - (void)createThread3 28 | { 29 | [self performSelectorInBackground:@selector(run:) withObject:@"jack"]; 30 | } 31 | - (void)createThread2 32 | { 33 | [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"rose"]; 34 | } 35 | ``` 36 | 37 | * 线程的状态一般就2种,就绪状态(等待执行),执行状态(正在执行)。 38 | 39 | ``` 40 | [NSThread sleepForTimeInterval:2]; // 让线程睡眠2秒(阻塞2秒) 41 | [NSThread sleepUntilDate:[NSDate distantFuture]];//distantFuture 遥远的未来,如果写上这句,那么程序执行到这里就直接睡死了,下面的代码不会在执行了。 42 | [NSThread exit]; // 直接退出线程 43 | 44 | ``` 45 | 46 | #### 线程的安全 47 | 48 | > 在多线程的运作之下,会出现多个任务共享同一资源,这样会造成数据错乱的问题。例如多个窗口买票,三个窗口同一时间都卖同一个座位的票,票只有一张到底怎么分配,所以这样会出现数据错乱的问题,为了解决这个问题,我们使用互斥锁去解决,同一时间只许一个人操作数据,将数据加锁,当前这个人没有解锁之前,别人无法访问资源。同时我们在使用锁的时候注意下,不能出现死锁的现象,死锁就是连个进程访问同一资源而陷入了相互等待。苹果给我们提供了一个关键字@synchronized处理加锁问题。代码如下 49 | > ``` 50 | > @synchronized(self) { 51 | > // 先取出总数 52 | > NSInteger count = self.ticketCount; 53 | > if (count > 0) { 54 | > self.ticketCount = count - 1; 55 | > NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name, self.ticketCount); 56 | > } else { 57 | > NSLog(@“票已经卖完了”); 58 | > break; 59 | > } 60 | > } 61 | 62 | #### 线程间的通信 63 | 64 | > 平时我们在子线中处理一些耗时的操作,处理完之后,我们拿到数据展示到页面上。处理UI只能在主线程中操作。所以如何从子线程中切回到主线程中呢?以下三种方式都可以回到主线程(MainThread)需要注意下。 65 | > ``` 66 | > // 回到主线程,显示图片方式1 67 | > [self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO]; 68 | > / 回到主线程,显示图片方式2 69 | > [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO]; 70 | > / 回到主线程,显示图片方式3 71 | > [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES]; 72 | 73 | ## 推荐文章 74 | 75 | 76 | **[iOS多线程面试题分析](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E5%A4%9A%E7%BA%BF%E7%A8%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%88%86%E6%9E%90.md)** 77 | 78 | **[iOS 多线程 线程间的状态](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E5%BA%95%E5%B1%82%E8%BF%9B%E9%98%B6/iOS%20%E5%A4%9A%E7%BA%BF%E7%A8%8B%20%E7%BA%BF%E7%A8%8B%E9%97%B4%E7%9A%84%E7%8A%B6%E6%80%81.md)** 79 | 80 | ## 多线程视频资料 81 | 82 | **[多线程底层解析](https://www.bilibili.com/video/BV1oi4y1j7sP)** 83 | 84 | 85 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS底层原理 - isa和superclass.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134446_b6800d72_9027123.png "isa1.png") 2 | 3 | instance的isa指针指向class。 4 | 5 | 当调用对象方法时,通过instance的isa指针找到class,最后找到对象方法的实现进行调用。 6 | 7 | class的isa指针指向meta-class。 8 | 9 | 当调用类方法时,通过class的isa指针找到meta-class,最后找到类方法的实现进行调用。 10 | 11 | ## 二.class的superclass 12 | 13 | #### 2.1 class对象的superclass指针 14 | 15 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134508_f2226696_9027123.png "isa2.png") 16 | 17 | 当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到Person的class,最后找到对象方法的实现进行调用。 18 | 19 | #### 2.2 meta-class对象的superclass指针 20 | 21 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134519_3388d4c5_9027123.png "isa3.png") 22 | 23 | 当Student的class要调用Person的类方法时,会先通过isa指针找到Student的meta-class,然后通过superclass找到Person的meta-class,最后找到类方法的实现进行调用 24 | 25 | ## 三.isa细节 26 | 27 | 从64bit开始,isa需要进行一次位运算,才能计算出真实地址 28 | 29 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134531_6abda4f2_9027123.png "isa4.png") 30 | 31 | 从源码中可以查到ISA_MASK的相关信息 32 | 33 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134545_0cdd7e24_9027123.png "isa5.png") 34 | 35 | ## isa、superclass总结 36 | 37 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134559_d42700ad_9027123.png "isa6.png") 38 | 39 | * 实例对象(instance)的isa指向class 40 | * 类对象(class)的isa指向meta-class 41 | * 元类对象(meta-class)的isa指向基类的meta-class 42 | * 类对象(class)的superclass指向父类的class 43 | * * 如果没有父类,superclass指针为nil 44 | * 元类对象(meta-class)的superclass指向父类的meta-class 45 | * * 基类的meta-class的superclass指向基类的class 46 | * instance调用对象方法的轨迹 47 | * * isa找到class,方法不存在,就通过superclass找父类 48 | * class调用类方法的轨迹 49 | * * isa找到meta-class,方法不存在,就通过superclass找父类 50 | 51 | ## 四.class和meta-class的结构 52 | 53 | ``` 54 | struct objc_class { 55 | Class _Nonnull isa OBJC_ISA_AVAILABILITY; 56 | 57 | #if !__OBJC2__ 58 | Class _Nullable super_class OBJC2_UNAVAILABLE; 59 | const char * _Nonnull name OBJC2_UNAVAILABLE; 60 | long version OBJC2_UNAVAILABLE; 61 | long info OBJC2_UNAVAILABLE; 62 | long instance_size OBJC2_UNAVAILABLE; 63 | struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; 64 | struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; 65 | struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; 66 | struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; 67 | #endif 68 | 69 | } OBJC2_UNAVAILABLE; 70 | /* Use `Class` instead of `struct objc_class *` */ 71 | ``` 72 | 73 | 在runtime.h头文件中我们可以看到一个条件编译如果不是objc2的版本下,那么条件范围内的代码将不会被执行,所以在OC2.0中,这段代码已经过时了。而且通过OBJC2_UNAVAILABLE也可以看出,struct objc_class这个结构体在OC2.0中已经不可用了。 74 | 75 | 76 | 77 | #### 4.1 窥探struct objc_class的结构 78 | 79 | 类和元类在内存中的结构,就是struct objc_class这样一个结构体,这个结构体在objc-runtime-new.h头文件中可以找到最新版本的定义。如下图结构体struct objc_class中部分代码所示: 80 | 81 | ``` 82 | struct objc_class : objc_object { 83 | Class isa; 84 | Class superclass; 85 | cache_t cache; // formerly cache pointer and vtable-方法缓存 86 | class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags-用于获取具体的类信息 87 | ... 88 | ... 89 | ... 90 | } 91 | ``` 92 | 93 | 注意:冒号语法是继承的意思,结构体objc_class继承自objc_object,这种语法是C++的语法。 94 | 95 | ``` 96 | class_rw_t *data() const { 97 | return bits.data(); 98 | } 99 | ``` 100 | 101 | ``` 102 | class_rw_t* data() const { 103 | return (class_rw_t *)(bits & FAST_DATA_MASK); 104 | } 105 | ``` 106 | 107 | #### 下图为老版本代码结构,可以参考新版本源码 108 | 109 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134609_aef53def_9027123.png "isa7.png") 110 | 111 | struct objc_class结构体中包含上面这样一段代码,返回值是class_rw_t,是一个可读写的表单。 112 | 113 | 进入class_rw_t头文件中我们可以看到一段public的代码,返回值是class_rw_ext_t类型。如下图所示 114 | 115 | ``` 116 | class_rw_ext_t *ext() const { 117 | return get_ro_or_rwe().dyn_cast(); 118 | } 119 | 120 | ``` 121 | 进入class_rw_ext_t头文件,我们可以看到方法列表、属性列表、协议列表的相关定义。如下图所示 122 | 123 | ``` 124 | struct class_rw_ext_t { 125 | const class_ro_t *ro; 126 | method_array_t methods; 127 | property_array_t properties; 128 | protocol_array_t protocols; 129 | char *demangledName; 130 | uint32_t version; 131 | }; 132 | ``` 133 | 134 | 进入到class_rw_ext_t结构体中class_ro_t类型的头文件中,我们可以看到instanceSize、ivars的定义。下图为头文件中的部分代码。 135 | 136 | ``` 137 | struct class_ro_t { 138 | uint32_t flags; 139 | uint32_t instanceStart; 140 | uint32_t instanceSize; 141 | #ifdef __LP64__ 142 | uint32_t reserved; 143 | #endif 144 | 145 | const uint8_t * ivarLayout; 146 | 147 | const char * name; 148 | method_list_t * baseMethodList; 149 | protocol_list_t * baseProtocols; 150 | const ivar_list_t * ivars; 151 | ... 152 | ... 153 | ... 154 | ``` 155 | 156 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/134619_2297bc00_9027123.png "isa8.png") 157 | 158 | 总结:OC的类信息存放在哪里? 159 | 160 | * 对象方法、属性、成员变量、协议信息,存放在class对象中。 161 | * 类方法,存放在meta-class对象中。 162 | * 成员变量的具体值,存放在instance对象。 163 | 164 | 165 | -------------------------------------------------------------------------------- /iOS底层进阶/iOS开发-动态和静态FrameWork.md: -------------------------------------------------------------------------------- 1 | >开发中我们会使用到第三方的SDK,有的时候也会将整个系统的公用的功能的抽象出来成为FrameWork,我们只需要暴露对外的接口,使用者只需要调用接口,对于内部实现的过程不需要维护,可以以库的形式进行封装,只暴露出头文件。库(FrameWork)是编译好的二进制文件,编译的时候只需要 Link 一下,提高浪费编译时间,库分为静态库和动态库。 2 | 3 | ## 基础知识 4 | 5 | 静态库即静态链接库(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做静态,是因为静态库在编译的时候会被直接拷贝一份,复制到目标程序里,这段代码在目标程序里就不会再改变了。静态库的好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,直接就可以运行。当然其缺点也很明显,就是会使用目标程序的体积增大。 6 | 7 | 动态库动态库即动态链接库(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib)。与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。动态库的优点是,不需要拷贝到目标程序中,不会影响目标程序的体积,而且同一份库可以被多个程序使用(因为这个原因,动态库也被称作**共享库**)。同时,编译时才载入的特性,也可以让我们随时对库进行替换,而不需要重新编译代码。动态库带来的问题主要是,动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境。如果环境缺少动态库或者库的版本不正确,就会导致程序无法运行(Linux 下喜闻乐见的 lib not found 错误)。 8 | 9 | ## 动态库 10 | 11 | xCode6之后制作动态库相比之前简单很多,xCode7基本上沿袭了xCode6的操作,细节方面有差别。在 iOS 8 之前,iOS 平台不支持使用动态 Framework,开发者可以使用的 Framework 只有苹果基础的 UIKit.Framework,Foundation.Framework 等。iOS 8/Xcode 6 推出之后,iOS 平台添加了动态库的支持,同时 Xcode 6 也原生自带了 Framework 支持(动态和静态都可以),iOS8多了 Extension ,Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。但是这种动态 Framework 和系统的 UIKit.Framework 还是有很大区别。系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 Framework 哪怕是动态的,最后也还是要拷贝到 App 中(App 和 Extension 的 Bundle 是共享的),因此苹果又把这种 Framework 称为Embedded FrameWork. 12 | 13 | 14 | 15 | 1.新建动态库File→New→Target→Cocoa Touch FrameWork: 16 | 17 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135137_c8dc47c0_9027123.png "动1.png") 18 | 19 | 2.项目名称DynamicLibrary,同时我们新建两个测试文件FEUIImage,TestImage: 20 | 21 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135146_2386e84c_9027123.png "动2.png") 22 | 23 | 3.在Dynamic.h中导入头文件: 24 | ``` 25 | #import 26 | 27 | //! Project version number for DynamicLibrary. 28 | FOUNDATION_EXPORT double DynamicLibraryVersionNumber; 29 | 30 | //! Project version string for DynamicLibrary. 31 | FOUNDATION_EXPORT const unsigned char DynamicLibraryVersionString[]; 32 | 33 | // In this header, you should import all the public headers of your framework using statements like #import 34 | #import 35 | 36 | ``` 37 | 38 | 4.将FEUIImage和TestImage设置为Public供外部访问: 39 | 40 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135156_c9ff3098_9027123.png "动3.png") 41 | 42 | 5.cmb+b编译项目,编辑成功之后将项目移动到项目中进行测试: 43 | 44 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135204_9c72badf_9027123.png "动4.png") 45 | 46 | 6.通过FEDevice项目找到编译之后的DynamicLibrary.framwork文件 47 | 48 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135214_31620106_9027123.png "动5.png") 49 | 50 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135224_39232585_9027123.png "动6.png") 51 | 52 | ## 通用动态库 53 | 54 | 我们将上面的DynamicLibrary.framework移动的其他项目中是可以直接使用的,但是运行的时候会出错, 错误信息如下: 55 | ``` 56 | Undefined symbols for architecture x86_64: 57 | "_OBJC_CLASS_$_FEUIImage", referenced from: 58 | objc-class-ref in ViewController.o 59 | ld: symbol(s) not found for architecture x86_64 60 | clang: error: linker command failed with exit code 1 (use -v to see invocation) 61 | ``` 62 | 错误信息跟动态的指令集和目标项目的指令集版本有关系,我们可以简单的了解下Achitectures和设备之间的关系,iPhone一直以来都是Arm处理器,Arm是处理器是移动设备上占用率最大的处理器。 在iOS模拟器上运行的是x86指令集,只有在真机上才会执行arm指令集,每个指令集对应不同的机型设置: 63 | 64 | 都是arm处理器的指令集。通常指令是向下兼容的。在模拟器运行时,iOS模拟器运行的是x86指令集。只有在真机上,才会对执行arm指令集。 65 | 66 | armv6:iPhone,iPhone 2G/3G,iPod 1G/2G,xCode4.5已经不支持armv6指令集; 67 | 68 | armv7 :iPhone 3GS,iPhone4,iPhone 4s,iPad,iPad2,iPad3(The New iPad),iPad mini,iPod Touch 3G,iPod Touch4,由于iPhone4s的占有率,目前是指令集的最低版本; 69 | 70 | armv7s:iPhone5, iPhone5C,iPad4和iPod5; 71 | 72 | arm64:iPhone5s,iPhone6,iPhone6 Plus,iPad Air,iPad mini2(iPad mini with Retina Display),iPhone6s,iPhone6s Plus 73 | 74 | 我们可以通过lipo命令查看发现i386是mac版指令集: 75 | ``` 76 | lipo -info DynamicLibrary.framework/DynamicLibrary 77 | Non-fat file: DynamicLibrary.framework/DynamicLibrary is architecture: i386 78 | ``` 79 | 1.如果用真机调试的话,同样会发生程序错误,所以需要将同样的代码同时支持模拟器和真机,两份库聚合一下,回到DynamicLibrary.frameWork项目,通过File→New→Target→Other→Aggregate: 80 | 81 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135234_a8c0c8bf_9027123.png "动7.png") 82 | 83 | 2.执行编译脚本地址,先选中DynamicLibrary-Universal,添加脚本地址: 84 | ``` 85 | /${PROJECT_DIR}/DynamicLibrary/ios-framework-universal-script.sh 86 | ``` 87 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135245_f34c36ba_9027123.png "动8.png") 88 | 89 | 3.设置脚本内容,同时设置DynamicLibrary-Universal的依赖项(Target Dependencies)为DynamicLibrary: 90 | ``` 91 | #!/bin/sh 92 | 93 | # ios-build-framework-script.sh 94 | # DynamicLibrary 博客园-FlyElephant 95 | # 博客园:http://www.cnblogs.com/xiaofeixiang/ 96 | # Created by keso on 16/1/17. 97 | # Copyright © 2016年 FlyElephant. All rights reserved. 98 | set -e 99 | set +u 100 | ### Avoid recursively calling this script. 101 | if [[ $UF_MASTER_SCRIPT_RUNNING ]] 102 | then 103 | exit 0 104 | fi 105 | set -u 106 | export UF_MASTER_SCRIPT_RUNNING=1 107 | ### Constants. 108 | UF_TARGET_NAME=${PROJECT_NAME} 109 | FRAMEWORK_VERSION="A" 110 | UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal 111 | IPHONE_DEVICE_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos 112 | IPHONE_SIMULATOR_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphonesimulator 113 | ### Functions 114 | ## List files in the specified directory, storing to the specified array. 115 | # 116 | # @param $1 The path to list 117 | # @param $2 The name of the array to fill 118 | # 119 | ## 120 | list_files () 121 | { 122 | filelist=$(ls "$1") 123 | while read line 124 | do 125 | eval "$2[\${#$2[*]}]=\"\$line\"" 126 | done <<< "$filelist" 127 | } 128 | ### Take build target. 129 | if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]] 130 | then 131 | SF_SDK_PLATFORM=${BASH_REMATCH[1]} # "iphoneos" or "iphonesimulator". 132 | else 133 | echo "Could not find platform name from SDK_NAME: $SDK_NAME" 134 | exit 1 135 | fi 136 | ### Build simulator platform. (i386, x86_64) 137 | echo "========== Build Simulator Platform ==========" 138 | echo "===== Build Simulator Platform: i386 =====" 139 | xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_SIMULATOR_BUILD_DIR}/i386" SYMROOT="${SYMROOT}" ARCHS='i386' VALID_ARCHS='i386' $ACTION 140 | echo "===== Build Simulator Platform: x86_64 =====" 141 | xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_SIMULATOR_BUILD_DIR}/x86_64" SYMROOT="${SYMROOT}" ARCHS='x86_64' VALID_ARCHS='x86_64' $ACTION 142 | ### Build device platform. (armv7, arm64) 143 | echo "========== Build Device Platform ==========" 144 | echo "===== Build Device Platform: armv7 =====" 145 | xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7" SYMROOT="${SYMROOT}" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s' $ACTION 146 | echo "===== Build Device Platform: arm64 =====" 147 | xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64" SYMROOT="${SYMROOT}" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION 148 | ### Build device platform. (arm64, armv7) 149 | echo "========== Build Universal Platform ==========" 150 | ## Copy the framework structure to the universal folder (clean it first). 151 | rm -rf "${UNIVERSAL_OUTPUTFOLDER}" 152 | mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" 153 | ## Copy the last product files of xcodebuild command. 154 | cp -R "${IPHONE_DEVICE_BUILD_DIR}/arm64/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" 155 | ### Smash them together to combine all architectures. 156 | lipo -create "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/i386/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/x86_64/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/armv7/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/arm64/${PROJECT_NAME}.framework/${PROJECT_NAME}" -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" 157 | ### Create standard structure for framework. 158 | # 159 | # If we don't have "Info.plist -> Versions/Current/Resources/Info.plist", we may get error when use this framework. 160 | # 161 | # MyFramework.framework 162 | # |-- MyFramework -> Versions/Current/MyFramework 163 | # |-- Headers -> Versions/Current/Headers 164 | # |-- Resources -> Versions/Current/Resources 165 | # |-- Info.plist -> Versions/Current/Resources/Info.plist 166 | # `-- Versions 167 | # |-- A 168 | # | |-- MyFramework 169 | # | |-- Headers 170 | # | | `-- MyFramework.h 171 | # | `-- Resources 172 | # | |-- Info.plist 173 | # | |-- MyViewController.nib 174 | # | `-- en.lproj 175 | # | `-- InfoPlist.strings 176 | # `-- Current -> A 177 | # 178 | echo "========== Create Standard Structure ==========" 179 | mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" 180 | mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" 181 | mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Headers" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" 182 | mkdir -p "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources" 183 | declare -a UF_FILE_LIST 184 | list_files "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/" UF_FILE_LIST 185 | for file_name in "${UF_FILE_LIST[@]}" 186 | do 187 | if [[ "${file_name}" == "Info.plist" ]] || [[ "${file_name}" =~ .*\.lproj$ ]] 188 | then 189 | mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${file_name}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources/" 190 | fi 191 | done 192 | mv "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/${FRAMEWORK_VERSION}/" 193 | ln -sfh "Versions/Current/Resources/Info.plist" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Info.plist" 194 | ln -sfh "${FRAMEWORK_VERSION}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Versions/Current" 195 | ln -sfh "Versions/Current/${PROJECT_NAME}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" 196 | ln -sfh "Versions/Current/Headers" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Headers" 197 | ln -sfh "Versions/Current/Resources" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Resources" 198 | ### Open the universal folder. 199 | open "${UNIVERSAL_OUTPUTFOLDER}" 200 | ``` 201 | 上面的脚本内容在脚本执行的过程中,会依次编译出支持 i386、x86_64、arm64、armv7、armv7s 的包,然后把各个包中的库文件通过 lipo 工具合并为一个支持各平台的通用库文件,再基于最后一个 xcodebuild 命令打出的包的结构(arm64/DynamicLibrary.framework)和这个通用库文件生成一个支持各个平台的通用 Framwork;最后编译之后会弹出framework的位置: 202 | 203 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135300_d4bff1ab_9027123.png "动9.png") 204 | 205 | 4.设置了通用库之后,还需要在Genral下Embedded Binaries添加一下动态库: 206 | 207 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135309_8a4e784f_9027123.png "动10.png") 208 | 209 | ## 静态库 210 | 211 | 动态 Framework 是开发中优先考虑的代码打包方式,但是为了兼容一些低版本系统对动态库的限制,我们需要打包静态库来使用,实现起来比较简单,在原有的DynamicLibrary项目 Build Settings 下设置 Mach-O Type 值为 Static Library,可以编译出静态库。 212 | 213 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135316_823fb2d6_9027123.png "动11.png") 214 | 215 | 设置为静态库之后动态库的最后一步Embedded Binaries就不用再添加了,如果已经添加建议删除~ 216 | 217 | -------------------------------------------------------------------------------- /iOS底层进阶/性能优化.md: -------------------------------------------------------------------------------- 1 | ## 1.造成tableView卡顿的原因有哪些? 2 | 3 | * 1.最常用的就是cell的重用, 注册重用标识符 4 | 5 | 如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell 6 | 7 | 如果有很多数据的时候,就会堆积很多cell。 8 | 9 | 如果重用cell,为cell创建一个ID,每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell 10 | 11 | * 2.避免cell的重新布局 12 | 13 | cell的布局填充等操作 比较耗时,一般创建时就布局好 14 | 15 | 如可以将cell单独放到一个自定义类,初始化时就布局好 16 | 17 | * 3.提前计算并缓存cell的属性及内容 18 | 19 | 当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度 20 | 21 | 而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入凭虚都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell 22 | 23 | * 4.减少cell中控件的数量 24 | 25 | 尽量使cell得布局大致相同,不同风格的cell可以使用不用的重用标识符,初始化时添加控件, 26 | 27 | 不适用的可以先隐藏 28 | 29 | * 5.不要使用ClearColor,无背景色,透明度也不要设置为0 30 | 31 | 渲染耗时比较长 32 | 33 | * 6.使用局部更新 34 | 35 | 如果只是更新某组的话,使用reloadSection进行局部更 36 | 37 | * 7.加载网络数据,下载图片,使用异步加载,并缓存 38 | 39 | * 8.少使用addView 给cell动态添加view 40 | 41 | * 9.按需加载cell,cell滚动很快时,只加载范围内的cell 42 | 43 | * 10.不要实现无用的代理方法,tableView只遵守两个协议 44 | 45 | * 11.缓存行高:estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同时存在,这两者同时存在才会出现“窜动”的bug。所以我的建议是:只要是固定行高就写预估行高来减少行高调用次数提升性能。如果是动态行高就不要写预估方法了,用一个行高的缓存字典来减少代码的调用次数即可 46 | 47 | * 12.不要做多余的绘制工作。在实现drawRect:的时候,它的rect参数就是需要绘制的区域,这个区域之外的不需要进行绘制。例如上例中,就可以用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否需要绘制image和text,然后再调用绘制方法。 48 | 49 | * 13.预渲染图像。当新的图像出现时,仍然会有短暂的停顿现象。解决的办法就是在bitmap context里先将其画一遍,导出成UIImage对象,然后再绘制到屏幕; 50 | 51 | * 14.使用正确的数据结构来存储数据。 52 | 53 | ##2.如何提升 tableview 的流畅度? 54 | 55 | * 本质上是降低 CPU、GPU 的工作,从这两个大的方面去提升性能。 56 | 57 | CPU:对象的创建和销毁、对象属性的调整、布局计算、文本的计算和排版、图片的格式转换和解码、图像的绘制 58 | 59 | GPU:纹理的渲染 60 | 61 | * 卡顿优化在 CPU 层面 62 | 63 | 尽量用轻量级的对象,比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView 64 | 65 | 不要频繁地调用 UIView 的相关属性,比如 frame、bounds、transform 等属性,尽量减少不必要的修改 66 | 67 | 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性 68 | 69 | Autolayout 会比直接设置 frame 消耗更多的 CPU 资源 70 | 71 | 图片的 size 最好刚好跟 UIImageView 的 size 保持一致 72 | 73 | 控制一下线程的最大并发数量 74 | 75 | 尽量把耗时的操作放到子线程 76 | 77 | 文本处理(尺寸计算、绘制) 78 | 79 | 图片处理(解码、绘制) 80 | 81 | * 卡顿优化在 GPU层面 82 | 83 | 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示 84 | 85 | GPU能处理的最大纹理尺寸是 4096x4096,一旦超过这个尺寸,就会占用 CPU 资源进行处理,所以纹理尽量不要超过这个尺寸 86 | 87 | 尽量减少视图数量和层次 88 | 89 | 减少透明的视图(alpha<1),不透明的就设置 opaque 为 YES 90 | 91 | 尽量避免出现离屏渲染 92 | 93 | 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:[642363427](https://jq.qq.com/?_wv=1027&k=smJUo1U5),不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术,iOS开发者一起交流学习成长! 94 | 95 | 96 | * iOS 保持界面流畅的技巧 97 | 98 | 1.预排版,提前计算 99 | 100 | 在接收到服务端返回的数据后,尽量将 CoreText 排版的结果、单个控件的高度、cell 整体的高度提前计算好,将其存储在模型的属性中。需要使用时,直接从模型中往外取,避免了计算的过程。 101 | 102 | 尽量少用 UILabel,可以使用 CALayer 。避免使用 AutoLayout 的自动布局技术,采取纯代码的方式 103 | 104 | 2.预渲染,提前绘制 105 | 106 | 例如圆形的图标可以提前在,在接收到网络返回数据时,在后台线程进行处理,直接存储在模型数据里,回到主线程后直接调用就可以了 107 | 108 | 避免使用 CALayer 的 Border、corner、shadow、mask 等技术,这些都会触发离屏渲染。 109 | 110 | 3.异步绘制 111 | 112 | 4.全局并发线程 113 | 114 | 5.高效的图片异步加载 115 | 116 | ## 3.APP启动时间应从哪些方面优化? 117 | 118 | App启动时间可以通过xcode提供的工具来度量,在Xcode的Product->Scheme-->Edit Scheme->Run->Auguments中,将环境变量DYLD_PRINT_STATISTICS设为YES,优化需以下方面入手 119 | 120 | * dylib loading time 121 | 122 | 核心思想是减少dylibs的引用 123 | 124 | 合并现有的dylibs(最好是6个以内) 125 | 126 | 使用静态库 127 | 128 | * rebase/binding time 129 | 130 | 核心思想是减少DATA块内的指针 131 | 132 | 减少Object C元数据量,减少Objc类数量,减少实例变量和函数(与面向对象设计思想冲突) 133 | 134 | 减少c++虚函数 135 | 136 | 多使用Swift结构体(推荐使用swift) 137 | 138 | * ObjC setup time 139 | 140 | 核心思想同上,这部分内容基本上在上一阶段优化过后就不会太过耗时 141 | 142 | initializer time 143 | 144 | * 使用initialize替代load方法 145 | 146 | 减少使用c/c++的attribute((constructor));推荐使用dispatch_once() pthread_once() std:once()等方法 147 | 148 | 推荐使用swift 149 | 150 | 不要在初始化中调用dlopen()方法,因为加载过程是单线程,无锁,如果调用dlopen则会变成多线程,会开启锁的消耗,同时有可能死锁 151 | 152 | 不要在初始化中创建线程 153 | 154 | ##4.如何降低APP包的大小 155 | 156 | 降低包大小需要从两方面着手 157 | 158 | * 可执行文件 159 | 160 | 编译器优化:Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 设置为 YES,去掉异常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 设置为 NO, Other C Flags 添加 -fno-exceptions 利用 AppCode 检测未使用的代码:菜单栏 -> Code -> Inspect Code 161 | 162 | 编写LLVM插件检测出重复代码、未被调用的代码 163 | 164 | * 资源(图片、音频、视频 等) 165 | 166 | 优化的方式可以对资源进行无损的压缩 167 | 168 | 169 | 170 | ## 5.如何检测离屏渲染与优化 171 | 172 | * 检测,通过勾选Xcode的Debug->View Debugging-->Rendering->Run->Color Offscreen-Rendered Yellow项。 173 | 174 | * 优化,如阴影,在绘制时添加阴影的路径 175 | 176 | ##6.怎么检测图层混合 177 | 178 | 1、模拟器debug中color blended layers红色区域表示图层发生了混合 179 | 180 | 2、Instrument-选中Core Animation-勾选Color Blended Layers 181 | 182 | 避免图层混合: 183 | 184 | * 确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明 185 | 186 | * 如无特殊需要,不要设置低于1的alpha值 187 | 188 | * 确保UIImage没有alpha通道 189 | 190 | UILabel图层混合解决方法: 191 | 192 | iOS8以后设置背景色为非透明色并且设置label.layer.masksToBounds=YES让label只会渲染她的实际size区域,就能解决UILabel的图层混合问题 193 | 194 | iOS8 之前只要设置背景色为非透明的就行 195 | 196 | 为什么设置了背景色但是在iOS8上仍然出现了图层混合呢? 197 | 198 | UILabel在iOS8前后的变化,在iOS8以前,UILabel使用的是CALayer作为底图层,而在iOS8开始,UILabel的底图层变成了_UILabelLayer,绘制文本也有所改变。在背景色的四周多了一圈透明的边,而这一圈透明的边明显超出了图层的矩形区域,设置图层的masksToBounds为YES时,图层将会沿着Bounds进行裁剪 图层混合问题解决了 199 | 200 | ##7.日常如何检查内存泄露? 201 | 202 | * 目前我知道的方式有以下几种 203 | 204 | Memory Leaks 205 | 206 | Alloctions 207 | 208 | Analyse 209 | 210 | Debug Memory Graph 211 | 212 | MLeaksFinder 213 | 214 | * 泄露的内存主要有以下两种: 215 | 216 | Laek Memory 这种是忘记 Release 操作所泄露的内存。 217 | 218 | Abandon Memory 这种是循环引用,无法释放掉的内存。 219 | 220 | ##8.光栅化 221 | 222 | 光栅化是将几何数据经过一系列变换后最终转换为像素,从而呈现在显示设备上的过程,光栅化的本质是坐标变换、几何离散化 223 | 224 | 我们使用 UITableView 和 UICollectionView 时经常会遇到各个 Cell 的样式是一样的,这时候我们可以使用这个属性提高性能: 225 | 226 | cell.layer.shouldRasterize=YES; 227 | 228 | cell.layer.rasterizationScale=[[UIScreenmainScreen]scale]; 229 | 230 | ## 9.如何优化APP的电量? 231 | 232 | ######程序的耗电主要在以下四个方面: 233 | 234 | * CPU 处理 235 | * 定位 236 | * 网络 237 | * 图像 238 | 239 | ##### 优化的途径主要体现在以下几个方面: 240 | 241 | * 尽可能降低 CPU、GPU 的功耗。 242 | * 尽量少用 定时器。 243 | * 优化 I/O 操作。 244 | * 不要频繁写入小数据,而是积攒到一定数量再写入 245 | * 读写大量的数据可以使用 Dispatch_io ,GCD 内部已经做了优化。 246 | * 数据量比较大时,建议使用数据库 247 | * 网络方面的优化 248 | * 减少压缩网络数据 (XML -> JSON -> ProtoBuf),如果可能建议使用 ProtoBuf。 249 | * 如果请求的返回数据相同,可以使用 NSCache 进行缓存 250 | * 使用断点续传,避免因网络失败后要重新下载。 251 | * 网络不可用的时候,不尝试进行网络请求 252 | * 长时间的网络请求,要提供可以取消的操作 253 | * 采取批量传输。下载视频流的时候,尽量一大块一大块的进行下载,广告可以一次下载多个 254 | * 定位层面的优化 255 | * 如果只是需要快速确定用户位置,最好用 CLLocationManager 的 requestLocation 方法。定位完成后,会自动让定位硬件断电 256 | * 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务 257 | * 尽量降低定位精度,比如尽量不要使用精度最高的 kCLLocationAccuracyBest 258 | * 需要后台定位时,尽量设置 pausesLocationUpdatesAutomatically 为 YES,如果用户不太可能移动的时候系统会自动暂停位置更新 259 | * 尽量不要使用 startMonitoringSignificantLocationChanges,优先考虑 startMonitoringForRegion: 260 | * 硬件检测优化 261 | * 用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件 262 | -------------------------------------------------------------------------------- /iOS进阶提升资源/.keep: -------------------------------------------------------------------------------- 1 | 2021年最新BAT面试专题PDF+学习笔记+学习路线图 2 | 3 | (目前也还在利用时间不断更新知识点) 4 | 5 | 给大家分享的资料包括 iOS面试题PDF+面试文档+学习笔记,高级架构技术进阶脑图、iOS开发面试专题资料,还有高级进阶架构资料 6 | 7 | 包括但不限于 【源码级分析、内存泄露分析、移动架构师、数据结构和算法等全方面的iOS进阶实践技术】 8 | 9 | 希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的! -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/.keep -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/Animation面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/Animation面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/Block面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/Block面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/OC底层面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/OC底层面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/Runloop面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/Runloop面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/Runtime面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/Runtime面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/UI相关面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/UI相关面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/内存管理面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/内存管理面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/多线程面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/多线程面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/性能优化面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/性能优化面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/数据安全及加密.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/数据安全及加密.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/数据结构与算法.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/数据结构与算法.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/网络相关面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/网络相关面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/BAT面试题PDF版/设计模式面试题.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS进阶提升资源/BAT面试题PDF版/设计模式面试题.pdf -------------------------------------------------------------------------------- /iOS进阶提升资源/iOS 内存管理总结.md: -------------------------------------------------------------------------------- 1 | # OC对象的内存管理 2 | 3 | 在iOS中,使用引用计数来管理OC对象的内存。 4 | 5 | 一个新创建的OC对象引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其占用的内存空间。 6 | 7 | 调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。 8 | 9 | * * * 10 | 11 | # 内存管理经验总结 12 | 13 | 当调用alloc、new、copy、mutableCopy方法返回了一个对象,在不需要这个对象时,要调用release或者autorelease来释放它。 14 | 15 | 想拥有某个对象,就让它的引用计数+1;不想再拥有某个对象,就让它的引用计数-1。 16 | 17 | # 引用计数的存储 18 | 19 | 引用计数存在优化过的isa共用体的extra_rc中,当数字过大,共用体分配的extra_rc的内存不够用了,共用体的has_sidetable_rc就会变为1,然后引用计数就会存储在SideTable中的refcnts散列表中。 20 | 21 | * * * 22 | 23 | # 内存布局 24 | 25 | 内存地址从低到高,一共分为代码段,数据段,堆,栈。 26 | 27 | 代码段: 存放编译之后的代码。 28 | 29 | 数据段: 1.字符串常量:比如 Nsstring *str=@"123" 2.已初始化数据:已初始化的全局变量、静态变量等。 3.未初始化数据:末初始化的全局变量、静态变量等。 30 | 31 | 堆: 通过alloc、 malloc、 calloc等动态分配的空间。 分配的内存空间地址越来越大。 32 | 33 | 栈: 函数调用开销,比如局部变量。 分配的内存空间地址越来越小。 34 | 35 | # 深拷贝、浅拷贝、copy、mutableCopy 36 | 37 | 深拷贝: 内容拷贝,产生新的对象。 38 | 39 | 浅拷贝: 指针拷贝,没有产生新的对象。 40 | 41 | copy: 不可变拷贝,产生不可变副本。 42 | 43 | mutableCopy: 可变拷贝,产生可变副本。 44 | 45 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/151749_3070babc_9027123.png "gz.png") 46 | 47 | 结论: 48 | 49 | 1.对字符串,字典,数组进行mutableCopy操作,属于深拷贝,并且会生成一个新的可变对象。 50 | 51 | 2.对可变字符串,字典,数组进行copy操作,属于深拷贝,并且会生成一个新的不可变对象。 52 | 53 | 3.对不可变字符串,字典,数组进行copy操作,属于浅拷贝,不会生成一个新的对象。 54 | 55 | * * * 56 | 57 | # 补充 58 | 59 | 1.refcnts是一个存放着对象引用计数的散列表。 -------------------------------------------------------------------------------- /iOS进阶提升资源/iOS 各种UI控件属性设置.md: -------------------------------------------------------------------------------- 1 | ``` 2 | //视图已经加载完了,可以进行ui的添加了 3 | - (void)viewDidLoad { 4 | [superviewDidLoad]; 5 | // Do any additional setup after loading the view. 6 | //初始化UILabel注意指定该对象的位置及大小 7 | UILabel *lb = [[UILabelalloc]initWithFrame:CGRectMake(0,20,300,200)]; 8 | //设置文字 9 | lb.text =@"label测试我在学习中学些ui story水电费水电费未入围 i肉煨入味哦水电费水电费水电费"; 10 | //设置背景色 11 | lb.backgroundColor = [UIColorcolorWithRed:0green:191.0/255.0blue:243.0/255.0alpha:1.0]; 12 | //设置文字颜色 13 | lb.textColor = [UIColorwhiteColor]; 14 | //文字大小,文字字体 15 | lb.font = [UIFontsystemFontOfSize:25]; 16 | NSLog(@"系统字体名字:%@",lb.font.familyName); 17 | //打印文字字体列表 18 | NSArray *arrFonts = [UIFontfamilyNames]; 19 | NSLog(@"系统字体列表:%@",arrFonts); 20 | //文字对齐 21 | lb.textAlignment =NSTextAlignmentJustified; 22 | // NSTextAlignmentLeft = 0, //居左对齐,默认 23 | // NSTextAlignmentCenter = 1, //居中对齐 24 | // NSTextAlignmentRight = 2, //居右对齐 25 | // NSTextAlignmentJustified = 3, // Fully-justified. The last line in a paragraph is natural-aligned. 26 | // NSTextAlignmentNatural = 4, // Indicates the default alignment for script 27 | 28 | //换行模式 29 | lb.lineBreakMode =NSLineBreakByCharWrapping; 30 | // NSLineBreakByWordWrapping = 0, //每一行的结尾以字或者一个完整单词换行(若不够一个单词的位置) 31 | // NSLineBreakByCharWrapping,//在每一行的结尾以字母进行换行 32 | // NSLineBreakByClipping,// Simply clip 33 | // NSLineBreakByTruncatingHead,// Truncate at head of line: "...wxyz" 34 | // NSLineBreakByTruncatingTail,// Truncate at tail of line: "abcd..." 35 | // NSLineBreakByTruncatingMiddle// Truncate middle of line: "ab...yz" 36 | 37 | //指定行数,0为不限制行树,可以指定具体的数字 38 | lb.numberOfLines =0; 39 | //加圆角 40 | lb.layer.cornerRadius =30; 41 | //此行必须加,将原来的矩形角剪掉 42 | lb.clipsToBounds =YES; 43 | //加边框颜色,宽度,注意给layer加的颜色是CGColor类型 44 | lb.layer.borderColor = [[UIColorredColor]CGColor]; 45 | lb.layer.borderWidth =1.0; 46 | 47 | //把label添加到视图上,并且会显示 48 | [self.viewaddSubview:lb]; 49 | } 50 | ``` 51 | 52 | Label的首行缩进一直是个很头疼的问题,现在IOS6只有有一个 attributedText的属性值得我们深究,可以达到我们自定义的行高,还有首行缩进,各种行距和间隔问题。下面这个是两个Label, 一个是UserName,另一个是Content文本多行信息 53 | 54 | **创建标签** 55 | 56 | ``` 57 | @interface ViewController : UIViewController 58 | @property ( weak , nonatomic ) IBOutlet UILabel *usernameLabel 59 | @property ( weak , nonatomic ) IBOutlet UILabel *contentLabel; 60 | @end 61 | ``` 62 | 63 | **视图展示层** 64 | 65 | ``` 66 | - ( void )viewDidLoad { 67 | self . usernameLabel . text = @"用户名Jordan CZ: " ; 68 | self . usernameLabel . adjustsFontSizeToFitWidth = YES ; 69 | [ self . usernameLabel sizeToFit ]; 70 | self . contentLabel . text = @"首行缩进根据用户昵称自动调整 间隔可自定根据需求随意改变。。。。。。。" ; 71 | self . contentLabel . adjustsFontSizeToFitWidth = YES ; 72 | self . contentLabel . adjustsLetterSpacingToFitWidth = YES ; 73 | [ self resetContent ]; 74 | } 75 | ``` 76 | 77 | **自适应计算间距** 78 | 79 | ``` 80 | - ( void )resetContent{ 81 | NSMutableAttributedString *attributedString = [[ NSMutableAttributedString alloc ]initWithString : self . contentLabel . text ]; 82 | NSMutableParagraphStyle *paragraphStyle = [[ NSMutableParagraphStyle alloc ]init ]; 83 | paragraphStyle. alignment = NSTextAlignmentLeft ; 84 | paragraphStyle. maximumLineHeight = 60 ; //最大的行高 85 | paragraphStyle. lineSpacing = 5 ; //行自定义行高度 86 | [paragraphStyle setFirstLineHeadIndent : self . usernameLabel . frame . size .width + 5 ]; //首行缩进 根据用户昵称宽度在加5个像素 87 | [attributedString addAttribute : NSParagraphStyleAttributeName value:paragraphStyle range : NSMakeRange ( 0 , [ self . contentLabel . text length ])]; 88 | self . contentLabel . attributedText = attributedString; 89 | [ self . contentLabel sizeToFit ]; 90 | } 91 | ``` 92 | 93 | ## UITextView的使用详解 94 | 95 | ``` 96 | //初始化并定义大小 97 | UITextView *textview = [[UITextView alloc] initWithFrame:CGRectMake(20, 10, 280, 30)]; 98 | textview.backgroundColor=[UIColor whiteColor]; //背景色 99 | textview.scrollEnabled = NO; //当文字超过视图的边框时是否允许滑动,默认为“YES” 100 | textview.editable = YES; //是否允许编辑内容,默认为“YES” 101 | textview.delegate = self; //设置代理方法的实现类 102 | textview.font=[UIFont fontWithName:@"Arial" size:18.0]; //设置字体名字和字体大小; 103 | textview.returnKeyType = UIReturnKeyDefault;//return键的类型 104 | textview.keyboardType = UIKeyboardTypeDefault;//键盘类型 105 | textview.textAlignment = NSTextAlignmentLeft; //文本显示的位置默认为居左 106 | textview.dataDetectorTypes = UIDataDetectorTypeAll; //显示数据类型的连接模式(如电话号码、网址、地址等) 107 | textview.textColor = [UIColor blackColor]; 108 | textview.text = @"UITextView详解";//设置显示的文本内容 109 | [self.view addSubview:textview]; 110 | ``` 111 | 112 | **UITextView的代理方法如下:** 113 | 114 | ``` 115 | //将要开始编辑 116 | - (BOOL)textViewShouldBeginEditing:(UITextView *)textView; 117 | 118 | //将要结束编辑 119 | - (BOOL)textViewShouldEndEditing:(UITextView *)textView; 120 | 121 | //开始编辑 122 | - (void)textViewDidBeginEditing:(UITextView *)textView; 123 | 124 | //结束编辑 125 | - (void)textViewDidEndEditing:(UITextView *)textView; 126 | 127 | //内容将要发生改变编辑 128 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text; 129 | 130 | //内容发生改变编辑 131 | - (void)textViewDidChange:(UITextView *)textView; 132 | 133 | //焦点发生改变 134 | - (void)textViewDidChangeSelection:(UITextView *)textView; 135 | ``` 136 | 137 | **有时候我们要控件自适应输入的文本的内容的高度,只要在textViewDidChange的代理方法中加入调整控件大小的代理即可** 138 | 139 | ``` 140 | 141 | - (void)textViewDidChange:(UITextView *)textView{ 142 | //计算文本的高度 143 | CGSize constraintSize; 144 | constraintSize.width = textView.frame.size.width-16; 145 | constraintSize.height = MAXFLOAT; 146 | CGSize sizeFrame =[textView.text sizeWithFont:textView.font 147 | constrainedToSize:constraintSize 148 | lineBreakMode:UILineBreakModeWordWrap]; 149 | 150 | //重新调整textView的高度 151 | textView.frame =CGRectMake(textView.frame.origin.x,textView.frame.origin.y,textView.frame.size.width,sizeFrame.height+5); 152 | } 153 | 154 | ``` 155 | 156 | **控制输入文字的长度和内容,可通调用以下代理方法实现** 157 | 158 | ``` 159 | - (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text 160 | { 161 | if (range.location>=100) 162 | { 163 | //控制输入文本的长度 164 | return NO; 165 | } 166 | if ([text isEqualToString:@"\n"]) { 167 | //禁止输入换行 168 | return NO; 169 | } 170 | else 171 | { 172 | return YES; 173 | } 174 | } 175 | ``` 176 | 177 | **UITextView退出键盘的几种方式** 178 | **因为iphone的软键盘没有自带的退键盘键,所以要实现退出键盘需要自己实现,有如下几种方式:** 179 | 1)如果你程序是有导航条的,可以在导航条上面加多一个Done的按钮,用来退出键盘,当然要先实UITextViewDelegate。 180 | 181 | ``` 182 | - (void)textViewDidBeginEditing:(UITextView *)textView { 183 | 184 | UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone 185 | target:self 186 | action:@selector(dismissKeyBoard)]; 187 | 188 | self.navigationItem.rightBarButtonItem = done; 189 | 190 | [done release]; 191 | done = nil; 192 | 193 | } 194 | 195 | - (void)textViewDidEndEditing:(UITextView *)textView { 196 | self.navigationItem.rightBarButtonItem = nil; 197 | } 198 | 199 | - (void)dismissKeyBoard { 200 | [self.textView resignFirstResponder]; 201 | } 202 | ``` 203 | 204 | 2)如果你的textview里不用回车键,可以把回车键当做退出键盘的响应键。 205 | 代码如下: 206 | 207 | ``` 208 | -(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text 209 | { 210 | if ([text isEqualToString:@"\n"]) { 211 | [textView resignFirstResponder]; 212 | return NO; 213 | } 214 | return YES; 215 | } 216 | ``` 217 | 218 | 3)还有你也可以自定义其他加载键盘上面用来退出,比如在弹出的键盘上面加一个view来放置退出键盘的Done按钮。 219 | 代码如下: 220 | 221 | ``` 222 | UIToolbar * topView = [[UIToolbar alloc]initWithFrame:CGRectMake(0, 0, 320,30)]; 223 | [topView setBarStyle:UIBarStyleBlack]; 224 | 225 | UIBarButtonItem *btnSpace = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace 226 | target:self 227 | action:nil]; 228 | 229 | UIBarButtonItem *doneButton = [[UIBarButtonItem alloc]initWithTitle:@"Done" 230 | style:UIBarButtonItemStyleDone 231 | target:self 232 | action:@selector(dismissKeyBoard)]; 233 | 234 | NSArray * buttonsArray = @[btnSpace, doneButton];; 235 | [doneButton release]; 236 | [btnSpace release]; 237 | [topView setItems:buttonsArray]; 238 | [textView setInputAccessoryView:topView];//当文本输入框加上topView 239 | [topView release]; 240 | topView = nil; 241 | 242 | -(IBAction)dismissKeyBoard 243 | { 244 | [tvTextView resignFirstResponder]; 245 | } 246 | ``` 247 | -------------------------------------------------------------------------------- /iOS进阶提升资源/iOS安全:Mach-O Type.md: -------------------------------------------------------------------------------- 1 | > iOS/maxOS二进制文件是mach-o格式的,mach-o又分为几种不同的类型。本文介绍了常见的mach-o文件类型以及它们的不同之处。 2 | 3 | 在Xcode->Build Setting -> Mach-O Type中,我们可以选择下面几种类型: 4 | 5 | * Executable 6 | * Dynamic Library 7 | * Bundle 8 | * Static Library 9 | * Relocatable Object File 10 | 11 | 新建Framework项目中,默认是Dynamic Library。 12 | 13 | 实际上,这里的Mach-o Type完整的定义在mach-o/loader.h struct mach_header_64结构的filetype: 14 | 15 | * #define MH_OBJECT 0x1 /* relocatable object file */ 16 | * #define MH_EXECUTE 0x2 /* demand paged executable file */ 17 | * #define MH_FVMLIB 0x3 /* fixed VM shared library file */ 18 | * #define MH_CORE 0x4 /* core file */ 19 | * #define MH_PRELOAD 0x5 /* preloaded executable file */ 20 | * #define MH_DYLIB 0x6 /* dynamically bound shared library */ 21 | * #define MH_DYLINKER 0x7 /* dynamic link editor */ 22 | * #define MH_BUNDLE 0x8 /* dynamically bound bundle file */ 23 | * #define MH_DYLIB_STUB 0x9 /* shared library stub for static */ /* linking only, no section contents */ 24 | * #define MH_DSYM 0xa /* companion file with only debug */ /* sections */ 25 | * #define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */ 26 | 27 | 各种类型说明如下: 28 | 29 | * MH_OBJECT 30 | 31 | .m,.c等文件编译出来的目标文件,文件后缀是.o。Static Library类型产出是MH_OBJECT类型文件的archieve。 32 | 33 | * MH_EXECUTE 34 | 35 | 可单独执行的可执行文件,必须有main方法作为执行入口,app的二进制就是这种类型;对应Executable类型产出; 36 | 37 | * MH_FVMLIB 38 | * MH_CORE 39 | * MH_PRELOAD 40 | * MH_DYLIB 41 | 42 | 动态库文件,包括.dylib文件,动态framework;对应Dynamic Library类型产出。 43 | 44 | * MH_DYLINKER 45 | 46 | 动态链接器,在 iOS中就是/usr/lib/dyld; 47 | 48 | * MH_BUNDLE 49 | 50 | 独立的二进制文件,不支持在项目中添加Link Binary使用。可以在Copy Bundle Resources中作为资源添加。 通过NSBundle load的方式加载;对应Bundle类型产出。典型的例子就是/System/Library/AccessibilityBundles目录的.axbundle后缀的文件。 51 | 52 | * MH_DSYM 53 | 54 | 存储二进制文件符号信息的文件,用于Debug分析; 55 | 56 | /usr/lib/dyld仅会处理MH_BUNDLE, MH_DYLIB,MH_EXECUTE类型文件。 57 | -------------------------------------------------------------------------------- /iOS进阶提升资源/iOS开发---数据结构.md: -------------------------------------------------------------------------------- 1 | ##数组和链表的区别 2 | 3 | - 数组 4 | 地址连续,查找速度快,操作效率低 5 | 存储单元在定义时分配,元素个数固定,内存空间要求高 6 | - 链表 7 | 地址不连续,查找速度慢,操作效率高 8 | 存储单元在程序执行时动态申请,可按需动态增减 9 | 10 | 11 | ##iOS内存分区的情况,五大区域 12 | 13 | 14 | - 栈区 Stack 先进后出 FILO 由编译器自动分配和释放栈空间多线程不共享连续的内存地址,由高向低分配,不会产生碎片空间较小,运行速度较快,效率高栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高 15 | - 堆区 Heap 分配方式类似链表,先进先出 FIFO 一般需要手动分配和释放堆内存多线程共享不连续的内存地址,由低向高分配,容易产生碎片空间较大,运行速度较慢,效率不如栈计算机底层并没有对堆的支持,堆是有C/C++函数库提供的,加上碎片问题,导致堆的效率比栈低 16 | - 全局区 17 | 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域 .data段 ,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域 .bss段 程序结束后由系统释放 18 | - 常量区 19 | 常量字符串就是放在这里的程序结束后由系统释放 20 | - 代码区 21 | 存放函数体的二进制代码 22 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/151908_496d4061_9027123.png "数据1.png") 23 | 24 | 25 | 当一个app启动后,代码区、常量区、全局区大小就已经固定,因此指向这些区的指针不会产生崩溃性的错误。而堆区和栈区是时时刻刻变化的(堆的创建销毁,栈的弹入弹出),所以当使用一个指针指向这个区里面的内存时,一定要注意内存是否已经被释放,否则会产生程序崩溃(也即是野指针报错) 26 | 27 | 28 | 29 | ##Hash 表 30 | 哈希表(Hash table,也叫散列表)是根据键Key直接访问在内存中存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。通俗讲就是把Key通过一个固定的算法函数(hash函数)转换成一个整型数字,然后就对该数字用数组的长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。当使用hash表查询时,就是使用hash函数将key转换成对应的数组下标,并定位到该下标的数组空间里获取value,这样就充分利用到数组的定位性能进行数据定位 31 | 32 | 33 | ##iOS里有哪些地方用到了Hash表 34 | 35 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/151922_0c547023_9027123.png "数据2.png") 36 | 37 | 38 | -------------------------------------------------------------------------------- /iOS进阶提升资源/iOS自动化布局-AutoLayout约束优先级.md: -------------------------------------------------------------------------------- 1 | ### 约束的优先级 2 | 3 | AutoLayout添加的约束中也有优先级(Priority),优先级的数值1~1000,分为两种情况: 4 | 5 | * 一种情况是我们经常添加的各种约束,默认值1000(最大值)优先执行,条件允许的话系统会自动满足我们的约束需求。 6 | * 第二种就是固有约束(intinsic content size)严格说这种更像UILabel和UIButton的一种属性,但是在Autolayout中需要满足属性取值与约束优先级属性结合才能完成图形的绘制 7 | 8 | 当UILabel显示的内容过长或太短,控件就会被拉伸和压缩,当我们不想让控件被拉伸压缩时,就需要设置控件的固有约束(intinsic content size)来实现我们的需求。固有约束分为两种: 9 | 10 | * 扛拉伸优先级(Content Hugging Priority):默认251,优先级越高越不易被拉伸 11 | * 防压缩优先级(Content Compression Resistance Prority):默认750,优先级越高越不易被压缩 12 | 13 | ### 例子 14 | 15 | 两个Label并排放置,左边Label根据内容自适应,右Label距离左Label中间有固定距离,右Label距离右边距有固定距离,在不设置优先级的情况下,左Label要么被拉伸要么被压缩,如下图 16 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152311_4258c73b_9027123.png "先1.png") 17 | 18 | 19 | 我们设置左边Label的抗压缩优先级和抗拉伸优先级都大于右边Label,效果如图 20 | 21 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/152300_5d2824a0_9027123.png "先2.png") 22 | 23 | 24 | 由于左边Label的抗压缩和抗拉伸优先级都高于右边Label,而且其他约束的优先级(1000)也都高于右边label的固有宽度优先级,所以系统选择拉伸或者压缩了右边的Label,实现了我们的需求。 25 | -------------------------------------------------------------------------------- /iOS进阶提升资源/iOS进阶导图.md: -------------------------------------------------------------------------------- 1 | ## 2021年最新BAT面试专题PDF+学习笔记+学习路线图(目前也还在利用时间不断更新知识点) 2 | 3 | 给大家分享的资料包括 iOS面试题PDF+面试文档+学习笔记,高级架构技术进阶脑图、iOS开发面试专题资料,还有高级进阶架构资料 4 | 5 | ![](https://images.gitee.com/uploads/images/2021/0512/152913_4762f2de_9027123.png ) 6 | 7 | 包括但不限于 【源码级分析、内存泄露分析、移动架构师、数据结构和算法等全方面的iOS进阶实践技术】 8 | 9 | 希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的! 10 | 11 | - #### **iOS基础框架底层原理** 12 | ![](https://images.gitee.com/uploads/images/2021/0512/152946_4d8192ab_9027123.jpeg ) 13 | 14 | 资料获取方式: 15 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 16 | 17 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 18 | 19 | 20 | - #### **iOS高级框架底层原理** 21 | ![](https://images.gitee.com/uploads/images/2021/0512/153021_f5c2e2d5_9027123.jpeg ) 22 | 23 | 资料获取方式: 24 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 25 | 26 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 27 | 28 | - #### **iOS音视频底层** 29 | ![](https://images.gitee.com/uploads/images/2021/0512/153050_77db998f_9027123.jpeg ) 30 | 31 | 资料获取方式: 32 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 33 | 34 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 35 | 36 | - #### **iOS核心动画** 37 | ![](https://images.gitee.com/uploads/images/2021/0512/153107_bc956ce9_9027123.jpeg ) 38 | 39 | 资料获取方式: 40 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 41 | 42 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 43 | 44 | - #### **RxSwift从初探到底层分析** 45 | ![](https://images.gitee.com/uploads/images/2021/0512/153125_32c079bb_9027123.jpeg ) 46 | 47 | 资料获取方式: 48 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 49 | 50 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 51 | 52 | - #### **性能优化探索** 53 | ![](https://images.gitee.com/uploads/images/2021/0512/153142_926808f1_9027123.jpeg ) 54 | 55 | 资料获取方式: 56 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 57 | 58 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 59 | 60 | - #### Alamofire框架底层分析 61 | ![](https://images.gitee.com/uploads/images/2021/0512/153158_c25f31cb_9027123.jpeg ) 62 | 63 | 资料获取方式: 64 | - [加BAT技术交流群;点击群号:[642363427](https://jq.qq.com/?_wv=1027&k=x9A2BV87), 请备注得知渠道(如知乎、简书、CSDN等),联系群主或者管理员,群主管理员在线时间(下午1点半到晚上10点半),前往免费领取也可以直接加我QQ:2594695341 更方便第一时间领取哦 ] 65 | 66 | ![](https://images.gitee.com/uploads/images/2021/0512/153004_d07a8b8d_9027123.gif ) 67 | 68 | ## 学习视频 69 | ![](https://images.gitee.com/uploads/images/2021/0512/153236_687f7a7b_9027123.png ) -------------------------------------------------------------------------------- /iOS逆向安防/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS逆向安防/.keep -------------------------------------------------------------------------------- /iOS逆向安防/ARM汇编基础(iOS逆向).md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/135711_9bc72fc1_9027123.png "arm1.png") 2 | 3 | 4 | ## ARM汇编基础 5 | 6 | 在逆向一个功能的时候,往往需要分析大量的汇编代码,在iOS逆向中,ARM汇编是必须掌握的语言,本文总结了ARM汇编的基础知识,如果你想了解更多,请参考狗神的小黄书《iOS逆向逆向工程》或ARM官方手册. 7 | 8 | ###  寄存器,内存和栈 9 | 10 | 在ARM汇编里,操作对象是寄存器,内存和栈 11 | ARM的栈遵循先进后出,是满递减的,向下增长,也就是开口向下,新的变量被存到栈底的位置;越靠近栈底,内存地址越小 12 | 一个名为stackPointer的寄存器保存栈的栈底地址,成为栈地址. 13 | 可以把一个变量给入栈(push)以保存它的值,也可以让它出栈(pop),恢复变量的原始值.在实际操作中,栈地址会不断变化;但是在执行一块代码的前后,栈地址应该是不变的,不然程序就要出问题, 14 | 15 | ### 特殊用途的寄存器 16 | 17 | ARM处理器中的部分寄存器有特殊用途 如下所示: 18 | 19 | | 寄存器 | 用途 | 20 | | --- | --- | 21 | | R0-R3 | 传递参数与返回值 | 22 | | R7 | 帧指针,指向母函数与被调用子函数在栈中的交界 | 23 | | R9 | 在iOS3.0以前被系统保留 | 24 | | R12 | 内部过程调用存储器,dynamic linker会用到它 | 25 | | R13 | sp寄存器 | 26 | | R14 | LR寄存器,保存函数返回地址 | 27 | | R15 | PC寄存器 | 28 | 29 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/152940_31326de0_9027123.gif "arm2.gif") 30 | 31 | 分支跳转与条件判断 32 | 33 | 处理器名为”Program counter”(简称PC)的寄存器用于存放下一条指令的地址.一般情况下,计算机一条接一条地顺序执行指令,处理器执行完一条指令后将PC加1,让它指向下一条指令.例如处理器顺序执行指令1到指令5,但是如果把PC的值变一变,指令执行的顺序就完全不同指令执行顺序被打乱,变成了指令1,指令5,指令4,指令2,指令3,指令6,这种乱序的学名叫做”分支”,或者”跳转”,它使循环和subroutime(子程序)成为可能,例如: 34 | 35 | ``` 36 | // endless() 函数 37 | ``` 38 | 39 | 在实际情况中,满足一定条件才得以触发的分支是最实用的,这种分支成为条件分支.if else 和 while都是基于条件分支实现的,在ARM汇编中,分支的条件一般有4种: 40 | 41 | * □ 操作结果为0(或不为0); 42 | 43 | * □ 操作结果为负数; 44 | 45 | * □ 操作结果有进位; 46 | 47 | * □ 运算溢出(比如两个正数相加得到的数超过了寄存器位数). 48 | 49 | 这些条件的判断准则(flag)存放在程序状态寄存器(Program Status Register,PSR)中,数据处理相关指令会改变这些flag,分支指令再根据这些flag决定是否跳转.下面的伪代码展示了一个for循环 50 | 51 | ``` 52 | for: 53 | ``` 54 | 55 | ## ARM/THUMB指令解读 56 | 57 | ARM处理器用到的指令集分为ARM和THUMB两种:ARM指令长度均为32bit,THUMB指令长度为16bit.所有指令可大致分为三类,分别为,数组操作指令,内存操作指令和分支指令. 58 | 59 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/152950_693db6f3_9027123.gif "arm3.gif") 60 | 61 | ### 数据操作指令 62 | 63 | 数据操作指令有以下2条规则: 64 | 65 | > * 所有的操作数均为32bit; 66 | > 67 | > 68 | > * 所有的结果均为32bit,且只能存放在寄存器当中. 69 | > 总的来说,数据操作指令的基本格式是: 70 | 71 | ``` 72 | op{cond}{s} Rd,Rn,Op2 73 | ``` 74 | 75 | 其中,”cond”和”s”是另个可选后缀;”cond”的作用是指定指令”op”在什么条件下执行,共有17中条件: 76 | 77 | | 指令 | 条件 | 78 | | --- | --- | 79 | | EQ | 结果为0(EQual to 0) | 80 | | NE | 结果不为0(Not Equal to 0) | 81 | | CS | 有进位或借位(Carry Set) | 82 | | HS | 同CS(unsigned Higer or Same) | 83 | | CC | 没有进位或借位(Carry Clear) | 84 | | LO | 同CC(unsigned LOwer) | 85 | | MI | 结果小于0(MInus) | 86 | | PL | 结果大于等于0(PLus) | 87 | | VS | 溢出(Overflow Set) | 88 | | VC | 无溢出(Overflow Clear) | 89 | | HI | 无符号比较大于(unsigned HIger) | 90 | | LS | 无符号比较小于等于(unsigned Lower or Same) | 91 | | GE | 有符号比较大于等于(signed Greater than or Equal) | 92 | | LT | 有符号比较小于(signed Less Than) | 93 | | GT | 有符号比较大于(signed Greater Than) | 94 | | LE | 有符号比较小于等于(signed Less than or Equal) | 95 | | AL | 无条件(Always,默认) | 96 | 97 | “cond”的用法很简单,例如: 98 | 99 | ``` 100 | 比较 R0, R1 101 | ``` 102 | 103 | 比较R0和R1的值,如果R0大于等于R1,则R2 = R0;否则R2 = R1. 104 | “s”的作用是指定指令”op”是否设置了flag,共有下面4种flag: 105 | 106 | * *N(Negative)*如果结果小于0则置1,否则置0; 107 | 108 | * *Z(zero)*如果结果是0则置1,否则置0; 109 | 110 | * *C(Carry)*对于加操作(包括CMN)来说,如果产生进位则置1,否则置0;对于减操作(包括CMP来说),Carry相当于Not-Borrow,如果产生借位则置0,否则置1;对于有移位的非加/减操作来说,C置移出值得最后一位;对于其他的非加/减操作来说,C的值一般不变; 111 | 112 | * *V(overflow)*如果操作导致溢出,则置1,否则置0 113 | 114 | > 需要注意一点的是,C flag表示无符号数运算结果是否溢出;V flag表示有符号数运算结果是否溢出. 115 | 116 | 算数操作指令可以大致分为4类: 117 | 118 | **算数操作** 119 | 120 | > ADD R0,R1,R2; ——————> R0 = R1 + R2ADC R0,R1,R2; ——————> R0 = R1 + R2 + C(array)SUB R0,R1,R2; ——————> R0 = R1 - R2SBC R0,R1,R2; ——————> R0 = R1 - R2 - !CRSB R0,R1,R2; ——————> R0 = R2 - R1RSC R0,R1,R2; ——————> R0 = R2 - R1 - !C算数操作中,ADD和SUB为基础操作,其他均为两者的变种.RSB是”Reverse Sub”的缩写,仅仅是把SUB的两个操作数调换了位置而已;以”C”结尾的变种代表没有进位和借位的加减法,当产生进位或者借位时,将Carrry flag 置为1. 121 | 122 | **逻辑操作** 123 | 124 | > AND R0,R1,R2; ——————> R0 = R1 & R2ORR R0,R1,R2; ——————> R0 = R1 | R2EOR R0,R1,R2; ——————> R0 = R1 ^ R2BIC R0,R1,R2; ——————> R0 = R1 &~ R2MOV RO,R2; ——————> R0 = R2MVN R0,R2; ——————> R0 = ~R2逻辑操作指令都已经用C操作符说明了作用,但是C操作符里的移位操作并没有对位的逻辑操作指令,ARM采用了桶式移位,共有四种指令:LSL 逻辑左移 LSR 逻辑右移 ASR 算术右移ROR 循环右移 125 | 126 | **比较操作** 127 | 128 | > CMP R1,R2; ——————> 执行R1 - R2并依结果设置flag 129 | 130 | CMN R1,R2; ——————> 执行R1 + R2并依结果设置flagTST R1,R2; ——————> 执行R1 & R2并依结果设置flagTEQ R1,R2; ——————> 执行R1 ^ R2并依结果设置flag比较操作其实就是改变flag的算术操作或逻辑操作,只是操作结果不保留在寄存器里而已. 131 | 132 | **乘法操作** 133 | 134 | > MUL R4,R3,R2 ——————> R4 = R3 * R2MLA R4,R3,R2,R1 ——————> R4 = R3 * R2 + R1乘法操作的操作数必须来自寄存器 135 | 136 | ### 内存操作指令 137 | 138 | 内存操作指令的基本格式是: 139 | 140 | ``` 141 | op{cond}{type} Rd,[Rn,Op2] 142 | ``` 143 | 144 | 其中Rn是基址寄存器,用于存放基地址;”cond”的作用与数据操作指令相同;”type”指定指令”op”操作的数据类型,共有四种: 145 | 146 | ``` 147 | B(unsigned Byte) 148 | ``` 149 | 150 | > 如果不指定”type”,则默认是word 151 | > ARM内存操作基础指令只有2个,LDR(loaD Register)将数据从内存中读出来,存到寄存器中;STR(STore Register)将数组从寄存中读出来,存到内存中.两个指令的使用情况如下: 152 | 153 | LDR 154 | 155 | ``` 156 | LDR Rt,[Rn {,#offset}] ; Rt = *(Rn {+ offset}),{}代表可选 157 | ``` 158 | 159 | STR 160 | 161 | ``` 162 | STR Rt,[Rn {,#offset}] ; *(Rn {+ offset}) = Rt 163 | ``` 164 | 165 | 此外,LDR和STR的变种LDRD和STRD还可以操作双字(DoubleWord),即一次性操作两个寄存器,其基本格式如下: 166 | 167 | ``` 168 | op{cond} Rt,Rt2, [Rn {, #offset}] 169 | ``` 170 | 171 | 其用法与原型类似,如下: 172 | 173 | STRD 174 | 175 | ``` 176 | SRTD R4,R5, [R9,#offset] ; *(R9 + offset) = R4;*(R9 + offset + 4) = R5 177 | ``` 178 | 179 | LDRD 180 | 181 | ``` 182 | LDRD R4,R5,[R9,#offset] ; R4 = *(R9 + offset); R5 = *(R9+offset+4) 183 | ``` 184 | 185 | 除LDR和STR外,还可以通过LDM(LoaD Multiple)和STM(STore Multipe)进行块传输,一次性操作多个寄存器.块传输指令的基本格式是 186 | 187 | ``` 188 | op{cond}{}mode] Rd{!},reglist 189 | ``` 190 | 191 | 其中Rd是基址寄存器,可选的”!”制定Rd变化后的值是否写会Rd, reglist是一系列寄存器,用大括号括起来,它们之间可以用”,”分割,也可以用”-“表示一个范围,比如,{R4-R6,R8}表示寄存器,R4,R5,R6,R8;这些寄存器的顺序是按照自身的编号由小到大排列的,与大括号内的排列顺序无关.需要特别注意的是,LDM和STM的操作方向与LDR和STR完全相反:LDM是把从Rd开始,地址连续的内存数据存入reglist中,STM是把reglist中的值存入从Rd开始,地址连续的内存中.此处特别容易混淆“cond” 的作用与数据操作指令相同.”mode”指定R4值得变化的4中规律,如下所示: 192 | 193 | ``` 194 | IA(Increament After)每次传输后增加Rd的值; 195 | ``` 196 | 197 | > 这是什么意思呢?下面以LDM为代表,举一个简单的例子,相信大家一看就明白了. 198 | 199 | > 在执行以下命令后,R4,R5,R6的值分别变成: 200 | 201 | ``` 202 | foo(): 203 | ``` 204 | 205 | > STM指令的作用方式与此类似,不再赘述.LDM和STM的操作与LDR和STR完全相反 206 | 207 | ### 分支指令 208 | 209 | 分支指令可以分为无条件分支和条件分支两种. 210 | 211 | **无条件分支** 212 | 213 | ``` 214 | B Label;PC = Label 215 | ``` 216 | 217 | **无条件分支** 218 | 219 | 跳转分支的cond是依照前面的flag来判断的,它们的对应关系如下: 220 | 221 | | cond | flag | 222 | | --- | --- | 223 | | EQ | Z = 1 | 224 | | NE | Z = 0 | 225 | | CS | C = 1 | 226 | | HS | C = 1 | 227 | | CC | C = 0 | 228 | | LO | C = 0 | 229 | | MI | N = 1 | 230 | | PL | N = 0 | 231 | | VS | V = 1 | 232 | | VC | V = 0 | 233 | | HI | C = 1 & Z = 0 | 234 | | LS | C = 0 | 235 | | GE | N = V | 236 | | LT | N != V | 237 | | GT | Z = 0 & N = V | 238 | | LE | Z = 1 | 239 | 240 | 在条件分支指令钱会有一条数据操作指令来设置flag,分支指令根据falg的值来决定代码走向,举例如下: 241 | 242 | ``` 243 | Label: 244 | ``` 245 | 246 | ### THUMB指令 247 | 248 | THUMB指令集是ARM指令集的一个子集,每条THUMB指令均为16bit;因此THUMB指令比ARM指令更节省空间,且在16位数据总线上的传输效率更高.有得必有失,除了”b”之外,所有的THUMB指令均无法条件执行;桶式移位无法结合其他指令执行;大多数THUMB指令只能使用R0-R7这8个寄存器等.相对于ARM指令,THUMB指令的特点如下: 249 | 250 | * 指令数量减少 251 | 252 | * 没有条件执行 253 | 254 | * 所有指令默认附带* 255 | 256 | * 桶式移位无法结合其他指令执行 257 | 258 | * 寄存器使用受限 259 | 260 | * 立即数和第二操作数使用有限 261 | 262 | * 不支持数据写回 263 | 264 | ##**微信公众号** 265 | 定期发布 Swift、iOS开发等技术文章,也会更新一些自己的学习心得,欢迎大家关注。 266 | 267 | -------------------------------------------------------------------------------- /iOS逆向安防/iOS符号表恢复&逆向支付宝.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153534_5144f1d8_9027123.jpeg "1.jpg") 2 | 3 | # 前言 4 | 5 | 符号表历来是逆向工程中的“必争之地”,而iOS应用在上线前都会裁去符号表,以避免被逆向分析。 6 | 7 | 本文会介绍一个自己写的工具,用于恢复iOS应用的符号表。 8 | 9 | 直接看效果,支付宝恢复符号表后的样子: 10 | 11 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153253_83a66489_9027123.jpeg "2.jpg") 12 | 13 | 文章有点长,请耐心看到最后,亮点在最后。 14 | 15 | # 为什么要恢复符号表 16 | 17 | 逆向工程中,调试器的动态分析是必不可少的,而 Xcode + lldb 确实是非常好的调试利器, 比如我们在Xcode里可以很方便的查看调用堆栈,如上面那张图可以很清晰的看到支付宝登录的RPC调用过程。 18 | 19 | 实际上,如果我们不恢复符号表的话,你看到的调试页面应该是下面这个样子: 20 | 21 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153303_924cee1b_9027123.jpeg "3.jpg") 22 | 23 | 同一个函数调用过程,Xcode的显示简直天差地别。 24 | 25 | 原因是,Xcode显示调用堆栈中符号时,只会显示符号表中有的符号。为了我们调试过程的顺利,我们有必要把可执行文件中的符号表恢复回来。 26 | 27 | # 符号表是什么 28 | 29 | 我们要恢复符号表,首先要知道符号表是什么,他是怎么存在于 Mach-O 文件中的。 30 | 31 | 符号表储存在 Mach-O 文件的 __LINKEDIT 段中,涉及其中的符号表(Symbol Table)和字符串表(String Table)。 32 | 33 | 这里我们用 MachOView 打开支付宝的可执行文件,找到其中的 Symbol Table 项。 34 | 35 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153315_695cdab7_9027123.jpeg "4.jpg") 36 | 37 | 符号表的结构是一个连续的列表,其中的每一项都是一个 `struct nlist`。 38 | 39 | ``` 40 | // 位于系统库 头文件中 41 | struct nlist { 42 | union { 43 | //符号名在字符串表中的偏移量 44 | uint32_t n_strx; 45 | } n_un; 46 | uint8_t n_type; 47 | uint8_t n_sect; 48 | int16_t n_desc; 49 | //符号在内存中的地址,类似于函数指针 50 | uint32_t n_value; 51 | }; 52 | ``` 53 | 54 | 这里重点关注第一项和最后一项,第一项是符号名在字符串表中的偏移量,用于表示函数名,最后一项是符号在内存中的地址,类似于函数指针(这里只说明大概的结构,详细的信息请参考官方Mach O文件格式的文档)。 55 | 56 | 也就是说如果我们知道了符号名和内存地址的对应关系,我们是可以根据这个结构来逆向构造出符号表数据的。 57 | 58 | 知道了如何构造符号表,下一步就是收集符号名和内存地址的对应关系了。 59 | 60 | 61 | 62 | #获取OC方法的符号表 63 | 64 | 因为OC语言的特性,编译器会将类名、函数名等编译进最后的可执行文件中,所以我们可以根据Mach-O文件的结构逆向还原出工程里的所有类,这也就是大名鼎鼎的逆向工具 class-dump 了。class-dump 出来的头文件里是有函数地址的: 65 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153325_aae3f9a0_9027123.jpeg "5.jpg") 66 | 67 | 所以我们只要对class-dump的源码稍作修改,即可获取我们要的信息。 68 | 69 | #符号表恢复工具 70 | 71 | 整理完数据格式,又理清了数据来源,我们就可以写工具了。 72 | 73 | 74 | 我们来看看怎么用这个工具: 75 | 76 | 1.下载源码编译 77 | ``` 78 | git clone --recursive https://github.com/tobefuturer/restore-symbol.git 79 | cd restore-symbol && make 80 | ./restore-symbol 81 | ``` 82 | 2.恢复OC的符号表,非常简单 83 | ``` 84 | ./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol 85 | ``` 86 | 87 | origin_AlipayWallet 为Clutch砸壳后,没有符号表的 Mach-O 文件 88 | -o 后面跟输出文件位置 89 | 90 | 3.把 Mach-O 文件重签名打包,看效果 91 | 92 | 文件恢复符号表后,多出了20M的符号表信息 93 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153334_a2c2bdea_9027123.jpeg "6.jpg") 94 | 95 | Xcode里查看调用栈 96 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153342_c2081ef8_9027123.jpeg "7.jpg") 97 | 98 | 可以看到,OC函数这部分的符号已经恢复了,函数调用栈里已经能看出大致的调用过程了,但是支付宝里,采用了block的回调形式,所以还有很大一部分的符号没能正确显示。 99 | 100 | 下面我们就来看看怎么样恢复这部分block的符号。 101 | 102 | # 获取block的符号信息 103 | 104 | 还是同样的思路,要恢复block的符号信息,我们必须知道block在文件中的储存形式。 105 | 106 | ## block在内存中的结构 107 | 108 | 首先,我们先分析下运行时,block在内存中的存在形式。block在内存中是以一个结构体的形式存在的,大致的结构如下: 109 | ``` 110 | struct __block_impl { 111 | /** 112 | block在内存中也是类NSObject的结构体, 113 | 结构体开始位置是一个isa指针 114 | */ 115 | Class isa; 116 | 117 | /** 这两个变量暂时不关心 */ 118 | int flags; 119 | int reserved; 120 | 121 | /** 122 | 真正的函数指针!! 123 | */ 124 | void (*invoke)(...); 125 | ... 126 | } 127 | ``` 128 | 129 | 说明下block中的isa指针,根据实际情况会有三种不同的取值,来表示不同类型的block: 130 | 131 | 1. _NSConcreteStackBlock 132 | 133 | 栈上的block,一般block创建时是在栈上分配了一个block结构体的空间,然后对其中的isa等变量赋值。 134 | 135 | 2. _NSConcreteMallocBlock 136 | 137 | 堆上的block,当block被加入到GCD或者被对象持有时,将栈上的block复制到堆上,此时复制得到的block类型变为了_NSConcreteMallocBlock。 138 | 139 | 3. _NSConcreteGlobalBlock 140 | 141 | 全局静态的block,当block不依赖于上下文环境,比如不持有block外的变量、只使用block内部的变量的时候,block的内存分配可以在编译期就完成,分配在全局的静态常量区。 142 | 143 | 第2种block在运行时才会出现,我们只关注1、3两种,下面就分析这两种isa指针和block符号地址之间的关联。 144 | 145 | ##block isa指针和符号地址之间的关联 146 | 147 | 分析这部分需要用到IDA这个反汇编软件, 这里结合两个实际的小例子来说明: 148 | 149 | ###1._NSConcreteStackBlock 150 | 151 | 假设我们的源代码是这样很简单的一个block: 152 | ``` 153 | @implementation ViewController 154 | - (void)viewDidLoad { 155 | int t = 2; 156 | void (^ foo)() = ^(){ 157 | NSLog(@"%d", t); //block 引用了外部的变量t 158 | }; 159 | foo(); 160 | } 161 | @end 162 | ``` 163 | 编译完后,实际的汇编长这个样子: 164 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153350_7186204e_9027123.jpeg "8.jpg") 165 | 166 | 实际运行时,block的构造过程是这样: 167 | 168 | 1. 为block开辟栈空间 169 | 2. 为block的isa指针赋值(一定会引用全局变量:`_NSConcreteStackBlock`) 170 | 3. 获取函数地址,赋值给函数指针 171 | 172 | 所以我们可以整理出这样一个特征: 173 | 174 | ***重点来了!!!*** 175 | 176 | ***凡是代码里用到了栈上的block,一定会获取`__NSConcreteStackBlock`作为isa指针,同时会紧接着获取一个函数地址,那个函数地址就是block的函数地址。*** 177 | 178 | 结合下面这个图,仔细理解上面这句话 179 | (这张图和上面那张图是同一个文件,不过裁掉了符号表) 180 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153401_9f3a92b7_9027123.jpeg "9.jpg") 181 | 182 | 利用这个特征,逆向分析时我们可以做如下推断: 183 | 184 | 在一个OC方法里发现引用了`__NSConcreteStackBlock`这个变量,那么在这附近,一定会出现一个函数地址,这个函数地址就是这个OC方法里的一个block。 185 | 186 | 比如上面图中,我们发现 viewDidLoad 里,引用了`__NSConcreteStackBlock`,同时紧接着加载了 sub_100049D4 的函数地址,那我们就可以认定sub_100049D4是viewDidLoad里的一个block, sub_100049D4函数的符号名应该是 viewDidLoad_block. 187 | 188 | ### 2\. _NSConcreteGlobalBlock 189 | 190 | 全局的静态block,是那种不引用block外变量的block,他因为不引用外部变量,所以他可以在编译期就进行内存分配操作,也不用担心block的复制等等操作,他存在于可执行文件的常量区里。 191 | 192 | 不太理解的话,看个例子: 193 | 194 | 我们把源代码改成这样: 195 | ``` 196 | @implementation ViewController 197 | - (void)viewDidLoad { 198 | 199 | void (^ foo)() = ^(){ 200 | //block 不引用外部的变量 201 | NSLog(@"%d", 123); 202 | }; 203 | foo(); 204 | } 205 | @end 206 | ``` 207 | 那么在编译后会变成这样: 208 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153410_cf68a438_9027123.jpeg "10.jpeg") 209 | 210 | 那么借鉴上面的思路,在逆向分析的时候,我们可以这么推断 211 | 212 | 1. 在静态常量区发现一个_NSConcreteGlobalBlock的引用 213 | 2. 这个地方必然存在一个block的结构体数据 214 | 3. 在这个结构体第16个字节的地方会出现一个值,这个值是一个block的函数地址 215 | 216 | ###3\. block 的嵌套结构 217 | 218 | 实际在使用中,可能会出现block内嵌block的情况: 219 | ``` 220 | - (void)viewDidLoad { 221 | dispatch_async(background_queue ,^{ 222 | ... 223 | dispatch_async(main_queue, ^{ 224 | ... 225 | }); 226 | }); 227 | } 228 | ``` 229 | 230 | 231 | 所以这里block就出现了父子关系,如果我们将这些父子关系收集起来,就可以发现,这些关系会构成图论里的森林结构,这里可以简单用递归的深度优先搜索来处理,详细过程不再描述。 232 | 233 | ## block符号表提取脚本(IDA+python) 234 | 235 | 整理上面的思路,我们发现搜索过程依赖于IDA提供各种引用信息,而IDA是提供了编程接口的,可以利用这些接口来提取引用信息。 236 | 237 | IDA提供的是Python的SDK,最后完成的脚本也放在仓库里[search_oc_block/ida_search_block.py](https://github.com/tobefuturer/restore-symbol/blob/master/search_oc_block/ida_search_block.py)。 238 | 239 | ##提取block符号表 240 | 241 | 这里简单介绍下怎么使用上面这个脚本 242 | 243 | 1. 用IDA打开支付宝的 Mach-O 文件 244 | 2. 等待分析完成! 可能要一个小时 245 | 3. Alt + F7 或者 菜单栏 `File -> Script file...` 246 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153420_19619085_9027123.jpeg "11.jpg") 247 | 248 | 4. 等待脚本运行完成,预计30s至60s,运行过程中会有这样的弹窗 249 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153426_9cfbfe5c_9027123.jpeg "12.jpg") 250 | 251 | 5. 弹窗消失即block符号表提取完成 252 | 6. 在IDA打开文件的目录下,会输出一份名为`block_symbol.json`的json格式block符号表 253 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153431_3f905dd2_9027123.jpeg "13.jpg") 254 | 255 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153501_96979250_9027123.jpeg "14.jpg") 256 | 257 | #恢复符号表&实际分析 258 | 259 | 用之前的符号表恢复工具,将block的符号表导入Mach-O文件 260 | ``` 261 | ./restore-symbol ./origin_AlipayWallet -o ./AlipayWallet_with_symbol -j block_symbol.json 262 | ``` 263 | 264 | -j 后面跟上之前得到的json符号表 265 | 266 | 最后得到一份同时具有OC函数符号表和block符号表的可执行文件 267 | 268 | 这里简单介绍一个分析案例, 你就能体会到这个工具的强大之处了。 269 | 270 | 1. 在Xcode里对 `-[UIAlertView show]` 设置断点 271 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153509_3fdffcc1_9027123.jpeg "15.jpg") 272 | 273 | 2. 运行程序,并在支付宝的登录页面输入手机号和*错误的密码*,点击登录 274 | 3. Xcode会在‘密码错误’的警告框弹出时停下,左侧会显示出这样的调用栈 275 | 276 | #一张图看完支付宝的登录过程* 277 | 278 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0528/153515_4ff37118_9027123.jpeg "16.jpg") 279 | 280 | -------------------------------------------------------------------------------- /iOS逆向安防/优化iOS小技巧.md: -------------------------------------------------------------------------------- 1 | ## 卡顿优化 2 | ---- 3 | 添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的 4 | 5 | #### CPU: 6 | 7 | 1. 使用轻量级的对象比如用不到事件处理的地方,可以考虑使用CALayer取代UIView 8 | 2. 不要频繁地调用UIView的相关属性,比如frame、bounds、transform等属性,尽量减少不必要的修改 9 | 3. 尽量提前计算好布局,在有需要时一次性调整对应的属性,不要多次修改属性 10 | 4. Autolayout会比直接设置frame消耗更多的CPU资源 11 | 5. 图片的size最好刚好跟UIImageView的size保持一致 12 | 6. 控制一下线程的最大并发数量 13 | 7. 尽量把耗时的操作放到子线程,如文字尺寸计算、绘制,图片解码、绘制、压缩 14 | 15 | #### GPU: 16 | 17 | 1. 尽量避免短时间内大量图片的显示,尽可能将多张图片合成一张进行显示 18 | 2. GPU能处理的最大纹理尺寸是4096x4096,一旦超过这个尺寸,就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸 19 | 3. 尽量减少视图数量和层次 20 | 4. 减少透明的视图(alpha<1),不透明的就设置opaque为YES 21 | 5. 尽量避免出现离屏渲染(离屏渲染,在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作) 22 | 23 | * 会触发离屏渲染的操作: 24 | 25 | 1. 光栅化,layer.shouldRasterize = YES 26 | 2. 遮 罩,layer.mask 27 | 3. 圆 角,同时设置layer.masksToBounds = YES、layer.cornerRadius大于0 28 | 4. 阴 影,layer.shadowXXX 29 | 30 | ## 耗电优化 31 | ---- 32 | 1. 少用定时器 33 | 2. 尽量不要频繁写入小数据,最好批量一次性写入 34 | 3. 读写大量重要数据时,考虑用dispatch_io,其提供了基于GCD的异步操作文件I/O的API。用dispatch_io系统会优化磁盘访问 35 | 4. 数据量比较大的,建议使用数据库(比如SQLite、CoreData) 36 | 5. 减少、压缩网络数据 37 | 6. 如果多次请求的结果是相同的,尽量使用缓存 38 | 7. 使用断点续传,否则网络不稳定时可能多次传输相同的内容 39 | 8. 网络不可用时,不要尝试执行网络请求 40 | 9. 让用户可以取消长时间运行或者速度很慢的网络操作,设置合适的超时时间 41 | 10. 批量传输,比如,下载视频流时,不要传输很小的数据包,直接下载整个文件或者一大块一大块地下载。如果下载广告,一次性多下载一些,然后再慢慢展示。如果下载电子邮件,一次下载多封,不要一封一封地下载 42 | 11. 如果只是需要快速确定用户位置,最好用CLLocationManager的requestLocation方法。定位完成后,会自动让定位硬件断电 43 | 12. 如果不是导航应用,尽量不要实时更新位置,定位完毕就关掉定位服务 44 | 13. 尽量降低定位精度,比如尽量不要使用精度最高的kCLLocationAccuracyBest 45 | 14. 需要后台定位时,尽量设置pausesLocationUpdatesAutomatically为YES,如果用户不太可能移动的时候系统会自动暂停位置更新 46 | 15. 尽量不要使用startMonitoringSignificantLocationChanges,优先考虑startMonitoringForRegion: 47 | 16. 用户移动、摇晃、倾斜设备时,会产生动作(motion)事件,这些事件由加速度计、陀螺仪、磁力计等硬件检测。在不需要检测的场合,应该及时关闭这些硬件 48 | 49 | ## 启动速度优化 50 | ---- 51 | 1. 减少动态库、合并一些动态库(定期清理不必要的动态库) 52 | 2. 减少Objc类、分类的数量、减少Selector数量(定期清理不必要的类、分类) 53 | 3. 减少C++虚函数数量 54 | 4. Swift尽量使用struct 55 | 5. 用+initialize方法和dispatch_once取代所有的**attribute**((constructor))、C++静态构造器、ObjC的+load 56 | 6. 在不影响用户体验的前提下,尽可能将一些操作延迟,不要全部都放在finishLaunching方法中 57 | 7. 按需加载 58 | 59 | ## 包大小优化 60 | ---- 61 | 1. 资源(图片、音频、视频等)采取无损压缩 62 | 2. 编写LLVM插件检测出重复代码、未被调用的代码 63 | -------------------------------------------------------------------------------- /iOS面试合集/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cz-add/iOS_maker/905d6f85d376138258116f443944e837feb4984c/iOS面试合集/.keep -------------------------------------------------------------------------------- /iOS面试合集/BAT面试分享——iOS开发高级工程师.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/164837_f808b31f_9027123.jpeg "分1.jpg") 2 | 3 | ##序言 4 | 之前也面试别人,现在轮到自己找工作,怎么说呢,现在轮到自己出去面试,怎么说呢,其实还是挺紧张的,原以为自己不会因此紧张或者焦虑,实际上,还是有的,在没找到合适的工作的时候,甚至晚上有点睡不着觉,总觉着有什么事压在心头,睡觉都不安心。既然睡不着,那还是看看资料吧,我有个习惯,睡前看点问题,第二天早上就能想到答案,睡前记点资料,第二天早上就能记得特别深刻,不说废话了,直接进入正题吧。 5 | 6 | ##面试技巧 7 | ###背熟你的简历 8 | **原因**:面试的第一个问题,一般都是让你简单介绍下你自己,或者介绍一下你最近的项目,而一个面试者,如果连自己的简历都无法熟知,对里面提到的项目、技术都无法描述清楚的话,我想没有哪家公司会接受这样的,哪怕你是超级人才,你自我表述不行,估计也会为此头疼。 9 | 10 | `深入了解并熟记部分iOS基础底层知识` 11 | 12 | **原因**:大部分公司无论面试初级还是高级,无论是笔试还是面试,都会问到一系列基础底层题(后面会分享小编总结的一些面试题和答案) 13 | 14 | `保持自信心和沉重冷静的心态` 15 | 16 | **原因**:面试过程中,自信是少不了的,相信你可以, 面试的路上可以自己对自己说一句: `I belive I can ! 反正我就是这么做的,自我的心里暗示吧,其实面对面试官的时候,你自信的状态也会让面试官觉得你是个很有底气的人,至少从感觉上会给你打个高分`。 17 | 另外还有就是保持沉重冷静,如果是让你提供技术方案或者说说某个技术原理,没必要一紧张一咕噜的什么都说,你可以对面试官说:我先想想,然后自己组装记忆中的知识,组装下语言,有条理的说出来,这样能更好的表现你的才能,不是吗? 18 | 19 | `尽量记住面试过程中你回答不出来或者回答存在不妥的问题` 20 | 21 | **原因**:面试失败了没关系,毕竟每个公司的要求不一样,问的问题和你擅长的方面可能有所出入,但是请记住一点:面试过程中那些你回答不出来问题,或者你自己感觉回答不太准确的问题,毕竟知识点就那么多,问题百变,原理不变,面试也是一个学习知识的过程,让你了解大部分公司目前需要或者要求的技术。这次不知道,下次就知道了 22 | 23 | `去面试之前,最好先了解你要去面试公司的情况(包括产品、项目情况)` 24 | 25 | **原因**:俗话说,知己知彼,百战不殆,面试就是一场战斗,你需要了解你面试公司基本情况,包括岗位要求,这样你就能大概知道你需要面试的这家公司的技术要求情况。 26 | 27 | `合理安排你的面试时间(如果有多家公司的面试机会,尽量把你想去的公司放到最后去面试)` 28 | 29 | **原因**:估计很多人都不理解这个,可能大部分的人对于如何安排面试时间比较迷茫,随意安排。可是这里有个技巧,如果同时有多个面试机会,你把你最想去的公司放到最末尾去面试,这样你经历过了前面的这些公司筛选,如果成功了是个机会,如果没成功,也是为最后做铺垫。而且建议安排一天的面试公司不要超过两家,最好的是上午一家,下午一家,这样你能有充足的时间调整状态。 30 | 31 | 小编给大家推荐一个iOS技术交流群:642363427!群内提供数据结构与算法、底层进阶、swift、逆向、底层面试题整合文档等免费资料! 32 | ### 内容管理 33 | 34 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165120_d63e3e8a_9027123.png "分2.png") 35 | ### Swift 36 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165132_9f2bc5cf_9027123.png "分3.png") 37 | 38 | 39 | 40 | ### 数据存储 41 | 42 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165252_e1db8831_9027123.png "分4.png") 43 | 44 | -------------------------------------------------------------------------------- /iOS面试合集/Objective-C与Swift的贯通编程.md: -------------------------------------------------------------------------------- 1 |  Swift 被设计用来无缝兼容 Cocoa 和 Objective-C 。在 Swift 中,你可以使用 Objective-C 的 API(包括系统框架和你自定义的代码),你也可以在 Objective-C中 使用 Swift 的 API。这种兼容性使 Swift 变成了一个简单、方便并且强大的工具集成到你的 Cocoa 应用开发工作流程中。下面通过一个案例演示,实现Swift与Object-C的混合编程。 2 | 3 | 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:[642363427](https://jq.qq.com/?_wv=1027&k=dCPZiOpH),不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术! 4 | 5 | 步骤一:创建工程文件,名为Person。注意选择编程语言为Swift。 6 | 7 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152018_bb90ee83_9027123.png "oc1.png") 8 | 9 | 10 | 步骤二:接下来就是要实现OC跟Swift的混合编程啦!首先创建一个Person类将他加入到工程中,语言选择为:Objective-C 11 | 12 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152349_ad5c6d40_9027123.png "oc2.png") 13 | 14 | 15 | 步骤三:单击Finsh按钮,会出现下图中的提示框,此处单击YES,系统会自动生成桥接文件。 16 | 17 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152408_c3110b38_9027123.png "oc3.png") 18 | 19 | 20 | 这是可以看到,系统已经创建出一个名为Person-Bridging-Header.h文件啦!,然后选中该文件将#import "Person.h"包含进去 21 | 22 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152438_d3ad2c70_9027123.png "oc4.png") 23 | 24 | 25 | 这是我们拷贝下系统创建的桥接文件名,在工程中进行搜索,可以看到配置文件 26 | 27 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152452_71a15105_9027123.png "oc5.png") 28 | 29 | 30 | 步骤四:Person类创建好后,我们先不用去写代码,接下来再去创建一个House类,不过此类是Swift语言编写的。 31 | 32 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152508_f01e0a2f_9027123.png "oc6.png") 33 | 34 | 35 | 在House类中,定义成员变量,初始化方法,以备Person类调用。 36 | 37 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152523_b0849e0b_9027123.png "oc7.png") 38 | 39 | 40 | 为防止后期,连接时无法使用,此处对该文件进行编译,如下图。 41 | 42 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/152550_abb9daca_9027123.jpeg "oc8.jpg") 43 | 44 | 步骤五:剩下来要做的工作就是编写代码啦!手写在Person类中使用前向声明调用House,然后声明几个成员变量, 45 | 46 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/150705_894f2052_9027123.png "oc9.png") 47 | 48 | 49 | 为之后测试做准备,在Person.m文件中去重写description方法,下图中的选中部分,是系统桥接时生成的文件。 50 | 51 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/150717_9a9a7415_9027123.png "oc10.png") 52 | 53 | 54 | 步骤六:在控制器中使用Person和House 55 | 56 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/150731_283c2400_9027123.png "oc11.png") 57 | 58 | 59 | 步骤七:打印输出结果 60 | 61 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/150741_f791f9fe_9027123.png "oc12.png") 62 | 63 | -------------------------------------------------------------------------------- /iOS面试合集/iOS BAT面试对答题.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/160451_dc2ece63_9027123.jpeg "对1.jpg") 2 | 3 | #Runtime相关面试问题 4 | 5 | ##1.Runtime是什么? 6 | 7 | 见名知意,其概念无非就是“因为 Objective-C 是一门动态语言,所以它需要一个运行时系统……这就是 Runtime 系统”云云。对博主这种菜鸟而言,Runtime 在实际开发中,其实就是一组C语言的函数。胡适说:“多研究些问题,少谈些主义”,云山雾罩的概念听多了总是容易头晕,接下来我们直接上runtime思维导图帮助大家理清思路: 8 | 9 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/160505_e89f6ca6_9027123.png "对2.png") 10 | 11 | ##2.objc在向一个对象发送消息时,发生了什么? 12 | 13 | objc在向一个对象发送消息时,runtime会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果一直到根类还没找到,转向拦截调用,走消息转发机制,一旦找到 ,就去执行它的实现IMP 。 14 | 15 | 16 | ##3.objc中向一个nil对象发送消息将会发生什么? 17 | 18 | 如果向一个nil对象发送消息,首先在寻找对象的isa指针时就是0地址返回了,所以不会出现任何错误。也不会崩溃。 19 | 20 | ##4.objc中向一个对象发送消息[obj foo]和objc_msgSend()函数之间有什么关系? 21 | 22 | 在objc编译时,[obj foo] 会被转意为:objc_msgSend(obj, @selector(foo));。 23 | 24 | ##5.什么时候会报unrecognized selector的异常? 25 | 26 | objc在向一个对象发送消息时,runtime库会根据对象的isa指针找到该对象实际所属的类,然后在该类中的方法列表以及其父类方法列表中寻找方法运行,如果,在最顶层的父类中依然找不到相应的方法时,会进入消息转发阶段,如果消息三次转发流程仍未实现,则程序在运行时会挂掉并抛出异常unrecognized selector sent to XXX 。 27 | 28 | ##6.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么? 29 | 30 | 不能向编译后得到的类中增加实例变量; 31 | 32 | 能向运行时创建的类中添加实例变量; 33 | 34 | **1.**因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表和 instance_size 实例变量的内存大小已经确定,同时runtime会调用 class_setvarlayout 或 class_setWeaklvarLayout 来处理strong weak 引用.所以不能向存在的类中添加实例变量。                35 |   **2.**运行时创建的类是可以添加实例变量,调用class_addIvar函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上. 36 | 37 | ##7.给类添加一个属性后,在类结构体里哪些元素会发生变化? 38 | 39 | instance_size :实例的内存大小;objc_ivar_list *ivars:属性列表 40 | 41 | ##8.一个objc对象的isa的指针指向什么?有什么作用? 42 | 43 | 指向他的类对象,从而可以找到对象上的方法Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。 44 | 45 | 每个Class都有一个isa指针指向唯一的Meta classRoot class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路。每个Meta class的isa指针都指向Root class (meta)。 46 | 47 | ##9.runtime如何通过selector找到对应的IMP地址? 48 | 49 | 每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实selector本质就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现. 50 | 51 | ##10._objc_msgForward函数是做什么的,直接调用它将会发生什么? 52 | 53 | _objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。 54 | 55 | ##11.runtime如何实现weak变量的自动置nil?知道SideTable吗? 56 | 57 | runtime 对注册的类会进行布局,对于 weak 修饰的对象会放入一个 hash 表中。 58 | 59 |  用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。 60 | 61 | **更细一点的回答:** 62 | 63 | **1.初始化时:**                                                                      runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。                   64 | 65 | **2.添加引用时:**                                                                  objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。                                  66 |   **3.释放时,调用clearDeallocating函数。** 67 | 68 | clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。                                                  SideTable结构体是负责管理类的引用计数表和weak表, 69 | 70 | ##12.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么? 71 | 72 | 无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。                                                                  ** 详解: **                                                                73 | 74 | **1.**调用 -release :                                                            75 | 76 |           引用计数变为零对象正在被销毁,生命周期即将结束. 不能再有新的 __weak 弱引用,否则将指向 nil.调用 [self dealloc]                          **2. **父类调用 -dealloc 继承关系中最直接继承的父类再调用 -dealloc 如果是 MRC 代码 则会手动释放实例变量们(iVars)继承关系中每一层的父类 都再调用 -dealloc                                                                **3. **NSObject 调 -dealloc 只做一件事:调用 Objective-C runtime 中object_dispose() 方法           77 | 78 | **4.**调用 object_dispose()为 C++ 的实例变量们(iVars)调用 destructors为 ARC 状态下的 实例变量们(iVars) 调用 -release 解除所有使用 runtime Associate方法关联的对象 解除所有 __weak 引用 调用 free() 79 | 80 |  ##13.什么是method swizzling(俗称黑魔法) 81 | 82 |       简单说就是进行方法交换在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。  83 | 84 |       利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。 85 | 86 |       每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。                              换方法的几种实现方式利用 method_exchangeImplementations 交换两个方法的实现利用 class_replaceMethod 替换方法的实现利用 method_setImplementation 来直接设置某个方法的IMP 87 | 88 | 89 | 90 | #多线程相关面试问题 91 | 92 | 多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径, 从技术角度来看,一个线程就是一个需要管理执行代码的内核级和应用级数据结 构组合。 93 | 94 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/160520_9849a506_9027123.png "对3.png") 95 | 96 | ##1.iOS系统为我们提供的几种多线程技术各自的特点是咋样的? 97 | 98 | GCD、NSOperation、NSThread。 99 | 100 | **GCD**:简单的线程间同步,包括子线程分派、多读单写等场景的解决。 101 | 102 | **NSOperation**:第三方框架AF,SD都会涉及到NSOperation,他可以对任务的状态进行控制,可以添加依赖,删除依赖。 103 | 104 | **NSThread**:实现常驻线程。 105 | 106 | 107 | 108 | #RunLoop相关面试问题 109 | 110 | 我相信大多数开发者一样,迷惑于runloop,最初只了解可以通过runloop一些监听事件的通知来做一些事情,优化性能。关于runloop源码的基础知识,可以参考下面的思维导图: 111 | 112 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/160534_f5222e7e_9027123.png "对4.png") 113 | 114 | **RunLoop运行流程** 115 | 116 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/160547_0df3c7f1_9027123.jpeg "对5.jpg") 117 | 118 | 没有事情的时候,Runloop处于休眠状态。当外部source将其唤醒后,它会依次处理接收到的timer/source,然后再次进入休眠。 119 | 120 | ##1.Runloop和线程是什么关系? 121 | 122 | 每条线程都有唯一的一个与之对应的RunLoop对象; 123 | 124 | 主线程的RunLoop已经自动创建,子线程的RunLoop需要主动创建; 125 | 126 | RunLoop在第一次获取时创建,在线程结束时销毁 127 | 128 | ##2.Runloop的mode作用是什么? 129 | 130 | 指定事件在运行循环中的优先级的, 131 | 132 | 线程的运行需要不同的模式,去响应各种不同的事件,去处理不同情境模式。(比如可以优化tableview的时候可以设置UITrackingRunLoopMode下不进行一些操作,比如设置图片等。) 133 | 134 | ##3.以+scheduledTimerWithTimeInterval:的方式触发的timer,在滑动页面上的列表时,timer会暂停回调, 为什么? 135 | 136 | 滑动scrollView时,主线程的RunLoop会切换到UITrackingRunLoopMode这个Mode,执行的也是UITrackingRunLoopMode下的任务(Mode中的item),而timer是添加在NSDefaultRunLoopMode下的,所以timer任务并不会执行,只有当UITrackingRunLoopMode的任务执行完毕,runloop切换到NSDefaultRunLoopMode后,才会继续执行timer。 137 | 138 | ##4.如何解决在滑动页面上的列表时,timer会暂停回调? 139 | 140 | 将Timer放到NSRunLoopCommonModes中执行即可 141 | 142 | ##5.NSTImer使用时需要注意什么? 143 | 144 | 注意timer添加到runloop时应该设置为什么mode 145 | 146 | 注意timer在不需要时,一定要调用invalidate方法使定时器失效,否则得不到释放 147 | 148 | 149 | 150 | #设计模式相关面试问题 151 | 152 | 设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。 153 | 使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。 154 | 155 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/160559_755896bd_9027123.png "对6.png") 156 | 157 | 158 | 159 | #架构/框架相关面试问题 160 | 161 | “100个读者就有100个哈姆雷特”一样,对于架构的理解不同的软件工程师有不同的看法。架构设计往往是一个权衡的过程,每一个架构设计者都要考虑到各个因素,比如团队成员的技术水平、具体的业务场景、项目的成长阶段和开发周期。下图是小编的一些架构理念,仅供参考: 162 | 163 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/164619_b45a3df0_9027123.png "对7.png") 164 | 165 | ##1.RAC中使用时线程问题?或者RAC的缺点? 166 | ##2.RAC中实现多个信号全部执行结束再执行与 多个信号任意一个结束就响应的处理方式? 167 | ##3.路由跳转的实现方式 ? 168 | 169 | 170 | 171 | #算法相关面试问题 172 | 173 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/164632_f46933d3_9027123.png "对8.png") 174 | 175 | ##1.对以下一组数据进行降序排序(冒泡排序)。“24,17,85,13,9,54,76,45,5,63” 176 | ``` 177 | int main(int argc, char *argv[]) { 178 | int array[10] = {24, 17, 85, 13, 9, 54, 76, 45, 5, 63}; 179 | int num = sizeof(array)/sizeof(int); 180 | for(int i = 0; i < num-1; i++) { 181 | for(int j = 0; j < num - 1 - i; j++) { 182 | if(array[j] < array[j+1]) { 183 | int tmp = array[j]; 184 | array[j] = array[j+1]; 185 | array[j+1] = tmp; 186 | } 187 | } 188 | } 189 | for(int i = 0; i < num; i++) { 190 | printf("%d", array[i]); 191 | if(i == num-1) { 192 | printf("\n"); 193 | } 194 | else { 195 | printf(" "); 196 | } 197 | } 198 | } 199 | ``` 200 | 201 | ##2.对以下一组数据进行升序排序(选择排序)。“86, 37, 56, 29, 92, 73, 15, 63, 30, 8” 202 | ``` 203 | void sort(int a[],int n) 204 | { 205 | int i, j, index; 206 | for(i = 0; i < n - 1; i++) { 207 | index = i; 208 | for(j = i + 1; j < n; j++) { 209 | if(a[index] > a[j]) { 210 | index = j; 211 | } 212 | } 213 | if(index != i) { 214 | int temp = a[i]; 215 | a[i] = a[index]; 216 | a[index] = temp; 217 | } 218 | } 219 | } 220 | int main(int argc, const char * argv[]) { 221 | int numArr[10] = {86, 37, 56, 29, 92, 73, 15, 63, 30, 8}; 222 | sort(numArr, 10); 223 | for (int i = 0; i < 10; i++) { 224 | printf("%d, ", numArr[i]); 225 | } 226 | printf("\n"); 227 | return 0; 228 | } 229 | ``` 230 | 231 | 232 | ##3.实现二分查找算法(编程语言不限) 233 | 234 | ``` 235 | int bsearchWithoutRecursion(int array[],int low,int high,int target) { 236 | while(low <= high) { 237 | int mid = (low + high) / 2; 238 | if(array[mid] > target) 239 | high = mid - 1; 240 | else if(array[mid] < target) 241 | low = mid + 1; 242 | else //findthetarget 243 | return mid; 244 | } 245 | //the array does not contain the target 246 | return -1; 247 | } 248 | ---------------------------------------- 249 | 递归实现 250 | int binary_search(const int arr[],int low,int high,int key) 251 | { 252 | int mid=low + (high - low) / 2; 253 | if(low > high) 254 | return -1; 255 | else{ 256 | if(arr[mid] == key) 257 | return mid; 258 | else if(arr[mid] > key) 259 | return binary_search(arr, low, mid-1, key); 260 | else 261 | return binary_search(arr, mid+1, high, key); 262 | } 263 | } 264 | ``` 265 | 266 | 267 | ##4.如何实现链表翻转(链表逆序)? 268 | 269 | 270 | ``` 271 | #include 272 | #include 273 | typedef struct NODE { 274 | struct NODE *next; 275 | int num; 276 | }node; 277 | node *createLinkList(int length) { 278 | if (length <= 0) { 279 | return NULL; 280 | } 281 | node *head,*p,*q; 282 | int number = 1; 283 | head = (node *)malloc(sizeof(node)); 284 | head->num = 1; 285 | head->next = head; 286 | p = q = head; 287 | while (++number <= length) { 288 | p = (node *)malloc(sizeof(node)); 289 | p->num = number; 290 | p->next = NULL; 291 | q->next = p; 292 | q = p; 293 | } 294 | return head; 295 | } 296 | void printLinkList(node *head) { 297 | if (head == NULL) { 298 | return; 299 | } 300 | node *p = head; 301 | while (p) { 302 | printf("%d ", p->num); 303 | p = p -> next; 304 | } 305 | printf("\n"); 306 | } 307 | node *reverseFunc1(node *head) { 308 | if (head == NULL) { 309 | return head; 310 | } 311 | node *p,*q; 312 | p = head; 313 | q = NULL; 314 | while (p) { 315 | node *pNext = p -> next; 316 | p -> next = q; 317 | q = p; 318 | p = pNext; 319 | } 320 | return q; 321 | } 322 | int main(int argc, const char * argv[]) { 323 | node *head = createLinkList(7); 324 | if (head) { 325 | printLinkList(head); 326 | node *reHead = reverseFunc1(head); 327 | printLinkList(reHead); 328 | free(reHead); 329 | } 330 | free(head); 331 | return 0; 332 | } 333 | ``` 334 | 335 | #第三方库相关面试问题 336 | 337 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/164644_17f37afe_9027123.png "对9.png") 338 | 339 | 340 | 341 | 来源:公众号   iOS进阶宝典 342 | 343 | [查看原文](https://mp.weixin.qq.com/s?__biz=MzI3MjU1MjE2NQ==&mid=2247485958&idx=1&sn=43c47c38d18e7a0178f9e10b1d0ad3ee&chksm=eb31938cdc461a9a864b32da55679af2f6a455ebcb2f8b69f305b60ac48bf9966411268e697a&token=1405749238&lang=zh_CN#rd) 344 | -------------------------------------------------------------------------------- /iOS面试合集/iOS188面试题面试题整理,底层、技术亮点公司需要的这里都有.md: -------------------------------------------------------------------------------- 1 | 100家知名企业今年来iOS面试题合集: 2 | 你要的这里都有; 3 | 4 | 企业要的这里也有; 5 | 6 | 从基础开始到进阶、深入底层 7 | 8 | 整理出188个面试题,全是干货 9 | 10 | 目录展示: 11 | 1、swift和oc的区别 12 | 13 | 2、编译链接 14 | 15 | 3、synthesize & denamic 16 | 17 | 4、在项目开发中常用的开发工具有哪些? 18 | 19 | 5、UITableView & UICollection 20 | 21 | 6、NSProxy & NSObject 22 | 23 | 7、Object & Swift 24 | 25 | 8、传值通知 & 推送通知(本地&远程) 26 | 27 | 9、第三方库 & 第三方平台 28 | 29 | 10、NSCache & NSDcitionary 30 | 31 | 11、 UIView的setNeedsDisplay和setNeedsLayout方法 32 | 33 | 12、UILayer & UIView 34 | 35 | 13、layoutSubViews & drawRects 36 | 37 | 14、UDID & UUID 38 | 39 | 15、CPU & GPU 40 | 41 | 16、点(pt)& 像素(px) 42 | 43 | 17、属性与成员变量 44 | 45 | 18、int和NSInteger的区别 46 | 47 | (1)import和include 48 | (2)@class 49 | (3)全局 & 静态变量 50 | 51 | 19、类和对象 52 | (1)分类拓展协议中哪些可以声明属性? 53 | (2)继承和类别的区别 54 | (3)分类的作用 55 | (4)分类的局限性 56 | 57 | 20、category & extension 58 | 59 | 21、Foundation 60 | (1)字符串 61 | (2)字符串截取 62 | (3)格式 63 | 64 | 22、NSArray和NSDictionary 65 | (1)iOS遍历数组/字典的方法 66 | (2)NSValue NSNumber 67 | (3)其它 68 | (4)如何避免循环引用 69 | 70 | 23、CFSocket使用有哪几个步骤 71 | 72 | 24、Core Foundation中提供了哪几种操作Socket的方法? 73 | 74 | 25、解析XML文件有哪几种方式? 75 | 76 | 26、什么是沙盒模型?哪些操作是属于私有api范畴? 77 | 78 | 27、在一个对象的方法里面:self.name= “object”;和 name =”object” 有什么不同吗? 79 | 80 | 28、请简要说明viewDidLoad和viewDidUnload何时调用 81 | 82 | 29、创建控制器、视图的方式 83 | 84 | 30、简述内存分区情况 85 | 86 | 31、队列和栈有什么区别 87 | 88 | 32、iOS的系统架构 89 | 90 | 33、控件主要响应3种事件 91 | 92 | 34、xib文件的构成分为哪3个图标?都具有什么功能 93 | 94 | 35、简述视图控件器的生命周期 95 | 96 | 36、app 项目的生命周期 97 | (1)应用的生命周期 98 | (2)简要说明一下APP的启动过程,main文件说起,main函数中有什么函数?作用是什么? 99 | (3)UIApplicationMain函数作用 100 | (4)main函数作用 101 | 102 | 37、 动画有基本类型有哪几种;表视图有哪几种基本样式。 103 | 104 | 38、实现简单的表格显示需要设置UITableView的什么属性、实现什么协议? 105 | 106 | 39、Cocoa Touch提供了哪几种Core Animation过渡类型? 107 | 108 | 40、UIView与CLayer有什么区别? 109 | 110 | 41、Quatrz 2D的绘图功能的三个核心概念是什么并简述其作用 111 | 112 | 42、iPhone OS主要提供了几种播放音频的方法? 113 | 114 | 43、使用AVAudioPlayer类调用哪个框架、使用步骤? 115 | 116 | 44、有哪几种手势通知方法、写清楚方法名? 117 | 118 | 45、ViewController的didReceiveMemoryWarning怎么被调用 119 | 120 | 46、什么时候用delegate,什么时候用Notification? 121 | 122 | 47、用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题) 123 | 124 | 48、写一个”标准"宏MIN ,这个宏输入两个参数并返回较小的一个。 125 | 126 | 49、关键字const有什么含意?修饰类呢?static的作用,用于类呢?还有extern c的作用 127 | 128 | 50、关键字volatile有什么含意?并给出三个不同的例子 129 | 130 | 51、一个参数既可以是const还可以是volatile吗? 一个指针可以是volatile 吗?解释为什么。 131 | 132 | 52、static 关键字的作用 133 | 134 | 53、列举几种进程的同步机制,并比较其优缺点。 135 | 136 | 54、进程之间通信的途径 137 | 138 | 55、进程死锁的原因 139 | 140 | 56、死锁的4个必要条件 141 | 142 | 57、死锁的处理 143 | 144 | 58、cocoa touch框架 145 | 146 | 59、自动释放池是什么,如何工作 147 | 148 | 60、sprintf,strcpy,memcpy使用上有什么要注意的地方 149 | 150 | 61、你了解svn,cvs等版本控制工具么? 151 | 152 | 62、什么是push 153 | 154 | 63、静态链接库 155 | 156 | 64、OC三大特性 157 | (1)封装_点语法 158 | (2)继承 159 | (3)多态 160 | 161 | 65、OC中如何实现多态 162 | 163 | 66、Objective-C的优缺点 164 | 165 | 67、对于OC,你认为最大的优点和最大的不足是什么?对于不足之处,现在有没有可用的方法绕过这些不足来实现需求。如果可以话,有没有考虑或者实现过重新实现OC的功能,如果有,具体怎么做? 166 | 167 | 68、oc中可修改和不可以修改类型 168 | 169 | 69、我们说的oc是动态运行时语言是什么意思? 170 | 171 | 70、通知和协议的不同之处? 172 | 173 | 71、什么是推送消息? 174 | 175 | 72、关于多态性 176 | 177 | 73、什么是谓词? 178 | 179 | 74、做过的项目是否涉及网络访问功能,使用什么对象完成网络功能? 180 | 181 | 75、简单介绍下NSURLConnection类及+sendSynchronousRequest:returningResponse:error:与– initWithRequest:delegate:两个方法的区别? 182 | 183 | 76、谈谈Object-C的内存管理方式及过程? 184 | 185 | 77、Object-C有私有方法吗?私有变量呢? 186 | 187 | 78、说说响应链 188 | 189 | 79、时间传递 & 响应者链 190 | 191 | 80、frame和bounds有什么不同? 192 | 193 | 81、方法和选择器有何不同? 194 | 195 | 82、OC的垃圾回收机制? 196 | 197 | 83、什么是延迟加载? 198 | 199 | 84、是否在一个视图控制器中嵌入两个tableview控制器? 200 | 201 | 85、一个tableView是否可以关联两个不同的数据源?你会怎么处理? 202 | 203 | 86、什么时候使用NSMutableArray,什么时候使用NSArray? 204 | 205 | 87、给出委托方法的实例,并且说出UITableVIew的Data Source方法 206 | 207 | 88、在应用中可以创建多少autorelease对象,是否有限制? 208 | 209 | 89、如果我们不创建内存池,是否有内存池提供给我们? 210 | 211 | 90、什么时候需要在程序中创建内存池? 212 | 213 | 91、类NSObject的那些方法经常被使用? 214 | 215 | 92、什么是简便构造方法? 216 | 217 | 93、如何使用Xcode设计通用应用? 218 | 219 | 94、 UIView的动画效果有那些? 220 | 221 | 95、Object-C有多继承吗?没有的话用什么代替?cocoa 中所有的类都是NSObject 的子类 222 | 223 | 96、内存管理 Autorelease、retain、copy、assign的set方法和含义? 224 | 225 | 97、C和obj-c 如何混用 226 | 227 | 98、类别的作用?继承和类别在实现中有何区别? 228 | 229 | 99、类别和类扩展的区别。 230 | 231 | 100、oc中的协议和java中的接口概念有何不同? 232 | 233 | 101、深拷贝与前拷贝区别 234 | (1)什么是深拷贝浅拷贝 235 | (2)字符串什么时候使用copy,strong 236 | (3)字符串所在内存区域 237 | (4)mutablecopy和copy @property(copy) NSMutableArray *arr;这样写有什么问题 238 | (5)如何让自定义类可以使用copy修饰符 239 | 240 | 102、对于语句NSString*obj = [[NSData alloc] init]; obj在编译时和运行时分别时什么类型的对象? 241 | 242 | 103、#import 跟#include 又什么区别,@class呢, #import<> 跟 #import”"又什么区别? 243 | 244 | 104、Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方法用继承好还是分类好?为什么? 245 | 246 | 105、 #import 跟#include 又什么区别,@class呢, #import<> 跟 #import””又什么区别? 247 | 248 | 106、写一个setter方法用于完成@property (nonatomic,retain)NSString *name,写一个setter方法用于完成@property(nonatomic,copy)NSString *name 249 | 250 | 107、常见的Objective-C的数据类型有那些, 和C的基本数据类型有什么区别?如:NSInteger和int 251 | 252 | 108、id 声明的对象有什么特性? 253 | 254 | 109、Objective-C如何对内存管理的,说说你的看法和解决方法? 255 | 256 | 110、原子(atomic)跟非原子(non-atomic)属性有什么区别? 257 | 258 | 111、看下面的程序,第一个NSLog会输出什么?这时str的retainCount是多少?第二个和第三个呢? 为什么? 259 | 260 | 112、内存管理的几条原则时什么?按照默认法则.那些关键字生成的对象需要手动释放?在和property结合的时候怎样有效的避免内存泄露? 261 | 262 | 113、如何对iOS设备进行性能测试? 263 | 264 | 114、设计模式 265 | (1)mvc模式 266 | (2)单例模式 267 | (3)mvvm模式 268 | (4)观察者模式 269 | (5)工厂模式 270 | (6)代理模式 271 | (7)策略模式 272 | (8)适配器模式 273 | (9)模版模式 274 | (10)外观模式 275 | (11)创建模式 276 | (12)MVP模式 277 | 278 | 115、MVVM模式原理分析 279 | 280 | 116、说说常用的几种传值方式 281 | 282 | 117、什么时候用delegate,什么时候用Notification 283 | 284 | 118、对于单例的理解 285 | 286 | 119、从设计模式角度分析代理,通知和KVO区别?ios SDK 提供 的framework使用了哪些设计模式,为什么使用?有哪些好处和坏处? 287 | 288 | 120、KVO,NSNotification,delegate及block区别 289 | 290 | 121、运行时(runTime) 291 | 292 | 122、runtime/消息转发机制 293 | (1)runtime 294 | 295 | 1.1、什么是runtime 296 | 297 | 1.2、runtime干什么用,使用场景 298 | (2)消息机制 299 | 300 | 2.1、消息转发的原理 301 | 302 | 2.2、SEL isa super cmd 是什么 303 | (3)动态绑定 304 | 305 | 123、使用bugly进行崩溃分析 306 | 307 | 124、jenkens 持续打包 308 | 309 | 125、KVO & KVC 310 | (1)底层实现 311 | (2)KVO概述 312 | (3)KVC概述 313 | 314 | 126、什么是KVO和KVC? 315 | 316 | KVO和KVC 317 | (1)如何调用私有变量,如何修改系统的只读属性,KVC的查找顺序 318 | (2)什么是键-值,键路径是什么 319 | (3)kvo的实现机制 320 | (4)KVO计算属性,设置依赖键 321 | (5)KVO集合属性 322 | (6)kvo使用场景 323 | 324 | 127、SDWebImage(SDWebImage的实现机制) 325 | (1)主要功能 326 | (2)缓存 327 | (3)内存缓存与磁盘缓存 328 | 329 | 128、框架 SDWebimage的缓存机制 330 | 331 | 129、网络安全 332 | 密码的安全原则 333 | 334 | 130、多线程 335 | (1)多线程概念 336 | (2)多线程的作用 337 | (3)使用场景 338 | 339 | 131、NSOperationQueue和GCD的区别是什么 340 | 341 | 132、GCD与NSThread的区别 342 | 343 | 133、进程和线程的区别与联系是什么? 344 | 345 | 134、别异步执行两个耗时操作,等两次耗时操作都执行完毕后,再回到主线程执行操作. 使用队列组(dispatch_group_t)快速,高效的实现上述需求 346 | 347 | 135、在项目什么时候选择使用GCD,什么时候选择NSOperation? 348 | 349 | 136、对比iOS中的多线程技术 350 | 351 | 137、多线程优缺点 352 | 353 | 138、iOS中的延迟操作 354 | 355 | 139、串行队列同步执行和异步主队列 356 | 357 | 140、资源抢夺解决方案 358 | 359 | 141、dispatch_barrier_async的作用是什么? 360 | 361 | 142、在多线程Core Data中,NSC,MOC,NSObjectModel哪些需要在线程中创建或者传递?你是用什么策越来实现的? 362 | 363 | 143、+(void)load与 +(void)initialize区别load 和 initialize方法的区别 364 | 365 | 144、http的post与区别与联系,实践中如何选择它们? 366 | 367 | 145、说说关于UDP/TCP的区别? 368 | 369 | 146、http和scoket通信的区别?socket连接相关库,TCP,UDP的连接方法,HTTP的几种常用方式? 370 | 371 | 147、HTTP请求常用的几种方式 372 | 373 | 148、block 374 | (1)使用block时什么情况会发生引用循环,如何解决? 375 | (2)在block内如何修改block外部变量? 376 | (3)Block & MRC-Block 377 | (4)什么是block 378 | (5)block 实现原理 379 | (6)关于block 380 | (7)使用block和使用delegate完成委托模式有什么优点 381 | (8)多线程与block 382 | (9)谈谈对Block 的理解?并写出一个使用Block执行UIVew动画? 383 | (10)写出上面代码的Block的定义(接上题) 384 | 385 | 149、Weak、strong、copy、assign 使用 386 | (1)什么情况使用 weak 关键字,相比 assign 有什么不同? 387 | (2)怎么用 copy 关键字? 388 | (3)weak & strong 389 | (4)这个写法会出什么问题: @property (copy) NSMutableArray *array 390 | (5) 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter? 391 | (6) @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的 392 | (7)ivar、getter、setter 是如何生成并添加到这个类中的? 393 | (8)用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题? 394 | (9)@protocol 和 category 中如何使用 @property 395 | (10)runtime如何通过selector找到对应的IMP地址? 396 | (11)retain和copy区别 397 | (12)copy和strong的使用? 398 | (13)NSString和NSMutableString,前者线程安全,后者线程不安全。 399 | (14)readwrite,readonly,assign,retain,copy,weak ,strong,nonatomic 属性的作用 400 | 401 | 150、OC与JS的交互(iOS与H5混编) 402 | TableView性能优化 403 | UITableView核心思想 404 | UITableView的优化主要从三个方面入手: 405 | 406 | 151、TableView为什么会卡? 407 | 408 | 152、UITableView 409 | (1)UITableView最核心的思想 410 | (2)定义高度 411 | (3)自定义高度原理 412 | (4)老生常谈之UITableView的性能优化 413 | (5)cell高度的计算 414 | (5.1)定高的cell和动态高度的cell 415 | (6)TableView渲染 416 | (7)减少视图的数目 417 | (8)减少多余的绘制操作 418 | (9)不要给cell动态添加subView 419 | (10)异步化UI,不要阻塞主线程 420 | (11)滑动时按需加载对应的内容 421 | (12)离屏渲染的问题 422 | (13)离屏渲染优化方案 423 | 424 | 153、环信SDK使用 425 | 426 | 154、蓝牙 427 | 428 | 155、在iPhone应用中如何保存数据? 429 | 430 | 156、什么是coredata? 431 | 432 | 157、 什么是NSManagedObject模型? 433 | 434 | 158、什么是NSManagedobjectContext? 435 | 436 | 159、 iOS平台怎么做数据的持久化?coredata 和sqlite有无必然联系?coredata是一个关系型数据库吗? 437 | 438 | 160、CoreData & SQLite3 439 | 440 | 161、数据存储 441 | (1)数据存储技术 442 | (1.1)数据存储的几种方式 443 | (1.2)各自特点(面试考点) 444 | (1.3)偏好设置(面试考点) 445 | (1.4)归档(面试考点) 446 | (2)数据库技术(SQLite&CoreData) 447 | 448 | 162、Objective-C堆和栈的区别? 449 | 450 | 163、内存泄露 & 内存溢出 451 | 452 | 164、堆 & 栈 453 | (1)堆栈空间分配区别 454 | (2)堆栈缓存方式区别 455 | (3)堆栈数据结构区别 456 | 457 | 165、内存管理 458 | (1)内存区域 459 | (1.1)堆和栈的区别 460 | (1.2)iOS内存区域 461 | (2)字符串的内存管理 462 | (3)你是如何优化内存管理 463 | (4)循环引用 464 | (5)autorelease的使用 465 | (5.1)工厂方法为什么不释放对象 466 | (5.2)ARC下autorelease的使用场景 467 | (5.3)自动释放池如何工作 468 | (5.4)避免内存峰值 469 | (5.5)ARC和MRC的混用 470 | (5.6)NSTimer的内存管理 471 | (5.7)ARC的实现原理 472 | 473 | 166、Runloop 474 | 475 | 167、fmmpeg框架 476 | 477 | 168、fmdb框架 478 | 479 | 169、320框架 480 | 481 | 170、UIKit和CoreAnimation和CoreGraphics的关系是什么?在开发中是否使用过CoreAnimation和CoreGraphics? 482 | 483 | 171、trasform 484 | 485 | 172、点讲动画和layer ,view的区别 486 | 487 | 173、图层与视图 488 | 489 | 174、平行的层级关系 490 | 491 | 175、图层的能力 492 | 493 | 176、使用图层 494 | 495 | 177、核心绘图 496 | (1)View和layer的区别 497 | (2)new和alloc init的区别 498 | 499 | 178、动画 500 | 501 | 179、UICollectionView 502 | (1)何实现瀑布流,流水布局 503 | (2)和UITableView的使用区别 504 | 505 | 180、UIImage 506 | 507 | 181、webview 508 | 509 | 182、描述九宫格算法 510 | 511 | 183、实现图片轮播图 512 | 513 | 184、iOS网络框架 514 | 515 | 185、网络 516 | (1)网络基础 517 | (2)网络传输 518 | (3)AFN 519 | 520 | 186、AFNetworking & ASIHttpRequest & MKNetWorking 521 | (1)底层实现 522 | (2)对服务器返回的数据处理 523 | (3)监听请求过程 524 | (4)在文件下载和文件上传的使用难易度 525 | (5)网络监控 526 | (6)ASI提供的其他实用功能 527 | (7)MKNetworkKit 528 | 529 | 187、性能优化 530 | 531 | 188、算法 532 | # 推荐👇: 533 | 534 | 535 | 如果你想一起进阶,想高薪不妨添加一下交流群[642363427](https://jq.qq.com/?_wv=1027&k=OLiG2Ids) 536 | 537 | -------------------------------------------------------------------------------- /iOS面试合集/iOS多线程面试题分析.md: -------------------------------------------------------------------------------- 1 | ## 一、多线程的选择方案 2 | | 技术方案 | 简介 | 语言 | 线程生命周期 | 使用评率 | 3 | | --- | --- | --- | --- | --- | 4 | | pthread | 一套通用的多线程API适用于Unix/Linux/Windows等系统跨平台/可移植使用难度大 | C |程序员管理 | 几乎不用 5 | | NSThread | 使用更加面向对象简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 | 6 | | GCD | 旨在替代NSThread等线程技术充分利用设备的多核 | C | 自动管理 | 经常使用 | 7 | | NSOperation | 基于GCD(底层是GCD)比GCD多了一些更简单实用的功能使用更加面向对象 | OC | 自动管理 | 经常使用 | 8 | 9 | 注意:如果使用NSThread的`performSelector:withObject:afterDelay:`时需要添加到当前线程的`runloop`中,因为在内部会创建一个`NSTimer` 10 | 11 | ## 二、GCD和NSOperation的比较 12 | 13 | * `GCD`和`NSOperation`的关系如下: 14 | 15 | * `GCD`是面向底层的C语言的API 16 | * `NSOperation`是用`GCD`封装构建的,是`GCD`的高级抽象 17 | * `GCD`和`NSOperation`的对比如下: 18 | 19 | 1. `GCD`执行效率更高,而且由于队列中执行的是由`block`构成的任务,这是一个轻量级的数据结构——写起来更加方便 20 | 2. `GCD`只支持`FIFO`的队列,而`NSOpration`可以设置最大并发数、设置优先级、添加依赖关系等调整执行顺序 21 | 3. `NSOpration`甚至可以跨队列设置依赖关系,但是`GCD`只能通过设置串行队列,或者在队列内添加`barrier`任务才能控制执行顺序,较为复杂 22 | 4. `NSOperation`支持`KVO`(面向对象)可以检测operation是否正在执行、是否结束、是否取消 23 | 24 | > * 实际项目中,很多时候只会用到异步操作,不会有特别复杂的线程关系管理,所以苹果推崇的是优化完善、运行快速的GCD 25 | > * 如果考虑异步操作之间的事务性、顺序性、依赖关系,比如多线程并发下载,GCD需要写更多的代码来实现,而NSOperation已经内建了这些支持 26 | > * 不管是GCD还是NSOperation,我们接触的都是任务和队列,都没有直接接触到线程,事实上线程管理也的确不需要我们操心,系统对于线程的创建、调度管理和释放都做得很好;而NSThread需要我们自己去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销 27 | 28 | ## 三、多线程的应用场景 29 | 30 | * 异步执行 31 | * 将耗时操作放在子线程中,使其不阻塞主线程 32 | * 刷新UI 33 | * 异步网络请求,请求完毕`dispatch_get_main_queue()`回到主线程刷新UI 34 | * 同一页面多个网络请求使用`dispatch_group`统一调度刷新UI 35 | * `dispatch_once` 36 | * 在`单例`中使用,一个类仅有一个实例且提供一个全局访问点 37 | * 在`method-Swizzling`使用保证方法只交换一次 38 | * `dispatch_after`将任务延迟加入队列 39 | * `栅栏函数`可用作同步锁 40 | * `dispatch_semaphore_t` 41 | * 用作锁保证线程安全 42 | * 控制`GCD`的最大并发数 43 | * `dispatch_source定时器`替代误差较大的`NSTimer` 44 | * `AFNetworking`、`SDWebImage`等知名三方库中的`NSOperation`使用 45 | * ... 46 | 47 | ## 四、线程池的原理 48 | 49 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/141134_55e30d88_9027123.png "多线程分享1.png") 50 | 51 | 52 | 53 | * 若`线程池大小`小于`核心线程池大小`时 54 | * 创建线程执行任务 55 | * 若`线程池大小`大于等于`核心线程池大小`时 56 | 1. 先判断线程池工作队列是否已满 57 | 2. 若没满就将任务push进队列 58 | 3. 若已满时,且`maximumPoolSize>corePoolSize`,将创建新的线程来执行任务 59 | 4. 反之则交给`饱和策略`去处理 60 | 61 | | 参数名 | 代表意义 | 62 | | --- | --- | 63 | | corePoolSize | 线程池的基本大小(核心线程池大小) | 64 | | maximumPool | 线程池的最大大小 | 65 | | keepAliveTime | 线程池中超过corePoolSize树木的空闲线程的最大存活时间 | 66 | | unit | keepAliveTime参数的时间单位 | 67 | | workQueue | 任务阻塞队列 | 68 | | threadFactory | 新建线程的工厂 | 69 | | handler | 当提交的任务数超过maxmumPoolSize与workQueue之和时,任务会交给RejectedExecutionHandler来处理 | 70 | 71 | 饱和策略有如下四个: 72 | 73 | * `AbortPolicy`直接抛出RejectedExecutionExeception异常来阻止系统正常运行 74 | * `CallerRunsPolicy`将任务回退到调用者 75 | * `DisOldestPolicy`丢掉等待最久的任务 76 | * `DisCardPolicy`直接丢弃任务 77 | 78 | ## 五、栅栏函数异同以及注意点 79 | 80 | 栅栏函数两个API的**异同**: 81 | 82 | * `dispatch_barrier_async`:可以控制队列中任务的执行顺序 83 | * `dispatch_barrier_sync`:不仅阻塞了队列的执行,也阻塞了线程的执行 84 | 85 | 栅栏函数**注意点**: 86 | 87 | 1. 尽量使用自定义的并发队列: 88 | * 使用`全局队列`起不到栅栏函数的作用 89 | * 使用`全局队列`时由于对全局队列造成堵塞,可能致使系统其他调用全局队列的地方也堵塞从而导致崩溃(并不是只有你在使用这个队列) 90 | 2. 栅栏函数只能控制同一并发队列:打个比方,平时在使用`AFNetworking`做网络请求时为什么不能用栅栏函数起到同步锁堵塞的效果,因为`AFNetworking`内部有自己的队列 91 | 92 | 93 | 94 | 95 | ## 六、栅栏函数的读写锁 96 | 97 | 多读单写功能指的是:可以多个读者同时读取数据,而在读的时候,不能写入数据;在写的过程中不能有其他写者去写。即读者之间是并发的,写者与其他写者、读者之间是互斥的 98 | 99 | ``` 100 | - (id)readDataForKey:(NSString*)key { 101 | __block id result; 102 | dispatch_sync(_concurrentQueue, ^{ 103 | result = [self valueForKey:key]; 104 | }); 105 | return result; 106 | } 107 | 108 | - (void)writeData:(id)data forKey:(NSString*)key { 109 | dispatch_barrier_async(_concurrentQueue, ^{ 110 | [self setValue:data forKey:key]; 111 | }); 112 | } 113 | ``` 114 | 115 | * 读:`并发同步`获取到值后返回给读者 116 | * 若使用`并发异步`则会先返回空的`result 0x0`,再通过getter方法获取到值 117 | * 写:写的那个时间段,不能有任何读者+其他写者 118 | * `dispatch_barrier_async`满足:等队列中前面的读写任务都执行完了再来执行当前任务 119 | 120 | ## 七、GCD的并发量 121 | 122 | 不同于`NSOperation`中可以通过`maxConcurrentOperationCount`去控制并发数,GCD需要通过信号量才能达到效果 123 | 124 | ``` 125 | dispatch_semaphore_t sem = dispatch_semaphore_create(1); 126 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT); 127 | 128 | for (int i = 0; i < 10; i++) { 129 | dispatch_async(queue, ^{ 130 | NSLog(@"当前%d----线程%@", i, [NSThread currentThread]); 131 | // 打印任务结束后信号量解锁 132 | dispatch_semaphore_signal(sem); 133 | }); 134 | // 由于异步执行,打印任务会较慢,所以这里信号量加锁 135 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 136 | } 137 | 138 | --------------------输出结果:------------------- 139 | 当前1----线程{number = 3, name = (null)} 140 | 当前0----线程{number = 6, name = (null)} 141 | 当前2----线程{number = 3, name = (null)} 142 | 当前3----线程{number = 6, name = (null)} 143 | 当前4----线程{number = 6, name = (null)} 144 | 当前5----线程{number = 3, name = (null)} 145 | 当前6----线程{number = 3, name = (null)} 146 | 当前7----线程{number = 6, name = (null)} 147 | 当前8----线程{number = 3, name = (null)} 148 | 当前9----线程{number = 6, name = (null)} 149 | --------------------输出结果:------------------- 150 | ``` 151 | 152 | > 在面试中更多会考验开发人员对于指定场景的多线程知识,接下来就来看看一些综合运用 153 | 154 | ## 八、综合运用一 155 | 156 | #### 1.下列代码会报错吗? 157 | 158 | ``` 159 | int a = 0; 160 | while (a < 5) { 161 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 162 | a++; 163 | }); 164 | } 165 | ``` 166 | 167 | * 编译会报错`Variable is not assignable (missing __block type specifier)` 168 | * 这块属于block的知识 169 | * 捕获外界变量并进行修改需要加`__block int a = 0;` 170 | * 这块内容在接下来的`block`会讲到 171 | 172 | #### 2.下列代码的输出 173 | 174 | ``` 175 | __block int a = 0; 176 | while (a < 5) { 177 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 178 | a++; 179 | }); 180 | } 181 | NSLog(@"%d", a); 182 | ``` 183 | 184 | * 会输出`0`吗? 185 | * 不会,尽管是并发异步执行,但是有`while`在,不满足条件就不会跳出循环 186 | * 会输出`1~4`吗? 187 | * 不会(原因请往下看) 188 | * 会输出`5`吗? 189 | * 有可能(原因请往下看) 190 | * 会输出`6~∞`吗? 191 | * 极有可能 192 | 193 | 分析: 194 | 195 | * 刚进入while循环时,`a=0`,然后进行`a++` 196 | * 由于是`异步并发`会开辟子线程并有可能超车完成 197 | * 当`线程2`在`a=0`执行`a++`时,`线程3`有可能已经完成了`a++`使`a=1` 198 | * 由于是操作同一片内存空间,`线程3`修改了`a`导致`线程2`中`a`的值也发生了变化 199 | * 慢一拍的`线程2`对已经是`a=1`进行`a++`操作 200 | * 同理还有`线程4`、`线程5`、`线程n`的存在 201 | * 可以这么理解,线程2、3、4、5、6同时在`a=0`时操作`a` 202 | * 线程2、3、4、5按顺序完成了操作,此时`a=4` 203 | * 然后`线程6`开始操作了,但是它还没执行完就跳到了下一次循环了开辟了`线程7`开始`a++` 204 | * 当`线程6`执行结束修改`a=5`之后来到`while条件判断`就会跳出循环 205 | * 然而`I/O`输出比较耗时,此时线程7又刚好完成了再打印,就会输出`大于5` 206 | * 也有那么种理想情况,`异步并发`都比较听话,刚好在`a=5`时没有子线程 207 | * 此时就会输出`5` 208 | 209 | > 如果还没有明白可以在while循环中添加打印代码 210 | 211 | ``` 212 | __block int a = 0; 213 | while (a < 5) { 214 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 215 | NSLog(@"%d————%@", a, [NSThread currentThread]); 216 | a++; 217 | }); 218 | } 219 | NSLog(@"此时的%d", a); 220 | ``` 221 | 222 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/141148_6034fd03_9027123.png "多线程分享2.png") 223 | 224 | 225 | > 打印信息证明while外面的打印已经执行,但是子线程还是有可能在对a进行操作的 226 | 227 | #### 3.怎么解决线程不安全? 228 | 229 | 可能有的小伙伴说这种需求不存在,但是我们只管解决便是了 230 | 231 | 此时我们应该能想到一下几种解决方案: 232 | 233 | * 同步函数替换异步函数 234 | * 使用栅栏函数 235 | * 使用信号量 236 | 237 | 1. 同步函数替换异步函数 238 | 239 | * 结果:能满足需求 240 | * 效果:不是很好——能使用异步函数去使唤子线程为什么不用呢(虽然会消耗内存,但是效率高) 241 | 242 | 2. 使用栅栏函数 243 | 244 | * 结果:能满足需求 245 | * 效果:一般 246 | * 首先`栅栏函数`和`全局队列`搭配使用会无效,需要更换队列类型; 247 | * 其次`dispatch_barrier_sync`会阻塞线程,影响性能 248 | * 而`dispatch_barrier_async`不能满足需求,它只能控制前面的任务执行完毕再执行栅栏任务(控制任务执行)可是异步栅栏执行也是在子线程中,当`a=4`时会先继续下一次循环添加任务到队列中,再来异步执行栅栏任务(不能控制任务的添加) 249 | 250 | ``` 251 | __block int a = 0; 252 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT); 253 | while (a < 5) { 254 | dispatch_async(queue, ^{ 255 | a++; 256 | }); 257 | dispatch_barrier_async(queue, ^{}); 258 | } 259 | 260 | NSLog(@"此时的%d", a); 261 | sleep(1); 262 | NSLog(@"此时的%d", a); 263 | 264 | --------------------输出结果:------------------- 265 | 此时的5 266 | 此时的17 267 | --------------------输出结果:------------------- 268 | ``` 269 | 270 | 3. 使用信号量 271 | 272 | * 结果:能满足需求 273 | * 效果:很好、简洁效率高 274 | 275 | ``` 276 | __block int a = 0; 277 | dispatch_semaphore_t sem = dispatch_semaphore_create(0); 278 | while (a < 5) { 279 | dispatch_async(dispatch_get_global_queue(0, 0), ^{ 280 | a++; 281 | dispatch_semaphore_signal(sem); 282 | }); 283 | dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); 284 | } 285 | 286 | NSLog(@"此时的%d", a); 287 | sleep(1); 288 | NSLog(@"此时的%d", a); 289 | 290 | --------------------输出结果:------------------- 291 | 此时的5 292 | 此时的5 293 | --------------------输出结果:------------------- 294 | ``` 295 | 296 | ## 九、综合运用二 297 | 298 | #### 1.输出内容 299 | 300 | ``` 301 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT); 302 | NSMutableArray *marr = @[].mutableCopy; 303 | for (int i = 0; i < 1000; i++) { 304 | dispatch_async(queue, ^{ 305 | [marr addObject:@(i)]; 306 | }); 307 | } 308 | NSLog(@"%lu", marr.count); 309 | ``` 310 | 311 | > * 你:输出一个小于1000的数,因为for循环中是异步操作 312 | > * 面试官:回去等消息吧 313 | > * 然后你回去之后试了下大吃一惊——程序崩了 314 | 315 | 这是为什么呢? 316 | 317 | 其实跟`综合运用一`是一样的道理——for循环异步时无数条线程访问数组,造成了线程不安全 318 | 319 | #### 2.怎么解决线程不安全? 320 | 321 | * 使用串行队列 322 | 323 | ``` 324 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_SERIAL); 325 | NSMutableArray *marr = @[].mutableCopy; 326 | for (int i = 0; i < 1000; i++) { 327 | dispatch_async(queue, ^{ 328 | [marr addObject:@(i)]; 329 | }); 330 | } 331 | NSLog(@"%lu", marr.count); 332 | 333 | --------------------输出结果:------------------- 334 | 998 335 | --------------------输出结果:------------------- 336 | ``` 337 | 338 | * 使用互斥锁 339 | 340 | ``` 341 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT); 342 | NSMutableArray *marr = @[].mutableCopy; 343 | for (int i = 0; i < 1000; i++) { 344 | dispatch_async(queue, ^{ 345 | @synchronized (self) { 346 | [marr addObject:@(i)]; 347 | } 348 | }); 349 | } 350 | NSLog(@"%lu", marr.count); 351 | 352 | --------------------输出结果:------------------- 353 | 997 354 | --------------------输出结果:------------------- 355 | ``` 356 | 357 | * 使用栅栏函数 358 | 359 | ``` 360 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT); 361 | NSMutableArray *marr = @[].mutableCopy; 362 | for (int i = 0; i < 1000; i++) { 363 | dispatch_async(queue, ^{ 364 | [marr addObject:@(i)]; 365 | }); 366 | dispatch_barrier_async(queue, ^{}); 367 | } 368 | NSLog(@"%lu", marr.count); 369 | ``` 370 | 371 | #### 3.分析思路 372 | 373 | 单路千万条,跳跳通罗马——当然除了这三种还有其他办法 374 | 375 | * 使用串行队列 376 | * 虽然效率低,但总归能解决线程安全问题 377 | * 虽然`串行异步`是任务一个接一个执行,但那是队列中的任务才满足执行规律 378 | * 要想得到打印结果`1000`,可以在队列中执行 379 | * 总的来说,能满足需求但不是很有效 380 | * 使用互斥锁 381 | * `@synchronized`是个好东西,简单易用还有效,但也没有满足我们的需求 382 | * 在for循环外使用队列内同步/异步都不能得到`100` 383 | * 要么先sleep一秒——这样不可控的代码是不可取的的 384 | * 且在iOS的锁家族中`@synchronized`效率很低 385 | * 使用栅栏函数 386 | * 栅栏函数可以有效的控制任务的执行 387 | * 且与`综合运用一`不同,本题中是for循环 388 | * 至于怎么得到打印结果`1000`,只需要在同一队列中打印即可(栅栏函数的注意点) 389 | 390 | ``` 391 | dispatch_queue_t queue = dispatch_queue_create("Felix", DISPATCH_QUEUE_CONCURRENT); 392 | NSMutableArray *marr = @[].mutableCopy; 393 | for (int i = 0; i < 1000; i++) { 394 | dispatch_async(queue, ^{ 395 | [marr addObject:@(i)]; 396 | }); 397 | dispatch_barrier_async(queue, ^{}); 398 | } 399 | dispatch_async(queue, ^{ 400 | NSLog(@"%lu", marr.count); 401 | }); 402 | ``` 403 | 404 | ## 写在后面 405 | 406 | 多线程在日常开发中占有不少份量,同时面试中也是必问模块。但只有基础知识是一成不变的,综合运用题稍有改动就是另外一种类型的知识考量了,而且也有多种解决方案 407 | 408 | -------------------------------------------------------------------------------- /iOS面试合集/iOS底层原理之部分面试题分析.md: -------------------------------------------------------------------------------- 1 | ## Runtime Asssociate方法关联的对象,是否需要在dealloc中释放? 2 | 3 | `不需要释放` 4 | 5 | ## 分析 6 | 7 | 我们知道当一个对象销毁的时候会调用 `dealloc` 方法,那么我们先看下 `dealloc` 都进行了哪些操作。 8 | 9 | * `dealloc` 函数调用了 `_objc_rootDealloc` 函数 10 | 11 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145101_8eccb96d_9027123.png "底层1.png") 12 | 13 | * `_objc_rootDealloc` 函数调用 `rootDealloc` 函数 14 | 15 | ``` 16 | void 17 | _objc_rootDealloc(id obj) 18 | { 19 | ASSERT(obj); 20 | 21 | obj->rootDealloc(); 22 | } 23 | ``` 24 | 25 | * `rootDealloc` 函数查看 26 | 27 | ``` 28 | inline void 29 | objc_object::rootDealloc() 30 | { 31 | if (isTaggedPointer()) return; // fixme necessary? 32 | 33 | if (fastpath(isa.nonpointer && 34 | !isa.weakly_referenced && 35 | !isa.has_assoc && 36 | !isa.has_cxx_dtor && 37 | !isa.has_sidetable_rc)) 38 | { 39 | assert(!sidetable_present()); 40 | free(this); 41 | } 42 | else { 43 | object_dispose((id)this); 44 | } 45 | } 46 | ``` 47 | 48 | 从 `rootDealloc` 函数中我们看到了判断isa相关属性的地方,实际上当一个对象存在会进入 `else` 中,即 `object_dispose` 函数 49 | 50 | * `object_dispose` 函数查看 51 | 52 | ``` 53 | id 54 | object_dispose(id obj) 55 | { 56 | if (!obj) return nil; 57 | 58 | objc_destructInstance(obj); 59 | free(obj); 60 | 61 | return nil; 62 | } 63 | ``` 64 | 65 | 通过 `objc_destructInstance` 函数找到对象,然后 `free` ,我们看 `objc_destructInstance` 函数 66 | 67 | * `objc_destructInstance` 函数 68 | 69 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145117_2adf73e7_9027123.png "底层2.png") 70 | 71 | 重点查看 `_object_remove_assocations` 函数 72 | 73 | * `_object_remove_assocations` 函数分析 74 | 75 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145130_b627f11e_9027123.png "底层3.png") 76 | 77 | ## 类、分类方法同名时调用顺序是怎样的? 78 | 79 | 当 `非+load` 方法同名时,分类的方法在类的方法前面( `注意不是覆盖` ),因为 `分类的方法是在类realize之后 attach进去的` ,所以 `优先分类,其次类` 80 | 81 | ## 当 `+load` 方法同名时, `优先类,其次分类` 82 | 83 | ## 分类与类的扩展 84 | 85 | ## 分类 86 | 87 | * 专门用来给类添加新的方法 88 | * 不能添加属性,但是可以通过runtime动态添加属性(因为我们在前面的篇章中分析过,分类底层代码中有属性列表) 89 | * 分类中 `@property` 定义的变量只会生成 `setter` 以及 `getter` 方法的声明,但是 `不会生成对应的方法实现以及带有下划线的成员变量` 90 | 91 | 92 | 93 | ## 类的扩展 94 | 95 | ``` 96 | @property 97 | ``` 98 | 99 | ## 什么是Runtime? 100 | 101 | runtime是由C和C++汇编实现的一套API,为OC语言添加了面向对象和运行时功能。 102 | 103 | * 运行时:将数据类型的确定由编译阶段推迟到了运行阶段。我们平时所写的OC代码,最终转换为runtime的C语言代码。 104 | 105 | ## 方法的本质是什么?SEL、IMP是什么?两者之间的关系是什么? 106 | 107 | ## 方法的本质 108 | 109 | 方法的本质是 `消息的发送` ,涉及到消息发送的流程有 110 | 111 | ``` 112 | objc_msgSend 113 | lookUpImpOrForward 114 | resolveInstanceMethod 115 | forwardingTargetForSelector 116 | mesthodSignatureForSelector & forwardInvocation 117 | ``` 118 | 119 | ## SEL、IMP 120 | 121 | * sel:方法编号,类比一本书的目录 122 | * imp:方法函数指针地址,类比一本书的页数 123 | * sel与imp关系:sel是方法编号,通过sel找到imp的函数指针地址,通过imp就能找到函数的实现 124 | 125 | ## 能否向编译后的类中添加实例变量?能否向运行时创建的类添加实例变量? 126 | 127 | ``` 128 | 编译后实例变量存储到 ro 中,一旦编译完成,内存结构就完全确定了,无法再次修改 129 | ``` 130 | 131 | ## [self class] 与 [super class]的区别 132 | 133 | 我们先看以下如下代码打印结果,其中self是LGTeacher类,LGTeacher继承于LGPerson,LGPerson继承于NSObject[图片上传失败...(image-e06edd-1604300283675)] 134 | 135 | 从打印结果中我们看到无论是 `[self class]` 还是 `[super class]` 的结果是一样的,为什么呢? 136 | 137 | ## 分析 138 | 139 | * 我们知道任何方法调用都会隐藏两个参数,即 `(id self , sel _cmd)` ,其中 `self` 是消息接收者。对于 `[self class]` 来说,它的消息接收者是 `自身LGTeacher` 没什么可说的,所以打印的是 `LGTeacher` 。 140 | * 首先我们要知道 `super` 只是关键字,它意思是说从父类调用方法,因此 `[super class]` 就是 `直接调用的就是父类的class` 方法,它的本质是 `objc_msgSendSuper` ,只是 `objc_msgSendSuper` 速度更快,直接跳过 `self` 。但需要注意的是, `[super class]` 的消息接受者依然是 `LGTeacher` ,所以最终打印的是 `LGTeacher` 。 141 | 142 | ## 内存偏移相关问题 143 | 144 | 我们先准备代码,定义 `IFPerson` 类,代码如下 145 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145148_4c3ed9ca_9027123.png "底层4.png") 146 | 147 | 148 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145202_042c61b1_9027123.png "底层5.png") 149 | 150 | 我们再看ViewController代码 151 | 152 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145509_6cddf742_9027123.png "底层6.png") 153 | 154 | 155 | 156 | 157 | ## `[(__bridge id)kc doSomething]` 为什么不会崩溃? 158 | 159 | 首先我们知道对于一个对象,它的指针地址指向的是 `isa` ,同时 `isa` 地址指向 `当前的class` ,所以 `kc` 指向的是 `IFPerson` 的 `isa` ,而 `person` 的指针指向的 `也是isa` ,这样它们都是 `isa` 从 `cache_t` 中查找 `doSomething` 方法,因此不会崩溃。 160 | 161 | ## 为什么 `[(__bridge id)kc doSomething]` 打印的结果是 `ViewController` ? 162 | 163 | * 从打印结果中 `[person doSomething]` 打印出出来 `shifx` 是没有什么问题的,毕竟给person.name赋值 `shifx` ,但是 `[(__bridge id)kc doSomething]` 打印的结果是 `ViewController` 呢?要解决这个问题首先我们需要知道 `person` 能够找到 `name` 是 `指针从isa内存平移了8个字节` 移动到了 `name` 。那么对于 `kc` 来说,它也需要指针平移,但是为什么平移后的结果是 `viewController` 呢?这就需要明白 `栈地址是从高到低存储的,且是先进后出` ,由于前面先调用了 `[super viewDidLoad]` 方法,且 `viewDidLoad` 的隐藏参数是 `(id self, IMP _cmd)` ,所以 `self` 会先入栈,其次是 `cls` -> `kc` -> `person` ,出栈的顺序刚好相反,由于 `[(__bridge id)kc doSomething]` 时需要指针平移,自然指向了 `self(即ViewController)` ,所以打印的结果是 `ViewController` 。 164 | * 为了验证我们上面分析是否正确,我们修改代码位置,将声明 `IFPerson *person = [[IFPerson alloc] init]` 放在 `[super viewDidLoad]` 之后,即 165 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/145535_a4e88dca_9027123.png "底层7.png") 166 | 167 | 此时我们按照我们上面的分析 `self` 会先入栈,其次是 `person` -> `cls` -> `kc` ,猜测 `[(__bridge id)kc doSomething]` 打印结果应该是 `IFPerson (person的isa指向其Class)` , 168 | 169 | 可以看出我们的分析是正确的。 170 | -------------------------------------------------------------------------------- /iOS面试合集/iOS经典面试题.md: -------------------------------------------------------------------------------- 1 | ## iOS面试知识点 2 | 3 | 本篇的面试题是我认为比较好的iOS开发基础知识点,希望大家看过这后在理解的基础上掌握而不是死记硬背。死记硬背很快也会忘记的。 4 | 5 | ### 1 iOS基础 6 | 7 | #### 1.1 父类实现深拷贝时,子类如何实现深度拷贝。父类没有实现深拷贝时,子类如何实现深度拷贝。 8 | 9 | * 深拷贝同浅拷贝的区别:浅拷贝是指针拷贝,对一个对象进行浅拷贝,相当于对指向对象的指针进行复制,产生一个新的指向这个对象的指针,那么就是有两个指针指向同一个对象,这个对象销毁后两个指针都应该置空。深拷贝是对一个对象进行拷贝,相当于对对象进行复制,产生一个新的对象,那么就有两个指针分别指向两个对象。当一个对象改变或者被销毁后拷贝出来的新的对象不受影响。 10 | 11 | * 实现深拷贝需要实现NSCoying协议,实现- (id)copyWithZone:(NSZone *)zone 方法。当对一个property属性含有copy修饰符的时候,在进行赋值操作的时候实际上就是调用这个方法。 12 | 13 | * 父类实现深拷贝之后,子类只要重写copyWithZone方法,在方法内部调用父类的copyWithZone方法,之后实现自己的属性的处理 14 | 15 | * 父类没有实现深拷贝,子类除了需要对自己的属性进行处理,还要对父类的属性进行处理。 16 | 17 | #### 1.2 KVO,NSNotification,delegate及block区别 18 | 19 | * KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。 20 | 21 | * NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。 22 | 23 | * delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。 24 | 25 | * block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。 26 | 27 | * KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。 28 | Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。 29 | 30 | #### 1.3 KVC如果实现,如何进行键值查找。KVO如何实现 31 | 32 | 请看视频详解:[RAC响应式编程结合KVO](https://www.bilibili.com/video/BV1Ya411F7Yx) 33 | 34 | #### 1.4 将一个函数在主线程执行的4种方法 35 | 36 | * GCD方法,通过向主线程队列发送一个block块,使block里的方法可以在主线程中执行。 37 | 38 | ``` 39 | dispatch_async(dispatch_get_main_queue(), ^{ 40 | //需要执行的方法 41 | }); 42 | 43 | ``` 44 | 45 | * NSOperation 方法 46 | 47 | ``` 48 | NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; //主队列 49 | NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 50 | //需要执行的方法 51 | }]; 52 | [mainQueue addOperation:operation]; 53 | 54 | ``` 55 | 56 | * NSThread 方法 57 | 58 | ``` 59 | [self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil]; 60 | 61 | [self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES]; 62 | 63 | [[NSThread mainThread] performSelector:@selector(method) withObject:nil]; 64 | 65 | ``` 66 | 67 | * RunLoop方法 68 | 69 | ``` 70 | [[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil]; 71 | 72 | ``` 73 | 74 | #### 1.5 如何让计时器调用一个类方法 75 | 76 | * 计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。 77 | * 使用计时器需要注意,计时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个计时器并加入到RunLoop中所以可以直接使用。 78 | * 如果计时器的repeats选择YES说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。 79 | 80 | ``` 81 | [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES]; 82 | 83 | -(void)timerMethod 84 | { 85 | //调用类方法 86 | [[self class] staticMethod]; 87 | } 88 | 89 | -(void)invalid 90 | { 91 | [timer invalid]; 92 | timer = nil; 93 | } 94 | 95 | ``` 96 | 97 | #### 1.6 如何重写类方法 98 | 99 | * 1、在子类中实现一个同基类名字一样的静态方法 100 | * 2、在调用的时候不要使用类名调用,而是使用[self class]的方式调用。原理,用类名调用是早绑定,在编译期绑定,用[self class]是晚绑定,在运行时决定调用哪个方法。 101 | 102 | #### 1.7 NSTimer创建后,会在哪个线程运行。 103 | 104 | * 用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程 105 | * 自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。 106 | 107 | #### 1.8 id和NSObject*的区别 108 | 109 | * id是一个 objc_object 结构体指针,定义是 110 | 111 | ``` 112 | typedef struct objc_object *id 113 | 114 | ``` 115 | 116 | * id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有这个方法,该崩溃还是会崩溃的。 117 | 118 | * NSObject *指向的必须是NSObject的子类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。 119 | 120 | * 不是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject *可指向的类型是id的子集。 121 | ## [iOS高级开发交流学习群,点击此处立即加入](https://jq.qq.com/?_wv=1027&k=ugTWIjyd) 122 | 123 | ### **麻烦在文末 “点个赞”如果我的理解有错漏请一定指出,非常感谢!** 124 | -------------------------------------------------------------------------------- /iOS面试合集/iOS面试十大要点.md: -------------------------------------------------------------------------------- 1 | 1\. 你使用过Objective-C的运行时编程(Runtime Programming)么?如果使用过,你用它做了什么?你还能记得你所使用的相关的头文件或者某些方法的名称吗? 2 | 3 | Objecitve-C的重要特性是Runtime(运行时),在#import 下能看到相关的方法,用过objc_getClass()和class_copyMethodList()获取过私有API;使用 4 | 5 | ``` 6 | objective-cMethod method1 = class_getInstanceMethod(cls, sel1);Method method2 = class_getInstanceMethod(cls, sel2);method_exchangeImplementations(method1, method2); 7 | 8 | ``` 9 | 10 | 代码交换两个方法,在写unit test时使用到。 11 | 12 | 2\. 你实现过多线程的Core Data么?NSPersistentStoreCoordinator,NSManagedObjectContext和NSManagedObject中的哪些需要在线程中创建或者传递?你是用什么样的策略来实现的? 13 | 14 | 3\. Core开头的系列的内容。是否使用过CoreAnimation和CoreGraphics。UI框架和CA,CG框架的联系是什么?分别用CA和CG做过些什么动画或者图像上的内容。(有需要的话还可以涉及Quartz的一些内容) 15 | 16 | UI框架的底层有CoreAnimation,CoreAnimation的底层有CoreGraphics。 17 | 18 | UIKit | 19 | ------------ | 20 | Core Animation | 21 | Core Graphics | 22 | Graphics Hardware| 23 | 24 | 4\. 是否使用过CoreText或者CoreImage等?如果使用过,请谈谈你使用CoreText或者CoreImage的体验。 25 | 26 | CoreText可以解决复杂文字内容排版问题。CoreImage可以处理图片,为其添加各种效果。体验是很强大,挺复杂的。 27 | 28 | 5\. NSNotification和KVO的区别和用法是什么?什么时候应该使用通知,什么时候应该使用KVO,它们的实现上有什么区别吗?如果用protocol和delegate(或者delegate的Array)来实现类似的功能可能吗?如果可能,会有什么潜在的问题?如果不能,为什么?(虽然protocol和delegate这种东西面试已经面烂了…) 29 | 30 | NSNotification是通知模式在iOS的实现,KVO的全称是键值观察(Key-value observing),其是基于KVC(key-value coding)的,KVC是一个通过属性名访问属性变量的机制。例如将Module层的变化,通知到多个Controller对象时,可以使用NSNotification;如果是只需要观察某个对象的某个属性,可以使用KVO。 31 | 对于委托模式,在设计模式中是对象适配器模式,其是delegate是指向某个对象的,这是一对一的关系,而在通知模式中,往往是一对多的关系。委托模式,从技术上可以现在改变delegate指向的对象,但不建议这样做,会让人迷惑,如果一个delegate对象不断改变,指向不同的对象。 32 | 33 | 6\. 你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。 34 | 35 | 使用NSOperationQueue用来管理子类化的NSOperation对象,控制其线程并发数目。GCD和NSOperation都可以实现对线程的管理,区别是 NSOperation和NSOperationQueue是多线程的面向对象抽象。项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。 36 | 项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。 37 | 38 | 7\. 既然提到GCD,那么问一下在使用GCD以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?如何避免循环引用? 39 | 40 | 使用block是要注意,若将block做函数参数时,需要把它放到最后,GCD是Grand Central Dispatch,是一个对线程开源类库,而Block是闭包,是能够读取其他函数内部变量的函数。 41 | 42 | 8\. 您是否做过异步的网络处理和通讯方面的工作?如果有,能具体介绍一些实现策略么? 43 | 44 | 使用NSOperation发送异步网络请求,使用NSOperationQueue管理线程数目及优先级,底层是用NSURLConnetion 45 | 46 | 9\. 对于Objective-C,你认为它最大的优点和最大的不足是什么?对于不足之处,现在有没有可用的方法绕过这些不足来实现需求。如果可以的话,你有没有考虑或者实践过重新实现OC的一些功能,如果有,具体会如何做? 47 | 48 | 最大的优点是它的运行时特性,不足是没有命名空间,对于命名冲突,可以使用长命名法或特殊前缀解决,如果是引入的第三方库之间的命名冲突,可以使用link命令及flag解决冲突。 49 | 50 | 10\. 你实现过一个框架或者库以供别人使用么?如果有,请谈一谈构建框架或者库时候的经验;如果没有,请设想和设计框架的public的API,并指出大概需要如何做、需要注意一些什么方面,来使别人容易地使用你的框架。 51 | 52 | 抽象和封装,方便使用。首先是对问题有充分的了解,比如构建一个文件解压压缩框架,从使用者的角度出发,只需关注发送给框架一个解压请求,框架完成复杂文件的解压操作,并且在适当的时候通知给是哦难过者,如解压完成、解压出错等。在框架内部去构建对象的关系,通过抽象让其更为健壮、便于更改。其次是API的说明文档。 53 | 54 | ## 推荐文章 55 | 56 | [关于 iOS 性能优化方面的面试题](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/%E5%85%B3%E4%BA%8E%20iOS%20%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E6%96%B9%E9%9D%A2%E7%9A%84%E9%9D%A2%E8%AF%95%E9%A2%98.md) 57 | 58 | [iOS求职之OC面试题](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E6%B1%82%E8%81%8C%E4%B9%8BOC%E9%9D%A2%E8%AF%95%E9%A2%98.md) 59 | 60 | 61 | -------------------------------------------------------------------------------- /iOS面试合集/iOS面试题文案及答案附件.md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/143906_836183bb_9027123.png "面试题文案1.png") 2 | 3 | 4 | ###1,分类和扩展有什么区别?可以分别用来做什么?分类有哪些局限性?分类的结构体里面有哪些成员? 5 | 6 | ①类别中原则上只能增加方法(能添加属性的的原因只是通过runtime能添加属性的的原因只是通过runtime的objc_setAssociatedObject和objc_getAssociatedObject方法解决无setter/getter的问题而已); 7 | ②类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的( 8 | 用范围只能在自身类,而不是子类或其他地方); 9 | ③类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。 10 | ④类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。 11 | ⑤定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。 12 | 13 | 最重要的还是类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。 14 | 分类方法未实现,编译器也不会报警告。 15 | 分类方法与原类中相同会优先调用分类。 16 | 17 | 分类的结构体 18 | ``` 19 | typedef struct objc_category *Category; 20 | struct objc_category { 21 | char *category_name OBJC2_UNAVAILABLE; // 分类名 22 | char *class_name OBJC2_UNAVAILABLE; // 分类所属的类名 23 | struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 实例方法列表 24 | struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 类方法列表 25 | struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分类所实现的协议列表 26 | } 27 | 28 | ``` 29 | ###2,讲一下atomic的实现机制;为什么不能保证绝对的线程安全(最好可以结合场景来说)? 30 | 31 | atomic是在setter和getter方法里会使用自旋锁spinlock_t来保证setter方法和getter方法的线程的安全。可以看做是getter方法获取到返回值之前不会执行setter方法里的赋值代码。如果不加atomic,可能在getter方法读取的过程中,再别的线成立发生setter操作,从而出现异常值。 32 | 33 | 加上atomic后,setter和getter方法是线程安全的,原子性的,但是出了getter方法和setter方法后就不能保证线程安全了 34 | ``` 35 | @property (atomic, strong) NSArray* arr; 36 | //thread A 37 | for (int i = 0; i < 10000; i ++) { 38 | if (i % 2 == 0) { 39 | self.arr = @[@"1", @"2", @"3"]; 40 | } 41 | else { 42 | self.arr = @[@"1"]; 43 | } 44 | } 45 | 46 | //thread B 47 | for (int i = 0; i < 100000; i ++) { 48 | if (self.arr.count >= 2) { 49 | NSString* str = [self.arr objectAtIndex:1]; 50 | } 51 | } 52 | 53 | ``` 54 | 上面的例子线程B里面可能会因为数组越界而引起crash,因为加入在B线程里判断self.arr.count >= 2的时候数组是self.arr = @[@“1”, @“2”, @“3”];但是当调用[self.arr objectAtIndex:1]可能self.arr的值已经在线程A里被更改为了@[@“1”],此时数组越界了。因此,虽然self.arr是atomic的,还是会出现线程安全问题。 55 | 56 | ###3,被weak修饰的对象在被释放的时候会发生什么?是如何实现的?知道sideTable么?里面的结构可以画出来么? 57 | 被weak修饰的对象在被释放时候会置为nil,不同于assign; 58 | 59 | Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。 60 | 61 | 1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。 62 | 2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。 63 | 3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。 64 | 65 | -------------------------------------------------------------------------------- /iOS面试合集/iOS面试题(十面埋伏).md: -------------------------------------------------------------------------------- 1 | #面试题一 2 | **runtime中,SEL、Method 和 IMP有什么区别,使用场景?** 3 | 4 | 它们之间的关系可以这么解释:一个类(Class)持有一个分发表,在运行期分发消息,表中的每一个实体代表一个方法(Method),它的名字叫做选择子(SEL),对应着一种方法实现(IMP)。 5 | 6 | ##**具体的分析如下** 7 | 8 | >**SEL定义**:typedef struct objc_selector *SEL,代表方法的名称。仅以名字来识别。翻译成中文叫做选择子或者选择器,选择子代表方法在 Runtime 期间的标识符。为 SEL 类型,虽然 SEL 是 objc_selector 结构体指针,但实际上它只是一个 C 字符串。在类加载的时候,编译器会生成与方法相对应的选择子,并注册到 Objective-C 的 Runtime 运行系统。不论两个类是否存在依存关系,只要他们拥有相同的方法名,那么他们的SEL都是相同的。比如,有n个viewcontroller页面,每个页面都有一个viewdidload,每个页面的载入,肯定都是不尽相同的。但是我们可以通过打印,观察发现,这些viewdidload的SEL都是同一个 9 | ``` 10 | SEL sel = @selector(methodName); 11 | // 方法名字 NSLog(@"address = %p",sel); 12 | // log输出为 address = 0x1df807e29 因此类方法定义时,尽量不要用相同的名字,就算是变量类型不同也不行。否则会引起重复,例如: 13 | 14 | -(void)setWidth:(int)width; -(void)setWidth:(double)width; 15 | ``` 16 | >**IMP定义**:typedef id (*IMP)(id, SEL, ...),代表函数指针,即函数执行的入口。该函数使用标准的 C 调用。第一个参数指向 self(它代表当前类实例的地址,如果是类则指向的是它的元类),作为消息的接受者;第二个参数代表方法的选择子;... 代表可选参数,前面的 id 代表返回值。 17 | 18 | >**Method定义**:typedef struct objc_method *Method,Method 对开发者来说是一种不透明的类型,被隐藏在我们平时书写的类或对象的方法背后。它是一个 objc_method 结构体指针,我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。 objc_method 的定义为: 19 | ``` 20 | /// Method 21 | struct objc_method { 22 | SEL method_name; 23 | char *method_types; 24 | IMP method_imp; 25 | }; 26 | ``` 27 | 方法名 method_name 类型为 SEL,前面提到过相同名字的方法即使在不同类中定义,它们的方法选择器也相同。 28 | 29 | 方法类型 method_types 是个 char 指针,其实存储着方法的参数类型和返回值类型,即是 Type Encoding 编码。 30 | 31 | method_imp 指向方法的实现,本质上是一个函数的指针,就是前面讲到的 Implementation。 32 | 33 | #面试题二 34 | **举例说明 Swift 中 map、filtter、reduce的作用?** 35 | 36 | **map: **方法作用是把数组[T]通过闭包函数把每一个数组中的元素变成U类型的值,最后组成数组[U]。定义如下: func map(transform: (T) -> U) -> [U] 37 | ``` 38 | // 将示例数组,每个数字都加10,获得一个新的数组: let numberArray = [1,2,3,4,5] var result = numberArray.map({$0 + 10}) print(result) 39 | // [11, 12, 13, 14, 15] 40 | // 在数字后拼接字符串,返回新的数组 let numberArray = [1,2,3,4,5] let resultArray = numberArray.map({"($0)只"}) print(resultArray) 41 | // 输出结果:["1只", "2只", "3只", "4只", "5只"] 42 | ``` 43 | >**拓展**:flatMap 更加强大,可以传入N个处理方法,将处理后得到数据,组合到同一个数组中 44 | ``` 45 | let numberArray = [1,2,3,4,5] 46 | resultArray = numberArray.flatMap({["\($0)个","\($0 )只"]}) 47 | print(resultArray) 48 | //输出结果: 49 | ["1个", "1只", "2个", "2只", "3个", "3只", "4个", "4只", "5个", "5只"] 50 | ``` 51 | filter就是筛选的功能,参数是一个用来判断是否筛除的筛选闭包,根据闭包函数返回的Bool值来过滤值。为True则加入到结果数组中。 52 | 定义如下: 53 | ``` 54 | func filter(includeElement: (T) -> Bool) -> [T] 55 | 56 | // 找出数组中大于2的数 let numberArray = [1,2,3,4,5] let filteredArray = numberArray.filter({$0 > 2}) print(filteredArray) // 输出结果: [3, 4, 5] 57 | 58 | reduce的作用给定一个类型为U的初始值,把数组[T]中每一个元素传入到combine的闭包函数里面,通过计算得到最终类型为U的结果值。定义如下: func reduce(initial: U, combine: (U, T) -> U) -> U 59 | 60 | // 求和 let numberArray = [1, 2, 3] let sum1 = numberArray.reduce(10) { (x, y) -> Int in print("x = (x) y = (y)") return x + y } let sum2 = numberArray.reduce(10) { $0 + $1 } 61 | 62 | print("sum=(sum1)") // 16 print("sum1=(sum2)") // 16 63 | ``` 64 | #面试题三 65 | **UIView与CALayer之间的关系是怎样的?** 66 | UIView是专门处理事件传递与视图响应的,而CALayer 是负责UI视图的显示工作的,二者的关系用到了6大设计原则中的 单一职责原则 ,也就是二者区分工作的原因:`单一职责原则` 67 | 68 | #面试题四 69 | **常见的内存泄漏有哪些情况?如何排查和避免?** 70 | **内存泄漏原理**:在百度上的解释就是“程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果”。 71 | 72 | ##常见的内存泄漏情况: 73 | 74 | `情况一`:**对象之间的循环引用问题** 循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收。解决办法:使用 weak 打破对象之间的相互强引用 75 | `情况二`:**block的循环引用** block在copy时都会对block内部用到的对象进行强引用的。解决办法使用:使用`__weak`打破循环的方法只在 ARC 下才有效,在 MRC 下应该使用`__block` 76 | ``` 77 | __weak typeof(self) weakSelf = self; self.myBlock = ^() { 78 | // 除了下面的还有 调用 self的一些属性等等 [weakSelf doSomething] 79 | }; 80 | ``` 81 | `情况三`: delegate 的循环引用 delegate是委托模式.委托模式是将一件属于委托者做的事情,交给另外一个被委托者来处理,在这里我们可能会出现委托者和被委托人之间的相互强引用问题; 82 | 83 | **解决办法:** 84 | >在声明 delegate 属性的时候 用weak 进行弱引用 或者 通过中间对象(代理对象)的方式来解决(效率更加高的中间对象NSProxy:不需要进行发送消息和再动态解析,直接进行消息转发) 85 | ``` 86 | @property(nonatomic, weak) id delegate; 87 | ``` 88 | `情况四`:CADisplayLink、NSTimer会对target产生强引用,如果target又对它们产生强引用,那么就会引发循环引用; 89 | **解决办法:** 90 | >NSTimer 有一个block的方法,我们可以利用block的弱指针来解决__weak typeof(self) weakSelf = self; 传 weakSelf 进去 91 | 92 | `情况五`:通知的循环引用 iOS9 以后,一般的通知,都不再需要手动移除观察者,系统会自动在dealloc 的时候调用 [[NSNotificationCenter defaultCenter] removeObserver: self]。iOS9 以前的需要手动进行移除。 93 | **原因是**:iOS9 以前观察者注册时,通知中心并不会对观察者对象做 retain 操作,而是进行了 unsafe_unretained 引用,所以在观察者被回收的时候,如果不对通知进行手动移除,那么指针指向被回收的内存区域就会成为野指针,这时再发送通知,便会造成程序崩溃。 94 | 从 iOS9 开始通知中心会对观察者进行 weak 弱引用,这时即使不对通知进行手动移除,指针也会在观察者被回收后自动置空,这时再发送通知,向空指针发送消息是不会有问题的。 95 | 96 | **建议最好加上移除通知的操作:** 97 | ``` 98 | (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"name" object:nil]; } 99 | ``` 100 | `情况六`:**WKWebView 造成的内存泄漏**\ 总的来说,WKWebView 不管是性能还是功能,都要比 UIWebView 强大很多,本身也不存在内存泄漏问题,但是,如果开发者使用不当,还是会造成内存泄漏。 101 | **请看下面这段代码:** 102 | ``` 103 | @property (nonatomic, strong) WKWebView *wkWebView; 104 | 105 | (void)webviewMemoryLeak { WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; 106 | config.userContentController = [[WKUserContentController alloc] init]; 107 | [config.userContentController addScriptMessageHandler:self name:@"WKWebViewHandler"]; 108 | _wkWebView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:config]; 109 | _wkWebView.backgroundColor = [UIColor whiteColor]; [self.view addSubview:_wkWebView]; 110 | NSURLRequest *requset = [NSURLRequest requestWithURL:[NSURL URLWithString:@"[https://www.baidu.com](https://www.baidu.com/)"]]; 111 | [_wkWebView loadRequest:requset]; 112 | } 113 | ``` 114 | 这样看起来没有问题,但是其实 “addScriptMessageHandler” 这个操作,导致了 wkWebView 对 self 进行了强引用,然后 “addSubview”这个操作,也让 self 对 wkWebView 进行了强引用,这就造成了循环引用。 115 | **解决方法就是在合适的机会里对 “MessageHandler” 进行移除操作:** 116 | ``` 117 | (void)viewDidDisappear:(BOOL)animated { 118 | [super viewDidDisappear:animated]; 119 | [_wkWebView.configuration.userContentController removeScriptMessageHandlerForName:@"WKWebViewHandler"]; 120 | } 121 | ``` 122 | 123 | 124 | ##内存泄漏的查询 125 | 126 | **第一种查询方式:**Analyze 静态分析 (command + shift + b)也就是编译, 127 | **主要分析以下四种问题:** 128 | `逻辑错误`:访问空指针或未初始化的变量等; 129 | `内存管理错误`:如内存泄漏等; 130 | `声明错误`:从未使用过的变量; 131 | `Api调用错误`:未包含使用的库和框架。 132 | **第二种查询方式:**Instruments中的Leak动态分析内存泄漏,product->profile ->leaks 打开工具主窗口 133 | **第三种:**Facebook早已开源了一款检测内存问题的三方库FBRetainCycleDetector 134 | 135 | ##面试题五 136 | **简单的描述一下 SDWebImage的缓存策略?** 137 | 首先,SDWebImage 的图片缓存采用的是 Memory(内存) 和 Disk(硬盘) 双重 Cache 机制,SDImageCache 中有一个叫做 memCache 的属性,它是一个 NSCache 对象,用于实现我们对图片的 Memory Cache,其实就是接受系统的内存警告通知,然后清除掉自身的图片缓存。 138 | Disk Cache,也就是文件缓存,SDWebImage 会将图片存放到 NSCachesDirectory 目录中,然后为每一个缓存文件生成一个 md5 文件名, 存放到文件中。 139 | **整体机制如下:** 140 | 141 | `Memory(内存)中查找:`SDImageCache 类的 queryDiskCacheForKey 方法,查询图片缓存,queryDiskCacheForKey 方法内部, 先会查询 Memory Cache ,如果查找到就直接返回,反之进入下面的硬盘查找。 142 | 143 | `Disk(硬盘) 中查找:`如果 Memory Cache 查找不到, 就会查询 Disk Cache,查询 Disk Cache 的时候有一个小插曲,就是如果 Disk Cache 查询成功,还会把得到的图片再次设置到 Memory Cache 中。 144 | 这样做可以最大化那些高频率展现图片的效率。 145 | 如果找不到就进入下面的网络下载。 146 | 147 | `网路下载:`请求网络使用的是 imageDownloader 属性,这个示例专门负责下载图片数据。 148 | 如果下载失败, 会把失败的图片地址写入 failedURLs 集合,为什么要有这个 failedURLs 呢, 因为 SDWebImage 默认会有一个对上次加载失败的图片拒绝再次加载的机制。 149 | 也就是说,一张图片在本次会话加载失败了,如果再次加载就会直接拒绝,SDWebImage 这样做可能是为了提高性能。 150 | 如果下载图片成功了,接下来就会使用 [self.imageCache storeImage] 方法将它写入缓存 ,同时也会写入硬盘,并且调用 completedBlock 告诉前端显示图片。 151 | 152 | `Disk(硬盘)缓存清理策略:`SDWebImage 会在每次 APP 结束的时候执行清理任务。 清理缓存的规则分两步进行。 第一步先清除掉过期的缓存文件。 如果清除掉过期的缓存之后,空间还不够。 那么就继续按文件时间从早到晚排序,先清除最早的缓存文件,直到剩余空间达到要求。 153 | 154 | ##面试题六 155 | **用递归算法计算 1 到 n 的和 ** 156 | 如下,递归主要是有终止的条件 157 | ``` 158 | /// 用递归方式获取 1~n 的相加之和 159 | /// - Parameter n: 数字几 160 | /// - Returns: 返回和 func getSum(_ n: Int) -> Int { return (n == 1) ? 1 : n + getSum(n - 1); } 161 | ``` 162 | ##面试题七 163 | **简述 MVC、MVP、MVVM 模式** 164 | 这三种模式均为MV*模式,M为模型层,V为视图层,都是希望能更好的对模型、视图与逻辑层的解耦。 165 | 166 | * ` MVC模型中,C为(controller)。` 167 | 主要处理逻辑为:View触发事件,controller响应并处理逻辑,调用Model,Model处理完成后将数据发送给View,View更新。 168 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/143604_2659ff5b_9027123.png "十面埋伏1.png") 169 | 170 | 171 | * `MVP模型中`,P为Presenter,并以Presenter为核心,负责从model获取数据,并填充到View中。 172 | 该模型使得Model和View不再有联系,且View被称为“被动视图”,暴露出setter接口。 173 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/143630_83c7a4aa_9027123.png "十面埋伏2.png") 174 | 175 | 176 | * `MVVM模型中`,VM为ViewModel,同样是以VM为核心,但是不同于MVP,MVVM采用了数据双向绑定的方案,替代了繁琐复杂的DOM操作。 177 | 该模型中,View与VM保持同步,View绑定到VM的属性上,如果VM数据发生变化,通过数据绑定的方式,View会自动更新视图; 178 | VM同样也暴露出Model中的数据。 179 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/143643_470f1225_9027123.png "十面埋伏3.png") 180 | 181 | ##面试题八 182 | **以下代码运行结果如何?** 183 | ``` 184 | (void)viewDidLoad { 185 | [super viewDidLoad]; 186 | NSLog(@"1"); 187 | dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2"); }); 188 | NSLog(@"3"); 189 | } 190 | ``` 191 | 只打印一个 1,原因是 viewDidLoad 和 dispatch_sync(dispatch_get_main_queue() 之间存在队列等待, viewDidLoad 方法是在串行队列优先执行完,而GCD的闭包要等到 viewDidLoad 执行完才能执行完,而 NSLog(@"3"); 要执行要先等GCD的闭包 执行完,相互等待,死锁 192 | 193 | ##面试题九 194 | 给定一个数组,用选择排序或者冒泡排序实现一个方法给数组排序 195 | 196 | ###冒泡排序 197 | 将前后每两个数进行比较,较大的数往后排,一轮下来最大的数就排到最后去了。 198 | 然后再进行第二轮比较,第二大的数也排到倒数第二了,以此类推,里面一层循环在某次扫描中没有执行交换,则说明此时数组已经全部有序列,无需在扫描了。 199 | 因此,增加一个标记,每次发生交换,就标记,如果某次循环没有标记,则说明已经完成排序。 200 | ``` 201 | NSMutableArray *array = [NSMutableArray arrayWithArray:@[@3, @5, @1, @9]]; 202 | BOOL isSort = true; for (int i = 0; i < array.count - 1; i++) { 203 | isSort = false; for (int j = 0; j < array.count - i- 1; j++) { 204 | if (array[j] > array[j + 1]) { 205 | [array exchangeObjectAtIndex:j withObjectAtIndex:j + 1]; 206 | isSort = YES; 207 | } 208 | } if (!isSort) { 209 | break; 210 | } 211 | } 212 | ``` 213 | ###选择排序 214 | 简单选择排序的基本思想:(从小到大) 第1趟,在待排序记录r[1]~r[n]中选出最小的记录,将它与r[1]交换; 第2趟,在待排序记录r[2]~r[n]中选出最小的记录,将它与r[2]交换; 以此类推,第i趟在待排序记录r[i]~r[n]中选出最小的记录,将它与r[i]交换,使有序序列不断增长直到全部排序完毕。 215 | ``` 216 | NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1,@100,@4,@3,nil]; 217 | for (int i = 0; i< arr.count; i++) { 218 | for (int j = i + 1; j < arr.count; 219 | j++) { 220 | if (arr[i] > arr[j]) { 221 | [arr exchangeObjectAtIndex:i withObjectAtIndex:j]; 222 | } 223 | } 224 | } 225 | NSLog(@"%@",arr); 226 | ``` 227 | ##面试题十 228 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标 229 | 230 | **方法一**:暴力法,遍历每个元素 x,并查找是否存在一个值与 target−x 相等的目标元素。 231 | 获取所有的可能 232 | ``` 233 | /// 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标 234 | /// - Parameters: 235 | /// - nums: 数组 236 | /// - target: 目标值 237 | /// - Returns: 数组下标的元祖 func twoSum( nums: [Int], target: Int) -> [Any] { 238 | guard nums.count >= 2 else { 239 | return [0] 240 | } var indexArray: [Any] = [] for i in 0.. [Any] { 253 | guard nums.count >= 2 else { 254 | return [0] 255 | } var tempHash: [Int : Int] = [:] var result : [Int] = [] var indexArray: [Any] = [] for (i, value) in nums.enumerated() { 256 | if let index = tempHash[target - value]{ 257 | result.append(index) result.append(i) indexArray.append((index,i)) 258 | } tempHash[value] = i 259 | } return indexArray 260 | } 261 | ``` 262 | -------------------------------------------------------------------------------- /iOS面试合集/iOS面试高薪,进阶 .md: -------------------------------------------------------------------------------- 1 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0531/150926_59269ee1_9027123.gif "高新1.gif") 2 | 3 | **这个栏目将持续更新--请iOS的小伙伴关注! 4 | 做这个的初心是希望能巩固自己的基础知识,当然也希望能帮助更多的开发者!** 5 | 6 | #大厂面试题视频详解 7 | >**[iOS面试题大全(上)](https://www.bilibili.com/video/BV1Jf4y1i72w)** 8 | > 9 | >**[iOS面试题大全(下)](https://www.bilibili.com/video/BV12K41137yc)** 10 | 11 | 12 | **在平时工作之余,我也创建了一个iOS技术交流群:[642363427](https://jq.qq.com/?_wv=1027&k=j07ImuG1),平时大家都会在里面探讨心得,不管你是大牛还是小白都欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!** 13 | 14 | #基础>分析>总结 面试 15 | - [iOS常见基础面试题(附参考答案)](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E5%B8%B8%E8%A7%81%E5%9F%BA%E7%A1%80%E9%9D%A2%E8%AF%95%E9%A2%98(%E9%99%84%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88).md) 16 | - [iOS底层原理之部分面试题分析](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E4%B9%8B%E9%83%A8%E5%88%86%E9%9D%A2%E8%AF%95%E9%A2%98%E5%88%86%E6%9E%90.md) 17 | - [iOS 涨薪: Run Loop 面试题](https://www.jianshu.com/p/e0605b4007e2) 18 | - [iOS面试反思总结](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E9%9D%A2%E8%AF%95%E5%8F%8D%E6%80%9D%E6%80%BB%E7%BB%93.md) 19 | - [iOS面试题文案及答案附件](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E9%9D%A2%E8%AF%95%E9%A2%98%E6%96%87%E6%A1%88%E5%8F%8A%E7%AD%94%E6%A1%88%E9%99%84%E4%BB%B6.md) 20 | 21 | #面试心得 22 | - [面试题 拓展:常用框架和第三方框架](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/%E9%9D%A2%E8%AF%95%E9%A2%98%20%E6%8B%93%E5%B1%95%EF%BC%9A%E5%B8%B8%E7%94%A8%E6%A1%86%E6%9E%B6%E5%92%8C%E7%AC%AC%E4%B8%89%E6%96%B9%E6%A1%86%E6%9E%B6.md) 23 | 24 | #Objective-C 25 | - [Objective-C与Swift的贯通编程](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/Objective-C%E4%B8%8ESwift%E7%9A%84%E8%B4%AF%E9%80%9A%E7%BC%96%E7%A8%8B.md) 26 | - [Objective-C自定义UITextView](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/%E3%80%90Objective-C%E3%80%91%E8%87%AA%E5%AE%9A%E4%B9%89UITextView.md) 27 | - [Objective-C语言的动态性](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/%E3%80%90Objective-C%E3%80%91Objective-C%E8%AF%AD%E8%A8%80%E7%9A%84%E5%8A%A8%E6%80%81%E6%80%A7.md) 28 | - [Objective-C探索Category底层的实质](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/%E3%80%90Objective-C%E3%80%91%E6%8E%A2%E7%B4%A2Category%E5%BA%95%E5%B1%82%E7%9A%84%E5%AE%9E%E8%B4%A8.md) 29 | 30 | #iOS 安防 优化 31 | - [优化iOS小技巧](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%80%86%E5%90%91%E5%AE%89%E9%98%B2/%E4%BC%98%E5%8C%96iOS%E5%B0%8F%E6%8A%80%E5%B7%A7.md) 32 | - [iOS 内存管理总结](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E8%BF%9B%E9%98%B6%E6%8F%90%E5%8D%87%E8%B5%84%E6%BA%90/iOS%20%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%80%BB%E7%BB%93.md) 33 | - [iOS开发---数据结构](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E8%BF%9B%E9%98%B6%E6%8F%90%E5%8D%87%E8%B5%84%E6%BA%90/iOS%E5%BC%80%E5%8F%91---%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.md) 34 | - [iOS 多线程 线程间的状态](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E5%BA%95%E5%B1%82%E8%BF%9B%E9%98%B6/iOS%20%E5%A4%9A%E7%BA%BF%E7%A8%8B%20%E7%BA%BF%E7%A8%8B%E9%97%B4%E7%9A%84%E7%8A%B6%E6%80%81.md) 35 | - [iOS安全:Mach-O Type](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E8%BF%9B%E9%98%B6%E6%8F%90%E5%8D%87%E8%B5%84%E6%BA%90/iOS%E5%AE%89%E5%85%A8%EF%BC%9AMach-O%20Type.md) 36 | - [iOS 各种UI控件属性设置](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E8%BF%9B%E9%98%B6%E6%8F%90%E5%8D%87%E8%B5%84%E6%BA%90/iOS%20%E5%90%84%E7%A7%8DUI%E6%8E%A7%E4%BB%B6%E5%B1%9E%E6%80%A7%E8%AE%BE%E7%BD%AE.md) 37 | - [iOS自动化布局-AutoLayout约束优先级](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E8%BF%9B%E9%98%B6%E6%8F%90%E5%8D%87%E8%B5%84%E6%BA%90/iOS%E8%87%AA%E5%8A%A8%E5%8C%96%E5%B8%83%E5%B1%80-AutoLayout%E7%BA%A6%E6%9D%9F%E4%BC%98%E5%85%88%E7%BA%A7.md) 38 | 39 | #Swift 40 | - [Swift开发之泛型实例](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/Swift/Swift%E5%BC%80%E5%8F%91%E4%B9%8B%E6%B3%9B%E5%9E%8B%E5%AE%9E%E4%BE%8B.md) 41 | - [Swift实现代码 iOS架构模式之MVP](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/Swift/%E3%80%90Swift%E5%AE%9E%E7%8E%B0%E4%BB%A3%E7%A0%81%E3%80%91iOS%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F%E4%B9%8BMVP.md) 42 | - [Swift WKWebView与JS的交互使用](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/Swift/%E3%80%90Swift%E3%80%91WKWebView%E4%B8%8EJS%E7%9A%84%E4%BA%A4%E4%BA%92%E4%BD%BF%E7%94%A8.md) 43 | - [Swift进阶之路——单例模式、属性传值、代理传值、闭包传值](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/Swift/Swift%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F%E3%80%81%E5%B1%9E%E6%80%A7%E4%BC%A0%E5%80%BC%E3%80%81%E4%BB%A3%E7%90%86%E4%BC%A0%E5%80%BC%E3%80%81%E9%97%AD%E5%8C%85%E4%BC%A0%E5%80%BC.md) 44 | - [Swift、OC分别实现用" | "隔开数组且只显示一行的小功能](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/Swift/Swift%E3%80%81OC%E5%88%86%E5%88%AB%E5%AE%9E%E7%8E%B0%E7%94%A8%22%20%7C%20%22%E9%9A%94%E5%BC%80%E6%95%B0%E7%BB%84%E4%B8%94%E5%8F%AA%E6%98%BE%E7%A4%BA%E4%B8%80%E8%A1%8C%E7%9A%84%E5%B0%8F%E5%8A%9F%E8%83%BD.md) 45 | 46 | #持续更新,请多多关注 47 | - **整理不易,如果您觉得还不错,麻烦在文末 “点个赞” 或者 评论 “Mark”,谢谢您的支持** -------------------------------------------------------------------------------- /iOS面试合集/【Objective-C】Objective-C语言的动态性.md: -------------------------------------------------------------------------------- 1 | Objective-C语言的动态性主要体现在以下3个方面 2 | 3 |   (1)动态类型:运行时确定对象的类型。 4 | 5 |   (2)动态绑定:运行时确定对象的方法。 6 | 7 |   (3)动态加载:运行时加载需要的资源或者或代码模块。 8 | 9 | **一、动态类型** 10 |   动态类型指对象指针类型的动态性,具体地说就是使用id类型将对象的类型推迟到运行时才确定,由赋给它的对象类型决定该对象类型(说起来怎么这么绕口),也就是说id修饰的对象是动态类型对象,其他在编译期指明类型的为静态类型对象,所以开发中如果不是涉及到多态,尽量还是使用静态的类型,这样编写错误,编译器会提前查出问题,可读性更高一点。 11 | ``` 12 | //编译时认为是NSString,这是赋值了一个NSData对象编译器会给出警告信息:Incompatible pointer types initializing 'NSString *' with an expression of type 'NSData *' 13 | NSString *testObject = [[NSData alloc]init]; 14 | //编译其认为是NSString,所以允许使用NSString的方法,不会有警告和错误, 15 | [testObject stringByAppendingString:@"string"]; 16 | //编译期不允许使用NSData的方法,错误提示;No visible @interface for 'NSString' declares the selector 'base64EncodedDataWithOptions:' 17 | [testObject base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength]; 18 | ``` 19 |   如以上代码,testObject在编译时,指针的类型是NSString,也就是说编译时期是被当做一个NSString类型来处理,编译器在类型检查时发现类型不匹配会给出警告信息,testObject在运行时,指针指向的是一个NSData对象,因此如果指针调用了NSString的方法,那么虽然编译通过了,但运行时会出现崩溃, 20 | 21 | **二、动态绑定** 22 |   动态绑定是建立在动态类型的基础之上,在OC的消息分发机制下将要执行的方法推迟到运行时才确定,可以动态的添加方法。也就是说一个OC对象是否调用某个方法不是在编译器确定的,方法的调用不和代码绑定在一起,而是到了运行时根据发出的具体消息,才去动态的确定需要调用的代码。 23 | 24 | **三、动态加载** 25 |   动态加载分为两部分:动态资源的加载(如:图片资源),代码模块的加载;这些都是在运行时根据需要有选择性的添加到程序中的,是一种代码和资源的“懒加载”模式,这样降低编译时期对内存的开销,提供程序的性能。 26 | 27 |   如:资源在动态加载图片进行屏幕适配时,因为同一个图片对象可能会准备几种不同分辨率的图片资源,程序就会根据当前机型动态的选择对应分辨率的图片,如:@1x,@2x,@3x的。 28 | 29 | **四、消息传递机制** 30 |   在OC中,方法的调用不能再去理解为对象调用其方法,而是要理解成对象接收消息。消息的发送采用“动态绑定”的机制,具体会调用那个方法直到运行时才确定。方法的调用其实就是告诉对象要做些什么事,给对象发送一个消息,对象为就是接收者recevier,调用的方法及其参数就是消息message,如果要给一个对象传递消息,可以表示为:[receiver message:xxx]。 31 | 32 |   在消息传递机制中,当开发者编写[receiver message:xxx]语句进行发送消息后,编译器都会将其转换成objc_msgSend C语言的发送格式。格式为: 33 | ``` 34 | void objc_msgSend(id self, SEL sel ...); 35 | ``` 36 |   这个函数参数可变,第一个参数填入消息的接收者,第二个参数传入的是消息,后面可以跟一下可选的消息参数。有了这些参数,objc_msgSend就能根据接收者的isa指针,到其对象的方法列表中以sel 的名称寻找对应的方法。若找到对应的方法,就会转到它的实现代码执行,如果找不到,就去父类中寻找,如果找到了根类还是无法找到对应的方法,说明接收者对象无法响应该消息,那么就会触发消息转发机制,给开发者一次补救程序的机会。 37 | 38 | **五、消息转发机制** 39 |   如果在消息传递过程中,接收者无法响应收到的消息,那么就会触发到消息转发机制。 40 | 41 |   消息转发提供了3道防线,任何一个起了作用,都能补救此次消息转发。依次为: 42 | 43 |   (1)动态的补加方法的实现 44 | ``` 45 | +(BOOL)resolveClassMethod:(SEL)sel 46 | +(BOOL)resolveInstanceMethod:(SEL) 47 | ``` 48 |   (2)直接返回消息到转发到的对象(就是将消息发送到另一个对象去处理) 49 | ``` 50 | -(id)forwardingTargetForSelector:(SEL)aSelector 51 | ``` 52 |   (3)手动生成签名并转发给另外一个对象 53 | ``` 54 | -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 55 | -(void)forwardInvocation:(NSInvocation *)anInvocation 56 | ``` 57 | **六、OC的编译时和运行时都做了哪些工作?** 58 |   编译时:该阶段,编译器对语言进行编译,编译器只会对语言进行最基本的检查报错、语法分析等,并将程序代码翻译成计算机能够识别的语言。那编译通过了,是不是就可以成功执行呢?你太单纯了,想的美。 59 | 60 |   运行时:程序通过了编译之后,就会将编译好的代码转载到内存中,这时候就会对类型进行检查,不仅仅是简单的扫描分析,此时若出现问题,程序可就Game Over了。 61 | 62 |   编译时就是一个静态的阶段,类型明显错误,就会被直接检查出来,运行时时动态的阶段,会将程序与开发环境结合起来。 63 | 64 |   OC是动态运行时语言,主要指的是OC语言的动态性。 65 | 66 |   动态性即OC的动态类型、动态绑定、动态加载,将对象类型的确定、方法调用的确定、代码和资源的转载推迟到运行时记性,灵活方便。 67 | 68 | -------------------------------------------------------------------------------- /iOS面试合集/【Objective-C】探索Category底层的实质.md: -------------------------------------------------------------------------------- 1 | 无论一个类设计的多么完美,在未来的需求演进中,都有可能会碰到一些无法预测的情况。那怎么扩展已有的类呢?一般而言,继承和组合是不错的选择。但是在Objective-C 2.0中,又提供了category这个语言特性,可以动态地为已有类添加新行为。如今category已经遍布于Objective-C代码的各个角落,从Apple官方的framework到各个开源框架,从功能繁复的大型APP到简单的应用,catagory无处不在。本文对category做了比较全面的整理,希望对读者有所裨益。   2 | 3 |   **Objective-C中类别特性的作用如下:** 4 | 5 |   (1)可以将类的实现分散到多个不同文件或多个不同框架中(补充新的方法)。 6 | 7 |   (2)可以创建私有方法的前向引用。 8 | 9 |   (3)可以向对象添加非正式协议。 10 | 11 |   **Objective-C中类别特性的局限性如下:** 12 | 13 |   (1)类别只能想原类中添加新的方法,且只能添加而不能删除或修改原方法,不能向原类中添加新的属性。 14 | 15 |   (2)类别向原类中添加的方法是全局有效的而且优先级最高,如果和原类的方法重名,那么会无条件覆盖掉原来的方法。  16 | 17 |  **一、Category的底层实现** 18 |    Objective-C 通过 Runtime 运行时来实现动态语言这个特性,所有的类和对象,在 Runtime 中都是用结构体来表示的,Category 在 Runtime 中是用结构体 category_t 来表示的,下面是结构体 category_t 具体表示: 19 | ```typedef struct category_t { 20 | const char *name;//类的名字 主类名字 21 | classref_t cls;//类 22 | struct method_list_t *instanceMethods;//实例方法的列表 23 | struct method_list_t *classMethods;//类方法的列表 24 | struct protocol_list_t *protocols;//所有协议的列表 25 | struct property_list_t *instanceProperties;//添加的所有属性 26 | } category_t; 27 | ``` 28 |   从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。 29 | 30 |   我们将结合 runtime 的源码探究下 Category 的实现原理。打开 runtime 源码工程,在文件 objc-runtime-new.mm 中找到以下函数: 31 | ``` 32 | void _read_images(header_info **hList, uint32_t hCount) 33 | { 34 | ... 35 | _free_internal(resolvedFutureClasses); 36 | } 37 | 38 | // Discover categories. 39 | for (EACH_HEADER) { 40 | category_t **catlist = 41 | _getObjc2CategoryList(hi, &count); 42 | for (i = 0; i < count; i++) { 43 | category_t *cat = catlist[i]; 44 | Class cls = remapClass(cat->cls); 45 | 46 | if (!cls) { 47 | // Category's target class is missing (probably weak-linked). 48 | // Disavow any knowledge of this category. 49 | catlist[i] = nil; 50 | if (PrintConnecting) { 51 | _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with " 52 | "missing weak-linked target class", 53 | cat->name, cat); 54 | } 55 | continue; 56 | } 57 | 58 | // Process this category. 59 | // First, register the category with its target class. 60 | // Then, rebuild the class's method lists (etc) if 61 | // the class is realized. 62 | BOOL classExists = NO; 63 | if (cat->instanceMethods || cat->protocols 64 | || cat->instanceProperties) 65 | { 66 | addUnattachedCategoryForClass(cat, cls, hi); 67 | if (cls->isRealized()) { 68 | remethodizeClass(cls); 69 | classExists = YES; 70 | } 71 | if (PrintConnecting) { 72 | _objc_inform("CLASS: found category -%s(%s) %s", 73 | cls->nameForLogging(), cat->name, 74 | classExists ? "on existing class" : ""); 75 | } 76 | } 77 | 78 | if (cat->classMethods || cat->protocols 79 | /* || cat->classProperties */) 80 | { 81 | addUnattachedCategoryForClass(cat, cls->ISA(), hi); 82 | if (cls->ISA()->isRealized()) { 83 | remethodizeClass(cls->ISA()); 84 | } 85 | if (PrintConnecting) { 86 | _objc_inform("CLASS: found category +%s(%s)", 87 | cls->nameForLogging(), cat->name); 88 | } 89 | } 90 | } 91 | } 92 | 93 | // Category discovery MUST BE LAST to avoid potential races 94 | // when other threads call the new category code before 95 | // this thread finishes its fixups. 96 | 97 | // +load handled by prepare_load_methods() 98 | 99 | ... 100 | } 101 | ``` 102 |   我们可以知道在这个函数中对 Category 做了如下处理: 103 | 104 |   (1)将 Category 和它的主类(或元类)注册到哈希表中; 105 | 106 |   (2)如果主类(或元类)已实现,那么重建它的方法列表; 107 | 108 |   **Category的实现原理:** 109 | 110 | - 在编译时期,会将分类中实现的方法生成一个结构体 method_list_t 、将声明的属性生成一个结构体 property_list_t ,然后通过这些结构体生成一个结构体 category_t 。 111 | - 然后将结构体 category_t 保存下来 112 | - 在运行时期,Runtime 会拿到编译时期我们保存下来的结构体 category_t 113 | - 然后将结构体 category_t 中的实例方法列表、协议列表、属性列表添加到主类中 114 | - 将结构体 category_t 中的类方法列表、协议列表添加到主类的 metaClass 中 115 | 116 | 117 | 118 | **二、为何Category中的方法优先级高于原类中的方法?** 119 |   category_t 中的方法列表是插入到主类的方法列表前面(类似利用链表中的 next 指针来进行插入),所以这里 Category 中实现的方法并不会真正的覆盖掉主类中的方法,只是将 Category 的方法插到方法列表的前面去了。运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会停止查找,这里就会出现覆盖方法的这种假象了。 120 | ``` 121 | // 这里大概就类似这样子插入 122 | newproperties->next = cls->data()->properties; 123 | cls->data()->properties = newproperties;, 124 | ``` 125 | **三、为何Category中不能添加实例变量?** 126 |   通过结构体 category_t ,我们就可以知道,在 Category 中我们可以增加实例方法、类方法、协议、属性。这里没有 objc_ivar_list 结构体,代表我们不可以在分类中添加实例变量。 127 |   因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局,这个就是 Category 中不能添加实例变量的根本原因。 128 | 129 | -------------------------------------------------------------------------------- /iOS面试合集/【Objective-C】自定义UITextView.md: -------------------------------------------------------------------------------- 1 | 代码支持: 2 | 1、长按textView弹出换行操作; 3 |   2、自定义文字间距; 4 |   3、为textView添加placeholder文字; 5 |   直接贴代码: 6 |   1、.m文件 7 | ``` 8 | #import "TextView.h" 9 | 10 | @interface TextView () 11 | 12 | { 13 | NSInteger b_index; 14 | } 15 | @end 16 | 17 | @implementation TextView 18 | 19 | CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25; 20 | 21 | - (void)dealloc 22 | { 23 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 24 | #if __has_feature(objc_arc) 25 | #else 26 | [_placeHolderLabel release]; _placeHolderLabel = nil; 27 | [_placeholderColor release]; _placeholderColor = nil; 28 | [_placeholder release]; _placeholder = nil; 29 | [super dealloc]; 30 | #endif 31 | } 32 | 33 | - (void)awakeFromNib 34 | { 35 | [super awakeFromNib]; 36 | if (!self.placeholder) { 37 | _placeholder = @""; 38 | } 39 | 40 | if (!self.placeholderColor) { 41 | [self setPlaceholderColor:[UIColor lightGrayColor]]; 42 | } 43 | 44 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil]; 45 | } 46 | 47 | - (id)initWithFrame:(CGRect)frame 48 | { 49 | if( (self = [super initWithFrame:frame]) ) 50 | { 51 | _placeholder = @""; 52 | self.tintColor = [UIColor colorWithHexString:@"#B1B5BA"]; 53 | self.returnKeyType = UIReturnKeyDone; 54 | [self setPlaceholderColor:[UIColor lightGrayColor]]; 55 | 56 | //设置菜单 57 | 58 | UIMenuItem *menuItem = [[UIMenuItem alloc]initWithTitle:@"换行" action:@selector(selfMenu:)]; 59 | UIMenuController *menuController = [UIMenuController sharedMenuController]; 60 | [menuController setMenuItems:[NSArray arrayWithObject:menuItem]]; 61 | [menuController setMenuVisible:NO]; 62 | 63 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil]; 64 | } 65 | return self; 66 | } 67 | 68 | -(BOOL)canPerformAction:(SEL)action withSender:(id)sender{ 69 | if (action == @selector(selfMenu:)) { 70 | return YES; 71 | 72 | }else if(action ==@selector(copy:) || action ==@selector(selectAll:) || action ==@selector(cut:) || action ==@selector(select:)){ 73 | BOOL isAppear = [super canPerformAction:action withSender:sender]; 74 | return isAppear; 75 | } 76 | return NO; 77 | 78 | } 79 | 80 | -(void)selfMenu:(id)sender{ 81 | self.text = [self.text stringByAppendingString:@"\n"]; 82 | } 83 | 84 | - (void)textChanged:(NSNotification *)notification 85 | { 86 | if([[self placeholder] length] == 0) 87 | { 88 | return; 89 | } 90 | 91 | [UIView animateWithDuration:UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION animations:^{ 92 | if([[self text] length] == 0) 93 | { 94 | [[self viewWithTag:999] setAlpha:1]; 95 | } 96 | else 97 | { 98 | [[self viewWithTag:999] setAlpha:0]; 99 | } 100 | }]; 101 | 102 | } 103 | 104 | - (void)setText:(NSString *)text { 105 | [super setText:text]; 106 | [self textChanged:nil]; 107 | } 108 | 109 | - (void)drawRect:(CGRect)rect 110 | { 111 | if( [[self placeholder] length] > 0 ) 112 | { 113 | UIEdgeInsets insets = self.textContainerInset; 114 | if (_placeHolderLabel == nil ) 115 | { 116 | 117 | _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(insets.left+10,5,self.bounds.size.width - (insets.left +insets.right+20),1.0)];//insets.top 118 | _placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping; 119 | _placeHolderLabel.font = self.font; 120 | _placeHolderLabel.backgroundColor = [UIColor clearColor]; 121 | _placeHolderLabel.textColor = self.placeholderColor; 122 | _placeHolderLabel.alpha = 0; 123 | _placeHolderLabel.tag = 999; 124 | [self addSubview:_placeHolderLabel]; 125 | } 126 | _placeHolderLabel.text = self.placeholder; 127 | [_placeHolderLabel sizeToFit];//insets.top 128 | [_placeHolderLabel setFrame:CGRectMake(insets.left+10,5,self.bounds.size.width - (insets.left +insets.right),CGRectGetHeight(_placeHolderLabel.frame))]; 129 | [self sendSubviewToBack:_placeHolderLabel]; 130 | } 131 | 132 | if( [[self text] length] == 0 && [[self placeholder] length] > 0 ) 133 | { 134 | [[self viewWithTag:999] setAlpha:1]; 135 | } 136 | [super drawRect:rect]; 137 | } 138 | - (void)setPlaceholder:(NSString *)placeholder{ 139 | if (_placeholder != placeholder) { 140 | _placeholder = placeholder; 141 | [self setNeedsDisplay]; 142 | } 143 | 144 | } 145 | 146 | 147 | -(void)textViewlineSpacing:(TextView *)textView{ 148 | 149 | if (textView.text.length < 1) { 150 | textView.text = @"间距"; 151 | } 152 | NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init]; 153 | paragraphStyle.lineSpacing = 4;// 字体的行间距 154 | NSDictionary *attributes = @{ 155 | 156 | NSFontAttributeName:[UIFont systemFontOfSize:13], 157 | 158 | NSParagraphStyleAttributeName:paragraphStyle 159 | 160 | }; 161 | 162 | textView.attributedText = [[NSAttributedString alloc] initWithString:self.text attributes:attributes]; 163 | textView.textColor = kTextColor_Black; 164 | if ([textView.text isEqualToString:@"间距"]) { 165 | textView.attributedText = [[NSAttributedString alloc] initWithString:@"" attributes:attributes]; 166 | } 167 | } 168 | 169 | @end 170 | ``` 171 | 2、.h文件 172 | ``` 173 | #import 174 | 175 | @interface TextView : UITextView 176 | 177 | @property (nonatomic, strong) NSString *placeholder; 178 | @property (nonatomic, strong) UIColor *placeholderColor; 179 | 180 | @property (nonatomic, strong) UILabel *placeHolderLabel; 181 | 182 | 183 | -(void)textChanged:(NSNotification*)notification; 184 | 185 | -(void)textViewlineSpacing:(TextView *)textView; 186 | 187 | @end 188 | ``` 189 | ### 190 | 191 | 分享: 192 | 193 | 工作之余,想进阶技术的同时找到志同道合的[**RD**,**欢迎点击RD**。](https://jq.qq.com/?_wv=1027&k=DaAyV952) 194 | 195 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/151016_9d41772b_9027123.jpeg "24396273-edf435cd4d7f39cf.jpg") 196 | 197 | -------------------------------------------------------------------------------- /iOS面试合集/关于 iOS 性能优化方面的面试题.md: -------------------------------------------------------------------------------- 1 | 这是我前面几天碰到的面试题: 2 | 3 | **如何对定位和分析项目中影响性能的地方?以及如何进行性能优化?** 4 | 5 | #我的答案: 6 | 7 | ###定位方法: 8 | 9 | **instruments** 10 | 11 |   在iOS上进行性能分析的时候,首先考虑借助instruments这个利器分析出问题出在哪,不要凭空想象,不然你可能把精力花在了1%的问题上,最后发现其实啥都没优化,比如要查看程序哪些部分最耗时,可以使用Time Profiler,要查看内存是否泄漏了,可以使用Leaks等。关于instruments网上有很多资料,作为一个合格iOS开发者,熟悉这个工具还是很有必要的。 12 | 13 | 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!希望帮助开发者少走弯路。 14 | 15 | ###优化建议: 16 | 17 | **1.用ARC管理内存** 18 | 19 | * ARC(Automatic Reference Counting, 自动引用计数)和iOS5一起发布,它避免了最常见的也就是经常是由于我们忘记释放内存所造成的内存泄露。它自动为你管理retain和release的过程,所以你就不必去手动干预了。下面是你会经常用来去创建一个View的代码段: UIView *view = [[UIView alloc] init]; 20 | 21 | * // ... 22 | 23 | * [self.view addSubview:view]; 24 | 25 | * [view release]; 26 | 27 | * 忘掉代码段结尾的release简直像记得吃饭一样简单。而ARC会自动在底层为你做这些工作。除了帮你避免内存泄露,ARC还可以帮你提高性能,它能保证释放掉不再需要的对象的内存。这都啥年代了,你应该在你的所有项目里使用ARC! 28 | 29 | **2.在正确的地方使用 reuseIdentifier** 30 | 31 | * 一个开发中常见的错误就是没有给UITableViewCells, UICollectionViewCells,甚至是UITableViewHeaderFooterViews设置正确的reuseIdentifier。 32 | 33 | * 为了性能最优化,table view用 tableView:cellForRowAtIndexPath: 为rows分配cells的时候,它的数据应该重用自UITableViewCell。 一个table view维持一个队列的数据可重用的UITableViewCell对象。不使用reuseIdentifier的话,每显示一行table view就不得不设置全新的cell。这对性能的影响可是相当大的,尤其会使app的滚动体验大打折扣。 34 | 35 | * 自iOS6起,除了UICollectionView的cells和补充views,你也应该在header和footer views中使用reuseIdentifiers 36 | 37 | **3.尽量把views设置为完全不透明** 38 | 39 | * 如果你有透明的Views你应该设置它们的opaque(不透明)属性为YES。例如一个黑色半透明的可以设置为一个灰色不透明的View替代.原因是这会使系统用一个最优的方式渲染这些views。这个简单的属性在IB或者代码里都可以设定。 40 | 41 | * Apple的文档对于为图片设置透明属性的描述是: 42 | 43 | * (opaque)这个属性给渲染系统提供了一个如何处理这个view的提示。如果设为YES, 渲染系统就认为这个view是完全不透明的,这使得渲染系统优化一些渲染过程和提高性能。如果设置为NO,渲染系统正常地和其它内容组成这个View。默认值是YES。 44 | 45 | * 在相对比较静止的画面中,设置这个属性不会有太大影响。然而当这个view嵌在scroll view里边,或者是一个复杂动画的一部分,不设置这个属性的话会在很大程度上影响app的性能。 46 | 47 | * 换种说法,大家可能更好理解:只要一个视图的不透明度小于1,就会导致blending.blending操作在iOS的图形处理器(GPU)中完成的,blending主要指的是混合像素颜色的计算。举个例子,我们把两个图层叠加在一起,如果第一个图层的有透明效果,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending。为什么Blending会导致性能的损失?原因是很直观的,如果一个图层是完全不透明的,则系统直接显示该图层的颜色即可。而如果图层是带透明效果的,则会引入更多的计算,因为需要把下面的图层也包括进来,进行混合后颜色的计算。 48 | 49 | **4. 避免过于庞大的XIB** 50 | 51 | * iOS5中加入的Storyboards(分镜)正在快速取代XIB。然而XIB在一些场景中仍然很有用。比如你的app需要适应iOS5之前的设备,或者你有一个自定义的可重用的view,你就不可避免地要用到他们。 52 | 53 | * 如果你不得不XIB的话,使他们尽量简单。尝试为每个Controller配置一个单独的XIB,尽可能把一个View Controller的view层次结构分散到单独的XIB中去。 54 | 55 | * 需要注意的是,当你加载一个XIB的时候所有内容都被放在了内存里,包括任何图片。如果有一个不会即刻用到的view,你这就是在浪费宝贵的内存资源了。Storyboards就是另一码事儿了,storyboard仅在需要时实例化一个view controller. 56 | 57 | * 当你加载一个引用了图片或者声音资源的nib时,nib加载代码会把图片和声音文件写进内存。在OS X中,图片和声音资源被缓存在named cache中以便将来用到时获取。在iOS中,仅图片资源会被存进named caches。取决于你所在的平台,使用NSImage 或UIImage 的imageNamed:方法来获取图片资源。 58 | 59 | **5. 不要阻塞主线程** 60 | 61 | * 永远不要使主线程承担过多。因为UIKit在主线程上做所有工作,渲染,管理触摸反应,回应输入等都需要在它上面完成。一直使用主线程的风险就是如果你的代码真的block了主线程,你的app会失去反应 62 | 63 | * 大部分阻碍主进程的情形是你的app在做一些牵涉到读写外部资源的I/O操作,比如存储或者网络。或者使用像 AFNetworking这样的框架来异步地做这些操作。如果你需要做其它类型的需要耗费巨大资源的操作(比如时间敏感的计算或者存储读写)那就用 Grand Central Dispatch,或者 NSOperation 和 NSOperationQueues.你可以使用NSURLConnection异步地做网络操作: + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler 64 | 65 | **6. 在Image Views中调整图片大小** 66 | 67 | * 如果要在UIImageView中显示一个来自bundle的图片,你应保证图片的大小和UIImageView的大小相同。在运行中缩放图片是很耗费资源的,特别是UIImageView嵌套在UIScrollView中的情况下。 68 | 69 | * 如果图片是从远端服务加载的你不能控制图片大小,比如在下载前调整到合适大小的话,你可以在下载完成后,最好是用background thread,缩放一次,然后在UIImageView中使用缩放后的图片。 70 | 71 | **7. 选择正确的Collection** 72 | 73 | 学会选择对业务场景最合适的类或者对象是写出能效高的代码的基础。当处理collections时这句话尤其正确。 74 | 75 | Apple有一个 Collections Programming Topics 的文档详尽介绍了可用的classes间的差别和你该在哪些场景中使用它们。这对于任何使用collections的人来说是一个必读的文档。 76 | 77 | 呵呵,我就知道你因为太长没看…这是一些常见collection的总结: 78 | 79 | * Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插入/删除很慢。 80 | 81 | * Dictionaries: 存储键值对。 用键来查找比较快。 82 | 83 | * Sets: 无序的一组值。用值来查找很快,插入/删除很快。 84 | 85 | **8. 打开gzip压缩** 86 | 87 | * 大量app依赖于远端资源和第三方API,你可能会开发一个需要从远端下载XML, JSON, HTML或者其它格式的app。 88 | 89 | * 问题是我们的目标是移动设备,因此你就不能指望网络状况有多好。一个用户现在还在edge网络,下一分钟可能就切换到了3G。不论什么场景,你肯定不想让你的用户等太长时间。 90 | 91 | * 减小文档的一个方式就是在服务端和你的app中打开gzip。这对于文字这种能有更高压缩率的数据来说会有更显著的效用。好消息是,iOS已经在NSURLConnection中默认支持了gzip压缩,当然AFNetworking这些基于它的框架亦然。像Google App Engine这些云服务提供者也已经支持了压缩输出。 -------------------------------------------------------------------------------- /iOS面试合集/如何一举拿下大厂Offer面经(附面试题).md: -------------------------------------------------------------------------------- 1 | **准备** 2 | 3 | 其实我很早就开始准备了,准确来说也不算准备,只是一直在总结iOS相关方面的知识,但是还是有大把时间可以自己学习一些感兴趣的方向。从过完年回来,我就有计划的复习和总结了一些知识。 4 | 5 | 看过的书籍,这里并不是泛泛的读一遍,而是详细理解了大多数内容,通俗一点就是可以用自己的话将相应的知识解读出 来 。《Android开发艺术探索》(这本书真心不错,我反复读了4、5遍)、《iOS群英传》(比较接近开发使用,因为做过一些应用开发,读起来还是比较简单的,读了2遍)、《剑指offer》(感觉面试中碰到的算法,70%都能找到相应的题目,保证所有的题都可以手写出来就行)。4个月精读了以上书籍,还有其他的都是简单了解,这里就不列举了,读完这些书,应该可以让你上一个层次吧(妈妈再也不用担心我面试啦…)。 6 | 7 | 刷题,主要是LeetCode(大概刷了300道题左右,每天3-6道,坚持下来,需要多复习,因为很多题过一段时间会忘记),还有看过一些牛课网。 8 | 9 | 看别人的面试经验,主要在网上,这里我列举两个比较好的。 10 | 11 | * 1、 iOS客户端面试题集锦 12 | * 2、 iOS阿里面试题锦集 13 | 14 | **投递简历** 15 | 16 | 一份好的简历是非常有必要的,需要突出你的重点和闪光点,具体怎么写简历可以参考 17 | 18 | ## [iOS面试高薪,进阶 你会这些呢嘛?(持续更新中)](https://gitee.com/cresta-df/i-os-engineers-secret/blob/master/iOS%E9%9D%A2%E8%AF%95%E5%90%88%E9%9B%86/iOS%E9%9D%A2%E8%AF%95%E9%AB%98%E8%96%AA%EF%BC%8C%E8%BF%9B%E9%98%B6%20.md) 19 | 20 | **CodeKK说简历** 21 | 22 | 有了一份好简历,接下来就是投递简历,一般是:拉钩+BOSS直聘+内推,从我这次面试机会来看,三者比例是2:2:1,如果被刷掉也不要灰心,现在大公司基本上各个部门都有自己的hr,可以在拉头和BOSS上多投递一些,万一其他部门看中你呢? 23 | 24 | 面试经历 25 | 26 | 这里我仅仅记录一些问过的题目(能记住的),答案我就不写出来,基本上都可以在网上找到相应的答案。 27 | 28 | **一面** 29 | 30 | 1、iOS一些优化方案 31 | 32 | 2、最常用的版本控制工具是什么,能大概讲讲原理么 33 | 34 | 3、UNIX常用命令 35 | 36 | 4、c语言在iOS开发中的重要性 37 | 38 | 5、源代码管理工具的作用 39 | 40 | **二面** 41 | 42 | 二面面试官是Eva?反正应该不是做iOS的,iOS的相关知识问的也不多,大多是项目上的东西。 43 | 44 | atomic的多线程安全 45 | 46 | 聊项目,都具体做了什么。 47 | 48 | nonatomic在自己管理内存的环境 49 | 50 | **三面** 51 | 52 | 应该是Eva吧,主要了解一些个人的情况,以及一些项目,最后问了期望的薪资,然后当场就给了offer。 53 | 54 | **快手** 55 | 56 | **一面** 57 | 58 | 问了关于数据库的一些问题,SQLite的相关操作,没办法,我在华为唯一一个做的和iOS相关的项目,但是不太擅长数据库。 59 | 60 | 网络相关的问题,网络的五层模型,又问了TCP和TIP,还有iOS相关的长连接,这里问的比较深。 61 | 62 | 开始iOS相关的知识,视觉控制器的生命周期(view的生命周期)内存告急的处理(手动释放不可见视图的内存和成员变量) 63 | 64 | 第一面这就算过关了等待二面。 65 | 66 | **二面** 67 | 68 | 问了项目相关的问题,这部分根据自己的项目经验,由于大家的经验都不同,这里我就不详细说了。 69 | 70 | 设定一个场景,怎么去实现相应的功能,因为快手这个部门想做社交,因此这里是问我是如何实现微信的联系人页面(包括与服务端有什么样的交互) 71 | 72 | 最后也是一个算法,写出所有数组的子序列 73 | 74 | 二面面试官是这个组的Eva,跟我讲了现在这个组的发展情况和快手现在的情况,由于快手成长很快,所以不能仅仅依靠一个APP,还需要在其他方面进行一些尝试,而这个组的任务就是在一些方面做一些尝试,大概就是这个样子。 75 | 76 | **三面** 77 | 78 | HR上来很亲切,问了我一些面试的情况,难不难之类的,然后又聊了聊我大学和研究生情况,我只想说我“too simple , too naive “,大概了解我后,只跟我聊我的不足,以此来压低我期望的薪水。说了一下薪资期望,加了微信,让我回去等待,说发offer大概是2周时间,因为需要走各种审批流程,让我不要着急。 79 | 80 | 快手是一个很年轻的公司,技术还是需要一定的积累,希望不要像小咖秀一样昙花一现。 81 | 82 | **美团外卖** 83 | 84 | **一面** 85 | 86 | 1、简历上写的项目问了一遍,然后开始问知识点。 87 | 88 | 2、volley的源代码,在图片缓存部分讨论了挺长时间,http中缓存机制, 89 | 90 | 3、视觉控制器的生命周期 91 | 92 | 4、数据库 93 | 94 | 5、多线程(NSTread、NSOPeration、GCDA+block) 95 | 96 | 6、http协议get post的区别 97 | 98 | 7、手机适配一些方案 99 | 100 | 8、真机调试、项目上线注意事项 101 | 102 | 9、静态方法是否能被重写 103 | 104 | 这些大概聊了1个半小时,开始的时候还有些紧张,慢慢聊开了,就好多了,面试官的语速有点快,老是需要面试官重复一遍,我也不经意间语速也变快了,不过能看出来面试官还是很厉害的。 105 | 106 | **二面** 107 | 108 | 2次握手和3次挥手的原因,以及为什么需要这样做。 109 | 110 | 1、id和nill代表什么(nill和NULL的区别) 111 | 112 | 2、向一个nill对象发送消息会发生什么? 113 | 114 | 3、进程与线程区别 115 | 116 | 5、写一个NSString类的实现 117 | 118 | 6、http中的同步和异步 119 | 120 | 聊了一些项目上做的东西,问了问职业规划 121 | 122 | 由于二面面试官不是做iOS,本来面试我的人临时开会去了,所以这一轮面试没怎么问iOS相关知识,不过二面面试官一直是微笑,所以这一轮很轻松,更像是一起讨论问题。 123 | 124 | 面试完已经是下午4:30了,由于面试当天是星期五,而周五美团的会议比较多,所以等了会,二面面试官说三面面试官在开会,面试另约时间,我还是说这次一次面试完吧,这一等就等了2个半小时,期间hr跟我说三面面试官是个大牛。 125 | 126 | **三面** 127 | 128 | 我认为iOS做的优秀的几个地方,然后又根据我说的问了问比较深入问题。 129 | 130 | 1、iOS是如何进行资源管理的。 131 | 132 | 2、Python比较重要的几个特性 133 | 134 | 3、网络五层结构,每一层协议,由于我网络不是很好,还问了一些其他的问题(例如MAC地址和ip地址的区别等)。 135 | 136 | 为什么离开原来公司,以及职业规划,然后因为面试完大概就晚上8点了,就先让我回去,下周让HR跟我联系,我想这是应该通过面试了吧。 137 | 138 | 美团技术还是很厉害的,从面试官的水平就可以看出来,尤其是外卖核心部门,办公环境是不错,但是感觉就是有点乱,不知道是不是因为今天面试的人很多,基本上一直有很多人来回走动,有一些嘈杂。 139 | 140 | **阿里** 141 | 142 | 梦寐以求的阿里终于找我来面试了,没办法谁让马云爸爸太厉害,我投递的是杭州的天猫,是做虚拟现实的小组(刚听到这个名字感觉和自己不太相符),这是我到面试完后,才知道的,面试官也跟我说iOS上的需求可能不会很多,更多的是AR技术在iOS上的应用,包含OpenGL等技术。 143 | 144 | **一面** 145 | 146 | 询问了我博客上写的一些东西,从项目立意谈起,到设计,再到详细的技术实现,可谓是面面俱到,由于自己写的博客还是比较熟悉,回答的还不错。 147 | 148 | 1、GLSurfaceView的相关知识,OpenGL,Shader,绘制流程。 149 | 150 | 2、询问当前做的项目,以及到具体的实现和优化。 151 | 152 | 3、多进程间的通讯,Binder机制。 153 | 154 | 4、询问看过哪些框架源码,EventBus,Volley讲了一下。 155 | 156 | 大概聊了一个小时左右,聊得还可以,基本上都回答上了,中间给了我很多建议,不懂的地方,也会仔细跟我讲解一番,其实有一半的时间都是跟我聊产品,为什么这个产品好,怎样做才能迎合市场,然后怎么设计整个产品等,感觉跟我现在水平不是一个层次的,果然,第二天就给我发了一封邮件,说我现在暂时不太合适投递的岗位。 157 | 158 | **面试结果** 159 | 160 | 除了阿里淘宝外,其他的公司基本都拿到offer。 161 | 162 | **最后总结** 163 | 164 | 自己对于互联网有一些小小的见解:随着资本的冷却,整个互联网市场也逐渐的冷静下来。iOS应用开发从一开始能说几个四大组件的名词,能随便写个监听事件,就能拿到高达上万的月薪的时代了。 165 | 166 | 归根到底并不是工作难找了,而是iOS应用开发工程师这个职位已经趋于正常,再也不是没什么技术也能拿高工资的香饽饽。当然这个也不是绝对的,对于中高级的开发人员来说,市场还是比较缺少的,尤其是知名企业对于招聘员工来说,不仅要求有过硬的技术,还要求有高素质,好的教育背景等等。 167 | 168 | 总的来说,高工资可以给你,但是前提条件是你要足够优秀,或者说让面试官觉得你很优秀。 169 | 170 | **笔试** 171 | 172 | 对于社招的同学来说,基本上不需要笔试,但是也有公司是需要的,例如 今日头条和网易都有笔试。笔试都是比较基础的一些知识,Python、iOS等方面的,一般不会有网络,计算机等方面的笔试,一般情况下大家都能答出来。 173 | 174 | **一、二面** 175 | 176 | 近一段时间的面试经历来说,一、二面的问题没有什么很大的区别(公司基本上都有3面技术面,但是也有例外,我在美团就是2面技术面。),基本上都是一线开发人员。主要考察你是否有牢固的基础知识和是否在平常开发中能熟练使用。 177 | 178 | 是否能讲解清楚你所做的项目,以及使用到的相关知识。 179 | 180 | 1、iOS基础知识 181 | 182 | 2、Python基础知识,大概是多线程,线程安全,集合类,JVM,类相关知识等。 183 | 184 | 3、iOS一些源码的阅读 185 | 186 | 4、优秀的第三方框架源码阅读 187 | 188 | **三、四面** 189 | 190 | 一般公司都是三轮技术面,但是也有四轮技术面的,不过不多。很多公司基本上每一轮面试官都会记录他所询问的问题,以便给下一轮面试官作参考,还有就是避免对同一个知识点多次询问。所以到了这轮面试,基本上不会再询问比较基础的知识。 191 | 192 | **会从两个方面考察,** 193 | 194 | * 1、广度:比较新的技术(多线程,插件化等),http协议,数据库,iOS(一般不会询问之前面试官问过的问题)。 195 | * 2、深度:一般会通过1或2个问题来考察,例如:项目中的贡献,所做的优化。设计能力,基本上不多,这个要看面试的岗位,因为我这里面试的只是高级开发,并不是架构。 196 | 197 | 工作中的亮点和突出。 198 | 199 | **HR面** 200 | 201 | 基本上到了这轮,你就算通过面试了。hr会询问一些你的经历,最主要的还是和你商定薪资待遇。在这轮,大家应该要对自己的薪资水平有一个大体的了解,一般都是在原来的工资基础上增长20%~30%的样子,当然,如果你在面试过程中表现非常优秀,也可以不受这个限制。当然如果公司诚心要你,就算你要的工资过高,hr也会委婉的告诉你,不会直接把你pass。 202 | 203 | > 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要这是一个我的iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长! 204 | 205 | 文章来源于网络,如有侵权,请联系小编删除。 206 | 207 | 208 | -------------------------------------------------------------------------------- /iOS面试合集/金三银四涨薪季,是否有把握住?.md: -------------------------------------------------------------------------------- 1 | 小编寻来大厂最喜欢在面试问到的问题,今天在这里分享给大家[**(下载地址)**](https://jq.qq.com/?_wv=1027&k=Aj34ccQE)! 2 | 3 | ##《BAT面试宝典》38页 4 | ###内容管理 5 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165824_f4dc9ae0_9027123.png "三1.png") 6 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165842_c940e82c_9027123.png "三2.png") 7 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165851_23983691_9027123.png "三3.png") 8 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165859_c52d1b18_9027123.png "三4.png") 9 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165907_4c3d6558_9027123.png "三5.png") 10 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165918_1cfb257c_9027123.png "三6.png") 11 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165927_164a557a_9027123.png "三7.png") 12 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165934_97e19670_9027123.png "三8.png") 13 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165942_caba82af_9027123.png "三9.png") 14 | ##《BAT面试宝典》59页 15 | ###Swift 16 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165951_6a9935be_9027123.png "三10.png") 17 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/165959_c6e878bc_9027123.png "三11.png") 18 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170007_b6c8bbbb_9027123.png "三12.png") 19 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170017_2eeed09d_9027123.png "三13.png") 20 | ##《BAT面试宝典》101页 21 | ###数据存储 22 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170029_0356e8a6_9027123.png "三14.png") 23 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170037_194cddd9_9027123.png "三15.png") 24 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170044_ba55e911_9027123.png "三16.png") 25 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170052_dfe1eac1_9027123.png "三17.png") 26 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170059_79f0abad_9027123.png "三18.png") 27 | 小编就不在这里全部列举出来了,可以在前面的看到,文档里面包括了目前比较受欢迎的所有面试题!有需要获取的话,点击:**[领取](https://jq.qq.com/?_wv=1027&k=gSBPCRDB)**! 28 | ![输入图片说明](https://images.gitee.com/uploads/images/2021/0601/170112_a31ce794_9027123.png "三19.png") 29 | -------------------------------------------------------------------------------- /iOS面试合集/面试题 拓展:常用框架和第三方框架.md: -------------------------------------------------------------------------------- 1 | ## **类变量的@protected,@private,@public,@package声明各有什么含义** 2 | **@private**:作用范围只能在自身类 3 | **@protected**:作用范围在自身类和继承自己的子类(默认) 4 | **@public**:作用范围最大,可以在任何地方被访问 5 | **@package**:这个类型最常用于框架类的实例变量,同一个包内能用,跨包就不能访问。 6 | 7 | **对于框架类的拓展:** 8 | 9 | ## **iOS常用基础框架** 10 | 11 | **Foundation**:提供OC基础类(例如NSObject)、基本数据类型等 12 | **UIKit**:创建和管理应用程序的用户界面 13 | **QuartzCore**:提供动画特效以及通过硬件进行渲染的能力 14 | **CoreGraphics**:提供2D绘制的基于C的Api 15 | **SystemConfiguration**:检测当前网络是否可用和硬件设备状态 16 | **AVFoundation**:提供音频录制和回放的底层API,同时也负责管理音频硬件 17 | **CFNetWork**:访问和配置网络,像HTTP、FTP和Bonjour Services 18 | **CoreFoundation**:提供抽象的常用数据类型,比如Unicode strings、XML、URL等 19 | **CoreLocation**:使用GPS和WIFI获取位置信息 20 | 21 | **GameKit**:为游戏提供网络功能:点对点互联和游戏中的语音交流 22 | **AddressBook**:提供访问用户联系人信息的功能 23 | **AddressBookUI**:提供一个用户界面,用于显示存储在地址薄中的联系人信息 24 | **AudioToolBox**:提供音频录制和回放的底层API,同时也负责管理音频硬件 25 | **AudioUnit**:提供一个接口,让我们的应用程序可以对音频进行处理 26 | **MapKit**:为应用程序提供内嵌地图的接口 27 | **MediaPlayer**:提供播放视频和音频的功能 28 | **MessageUI**:提供视图控制接口用以处理M-mail和短信 29 | **OpenGLES**:提供动画特效以及通过硬件进行渲染的能力 30 | **StoreKit**:为应用程序提供在程序运行中消费的支持 31 | 32 | ## 常用的第三方开源框架: 33 | 34 | 1.**JSON** json编码解码 35 | 2.**GTMBase64** base64编码解码 36 | 3.**TouchXML** 解析 37 | 4.**SFHFKeychainUtils** 安全保存用户密码到keychain中 38 | 5.**MBProgressHUD**很棒的一个加载等待特效框架 39 | 6.**ASIHTTPRequest** 等相关协议封装 40 | 7.**EGORefreshTableHeaderView** 下拉刷新代码 41 | 8.**AsyncImageView** 异步加载图片并缓存代码 42 | 9.类似**setting**的竖立也分栏程序 43 | 10.**MBProgressHUD**——进展指示符库 44 | 11.**Flurry**——详尽的使用统计 45 | 12.**CorePlot**——2D图形绘图仪 46 | 13.**GData client**——iPhone上所有Google相关服务的类库 47 | 14.**SDWebImage**——简化网络图片处理 48 | 15.**RegexKitLite**——正则表达式支持 49 | 50 | >最后推荐个我的**iOS交流群:**[642363427](https://link.zhihu.com/?target=https%3A//jq.qq.com/%3F_wv%3D1027%26k%3D15vUEWzp) 有一个共同的圈子很重要,结识人脉!里面都是iOS开发,全栈发展,欢迎入驻,共同进步!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!) 51 | 52 | --------------------------------------------------------------------------------