├── Interview.zip ├── 屏幕快照 2016-08-24 下午5.42.00.png ├── 屏幕快照 2017-03-20 下午9.18.54.png ├── README.md ├── .gitignore ├── 读书总结(转).md ├── LICENSE ├── blog_reading.md └── 小知识点.md /Interview.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hulinSun/iOS-Collection/HEAD/Interview.zip -------------------------------------------------------------------------------- /屏幕快照 2016-08-24 下午5.42.00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hulinSun/iOS-Collection/HEAD/屏幕快照 2016-08-24 下午5.42.00.png -------------------------------------------------------------------------------- /屏幕快照 2017-03-20 下午9.18.54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hulinSun/iOS-Collection/HEAD/屏幕快照 2017-03-20 下午9.18.54.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS-Collection 2 | 个人对于iOS的总结 3 | 4 | #### [小知识点](https://github.com/hulinSun/iOS-Collection/blob/master/%E5%B0%8F%E7%9F%A5%E8%AF%86%E7%82%B9.md) 5 | #### [读书总结](https://github.com/hulinSun/iOS-Collection/blob/master/%E8%AF%BB%E4%B9%A6%E6%80%BB%E7%BB%93%EF%BC%88%E8%BD%AC%EF%BC%89.md) 6 | #### [博客知识总结](https://github.com/hulinSun/iOS-Collection/blob/master/blog_reading.md) 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /读书总结(转).md: -------------------------------------------------------------------------------- 1 | # 熟悉 Objective-C 2 | 3 | ## 1. OC 的起源 4 | 5 | OC 的方法(本质上讲是消息)在运行时决定。使用函数调用的语言,由编译器决定。如果涉及多态,则用到虚函数表。 6 | 7 | ## 2. 少在头文件中引用其他文件 8 | 9 | 1. 两个头文件互相引用会导致编译错误 10 | 2. 引用协议,超类时,无法使用前向声明(`@class`),只能引用头文件 11 | 3. 协议一般放在单独的文件中,委托协议除外 12 | 13 | ## 3. 多用字面量语法 14 | 15 | 1. 向数组中插入 `nil` 时,如果使用字面量会直接报错,如果是使用初始化方法,则 `nil` 以后的对象被忽略 16 | 2. 字典也是同理 17 | 18 | ## 4. 多用常量,少用 #define 19 | 20 | 1. 不在头文件里面声明预处理命令,防止被别的文件引用,`static const` 也不能用 21 | 2. 在 `.m` 文件里面声明的变量,需要加上 static,不然会产生外部符号,重复的外部符号会导致编译错误 22 | 3. 用 `static const` 定义只在类内部可见的常量 23 | 4. 用 `extern` 定义全局常量,并使用类名作为前缀 24 | 5. 头文件中使用 `static const` 会在每个编译单元中出现一个字符串对象,而用 `extern` 定义的对象放在全局符号表中。参考链接:[static const Vs extern const](http://stackoverflow.com/questions/23652665/static-const-vs-extern-const) 25 | 6. 使用 `FOUNDATION_EXPORT` 比 `extern` 更好,参考链接:[“FOUNDATION_EXPORT” vs “extern”](http://stackoverflow.com/questions/10953221/foundation-export-vs-extern) 26 | 27 | ## 5. 用枚举表示状态和选项 28 | 29 | 1. 按位或操作来组合的枚举使用 `NS_OPTIONS` 宏定义,不需要相互组合的用 `NS_ENUM ` 来定义。 30 | 2. 尽可能指定枚举变量的类型 31 | 32 | # 对象、消息、运行期 33 | 34 | ## 6. 理解“属性”这一概念 35 | 36 | 1. 如果直接使用实例变量,系统会根据偏移量来确定它们的位置。如果后来新增了实例变量,会导致重新编译。 37 | 2. `@property` 的作用是合成存取方法 38 | 3. `@synthesize` 的作用是指定实例变量的名字,目前默认会执行 `@synthesize xxx = _xxx;` 39 | 4. `@dynamic` 的作用是不要创建实例变量,也不要创建存取方法 40 | 41 | ## 7. 在对象内部尽量直接访问实例变量 42 | 43 | 1. 直接访问实例变量不会经过方法派发,不会触发内存管理语义,不会触发 KVO,无法调试(加断点)。 44 | 2. 可以考虑通过直接读取实例变量来加快读取速度 45 | 3. 初始化方法中不要调用 setter,这是因为子类有可能重写设置方法。参见 Demo 的 `EOCSmithPerson.m` 文件。 46 | 4. 如果属性是惰性初始化的,必须通过 getter 来读取。 47 | 48 | ## 8. 理解对象等同性 49 | 50 | 1. 应该保证相等的对象具有相等的 `hash` 值,但即使 `hash` 值不同,也不影响判断对象等同性。但是考虑到对象加入集合中的情况,我们总是应该为相同的对象提供相同的哈希值。 51 | 2. 要么确保对象的哈希值不依赖内部可变的状态,要么确保依赖的状态不会改变。 52 | 53 | ## 9. 以“类族模式”隐藏实现细节 54 | 55 | 1. 用类族模式可以将实现细节隐藏,返回基类的对象,并且通过多态完成任务。这其实是一种工厂模式。 56 | 2. 注意此时调用 `isMemberOf: superClass` 的返回结果是 `NO`,因为对象实际上是子类的实例。调用 `isKindOf: superClass` 的返回结果则是 `YES`。 57 | 3. `[arrayInstance class] = [NSArray class]` 的返回结果总是 `NO`,因为左边一定是 `NSArray` 的子类。 58 | 59 | ## 10. 使用关联对象 60 | 61 | 1. 使用静态全局变量作为键,设置好合适的内存管理语义 62 | 63 | ## 11. objc_msgSend 64 | 65 | 1. 大部分消息调用使用了 `objc_msgSend` 函数。对于某些边界情况,运行时环境可能使用一些其他的函数来处理:`objc_msgSend_stret`:如果返回的是结构体,调用这个函数。`objc_msgSend_fpret`:如果返回的是浮点数,调用这个函数。`objc_msgSendSuper`:如果给超类发消息,调用这个函数。 66 | 67 | ## 12. 消息转发 68 | 69 | 1. 分为三个步骤,动态方法解析,快速转发和完整转发 70 | 2. 实现一个“字典”对象:TBD 71 | 72 | ## 14. 理解“类对象” 73 | 74 | 1. 运行期检视对象类型被称为“内省” 75 | 2. 因为类对象是单例,所以可以直接通过 `==` 判断类对象是否相等。但应该避免这么做,推荐使用类型信息查询方法(`isMemberOf` 和 `isKindOf`)。TBD:举例说明 76 | 77 | # 接口与 API 设计 78 | 79 | ## 15. 用前缀避免命名空间冲突 80 | 81 | 1. 前缀至少是三个字母,两个字母的前缀由 Apple 保留 82 | 2. 全局函数和变量需要注意避免冲突 83 | 3. 在自己开发的库中,为用到的第三方库添加前缀 84 | 85 | ## 16. 全能初始化方法 86 | 87 | 1. 设置一个全能初始化方法,别的初始化方法都调用它 88 | 89 | ## 17. 实现 description 方法 90 | 91 | 1. 实现 `description` 方法,改变 `NSLog` 时的输出结果 92 | 2. 实现 `debugDescription` 方法,改变 `po` 时的输出结果 93 | 94 | ## 18. 尽量使用不可变对象 95 | 96 | 1. 不要把可变的 collection 作为属性公开,应该公开一个不可变的 collection 然后提供相关的修改方法 97 | 98 | ## 19. 命名方式 99 | 100 | 1. 如果返回值是新建的,首个词是返回值类型 101 | 2. 不要使用 `str` 这种简称,使用 `string` 102 | 3. `get` 前缀仅在由“输出参数”保存返回值的方法中使用 103 | 104 | ## 21. 错误类型 105 | 106 | 1. 严重的错误可以直接抛出异常(比如禁止调用父类的方法,需要子类重写) 107 | 2. 一般的错误使用 `NSError`,指定 domain(全局常量)、错误码(枚举类型)和用户细信息。 108 | 109 | ## 22. NSCopying 协议 110 | 111 | 1. 实现 `NSCopying` 协议,重写 `copyWithZone` 方法。 112 | 2. 容器的拷贝总是浅拷贝,`copy` 与 `mutableCopy` 的区别只是返回对象是否可变。 113 | 114 | # 协议与分类 115 | 116 | ## 23. 使用委托 117 | 118 | 1. 调用 delegate 中的方法时,总是应该传入发起委托的对象。这样代理对象可以判断不同的委托者。 119 | 120 | ## 24. 使用分类 121 | 122 | 1. 可以把所有的私有方法都归入名叫 Private 的分类中 123 | 124 | ## 25. 为第三方分类添加前缀 125 | 126 | 1. 分类中定义的方法可以多次互相覆盖,以最后一个分类为准 127 | 2. 向第三方类中添加分类时,为分类名称和方法名称添加前缀 128 | 129 | ## 26. 分类中不要声明属性 130 | 131 | 1. 分类中无法创建实例变量 132 | 2. 可以定义存取方法,但是不要定义属性 133 | 3. 按钮的分类。要提供getter 方法,虽然不起作用, 134 | 135 | ## 未完. 136 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /blog_reading.md: -------------------------------------------------------------------------------- 1 | #博客总结 2 | #####git fork 流 3 | 4 | ``` 5 | git remote add upstream git@123.57.152.76:teamfrontend/dreammentor.git 6 | 7 | git fetch upstream 8 | 9 | git merge upstream/master 10 | fork 关联元地址 11 | ``` 12 | 13 | 需求:B要加入A的项目,不论是作为B的初始项目进行二次开发还是成为A项目的一员加入一起开发,步骤如下: 14 | 15 | 1.B首先要fork一个。' 16 | B首先到A的github上,也就是此项目的位置:https://github.com/A/durit,然后单击fork,然后你(B)的github上就出现了一个fork,位置是:https://github.com/B/durit 17 | 18 | 2.B把自己的fork克隆到本地。 19 | $ git clone https://github.com/B/durit 20 | (当你clone到本地,会有一个默认的远程名叫"origin",它指向了fork on github,也就是B上的fork,而不是指向A) 21 | 22 | 3.现在你是主人,为了保持与A的durit的联系,你需要给A的durit起个名,供你来驱使。 23 | $ cd durit 24 | $ git remote add upstream https://github.com/A/durit 25 | (现在改名为upstream,这名随意,现在你(B)管A的durit叫upstream,以后B就用upstream来和A的durit联系了) 26 | 27 | 4.获取A上的更新(但不会修改你的文件)。 28 | $ git fetch upstream 29 | (这不,现在B就用upstream来联系A了) 30 | 31 | 5.合并拉取的数据 32 | $ git merge upstream/master 33 | (又联系了一次,upstream/master,前者是你要合并的数据,后者是你要合并到的数据(在这里就是B本地的durit了)) 34 | 35 | 6.在B修改了本地部分内容后,把本地的更改推送到B的远程github上。 36 | $ git add 修改过的文件 37 | $ git commit -m "注释" 38 | $ git push origin master 39 | (目前为止,B上的github就跟新了) 40 | 41 | 7.然后B还想让修改过的内容也推送到A上,这就要发起pull request了。 42 | 打开B的github,也就是https://github.com/B/durit 43 | 点击Pull Requests 44 | 单击new pull request 45 | 单击create pull request 46 | 输入title和你更改的内容 47 | 然后单击send pull request 48 | 这样B就完成了工作,然后就等着主人A来操作了。 49 | 50 | 8.在B想要更新A的github上到内容时,结果冲突,因为B和A同时修改了文件,比如说是README.ME,该这样做: 51 | $ git status(查看冲突文件) 52 | 找到冲突文件(README.MD)后,打开并修改,解决冲突后 53 | $ git add README.MD 54 | $ git commit -m "解决了冲突文件README.MD" 55 | 现在冲突解决了,可以更新A的内容了,也就是上面第4步和第5步 56 | 57 | ##### 异步渲染 58 | 59 | ``` 60 | YYAsyncLayer:继承自CALayer,绘制、创建绘制线程的部分都在这个类。 61 | YYTransaction:用于创建RunloopObserver监听MainRunloop的空闲时间,并将YYTranaction对象存放到集合中。 62 | YYSentinel:提供获取当前值的value(只读)属性,以及- (int32_t)increase自增加的方法返回一个新的value值,用于判断异步绘制任务是否被取消的工具 63 | 64 | kCFRunLoopBeforeWaiting:Runloop将要进入休眠。 65 | kCFRunLoopExit:即将退出本次Runloop。 66 | 在监听这个mainrunloop 的回调里面执行刷新方法 [transaction.target performSelector:transaction.selector]; 67 | 68 | 对外接口YYAsyncLayerDelegate代理中提供- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask方法用于回调绘制的代码,以及是否异步绘制的BOOl类型属性displaysAsynchronously,同时重写CALayer的display 方法来调用绘制的方法- (void)_displayAsync:(BOOL)async。 69 | ``` 70 | 71 | #####runtime 72 | * 替换系统自带的方法 73 | * 字典转模型 74 | * 一键归档,解档 75 | * 万能控制器跳转 76 | * 分类中设置属性 77 | 78 | 79 | ``` 80 | / 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数 81 | 82 | NSDictionary *userInfo = @{@"class":@"HSFeedsViewController",@"property":@{@"ID": @"123", @"type":@"12"}}; 83 | 84 | - (void)push:(NSDictionary *)params 85 | { 86 | // 类名 87 | NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]]; 88 | const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding]; 89 | // 从一个字串返回一个类 90 | Class newClass = objc_getClass(className); 91 | if (!newClass) 92 | { 93 | // 创建一个类 94 | Class superClass = [NSObject class]; 95 | newClass = objc_allocateClassPair(superClass, className, 0); 96 | // 注册你创建的这个类 97 | objc_registerClassPair(newClass); 98 | } 99 | // 创建对象 100 | id instance = [[newClass alloc] init]; 101 | // 对该对象赋值属性 102 | NSDictionary * propertys = params[@"property"]; 103 | [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { 104 | // 检测这个对象是否存在该属性 105 | if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) { 106 | // 利用kvc赋值 107 | [instance setValue:obj forKey:key]; 108 | } 109 | }]; 110 | // 获取导航控制器 111 | UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController; 112 | UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex]; 113 | // 跳转到对应的控制器 114 | [pushClassStance pushViewController:instance animated:YES]; 115 | } 116 | 117 | // 检测对象是否存在该属性 118 | - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName 119 | { 120 |     unsigned int outCount, i; 121 |     // 获取对象里的属性列表 122 |     objc_property_t * properties = class_copyPropertyList([instance 123 |                                                            class], &outCount); 124 |     for (i = 0; i < outCount; i++) { 125 |         objc_property_t property =properties[i]; 126 |         //  属性名转成字符串 127 |         NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; 128 |         // 判断该属性是否存在 129 |         if ([propertyName isEqualToString:verifyPropertyName]) { 130 |             free(properties); 131 |             return YES; 132 |         } 133 |     } 134 |     free(properties); 135 |     return NO; 136 | } 137 | ``` 138 | 139 | #####NSString copy strong 140 | 141 | **不可变字符串 strong copy 之后都是指向同一份内存地址(做了浅拷贝),但是可变字符串,做的是深拷贝。这时候改变string strong修饰的字符串也会跟着改变,而深拷贝的对象是新的对象,不会改变,变不变取决于需求,但是一般情况下都不希望别人改变,所以用copy** 142 | 143 | ``` 144 | NSString *string = [NSString stringWithFormat:@"abc"]; 145 | self.strongString = string; 146 | self.copyedString = string; 147 | 148 | NSLog(@"origin string: %p, %p", string, &string); 149 | NSLog(@"strong string: %p, %p", strongString, &strongString); 150 | NSLog(@"copy string: %p, %p", copyedString, ©edString); 151 | 152 | origin string: 0x7fe441592e20, 0x7fff57519a48 153 | strong string: 0x7fe441592e20, 0x7fe44159e1f8 154 | copy string: 0x7fe441592e20, 0x7fe44159e200 155 | 156 | //若果将string 改成mutablestring 157 | NSMutableString *string = [NSMutableString stringWithFormat:@"abc"]; 158 | 159 | origin string: 0x7ff5f2e33c90, 0x7fff59937a48 160 | strong string: 0x7ff5f2e33c90, 0x7ff5f2e2aec8 161 | copy string: 0x7ff5f2e2aee0, 0x7ff5f2e2aed0 162 | ``` 163 | 164 | #####GPUImage 165 | 166 | ``` 167 | // Base classes 168 | #import "GPUImageOpenGLESContext.h" 169 | #import "GPUImageOutput.h" 170 | #import "GPUImageView.h" 171 | #import "GPUImageVideoCamera.h" 172 | #import "GPUImageStillCamera.h" 173 | #import "GPUImageMovie.h" 174 | #import "GPUImagePicture.h" 175 | #import "GPUImageRawDataInput.h" 176 | #import "GPUImageRawDataOutput.h" 177 | #import "GPUImageMovieWriter.h" 178 | #import "GPUImageFilterPipeline.h" 179 | #import "GPUImageTextureOutput.h" 180 | #import "GPUImageFilterGroup.h" 181 | #import "GPUImageTextureInput.h" 182 | #import "GPUImageUIElement.h" 183 | #import "GPUImageBuffer.h" 184 | 185 | // Filters 186 | #import "GPUImageFilter.h" 187 | #import "GPUImageTwoInputFilter.h" 188 | 189 | 190 | #pragma mark - 调整颜色 Handle Color 191 | 192 | #import "GPUImageBrightnessFilter.h" //亮度 193 | #import "GPUImageExposureFilter.h" //曝光 194 | #import "GPUImageContrastFilter.h" //对比度 195 | #import "GPUImageSaturationFilter.h" //饱和度 196 | #import "GPUImageGammaFilter.h" //伽马线 197 | #import "GPUImageColorInvertFilter.h" //反色 198 | #import "GPUImageSepiaFilter.h" //褐色(怀旧) 199 | #import "GPUImageLevelsFilter.h" //色阶 200 | #import "GPUImageGrayscaleFilter.h" //灰度 201 | #import "GPUImageHistogramFilter.h" //色彩直方图,显示在图片上 202 | #import "GPUImageHistogramGenerator.h" //色彩直方图 203 | #import "GPUImageRGBFilter.h" //RGB 204 | #import "GPUImageToneCurveFilter.h" //色调曲线 205 | #import "GPUImageMonochromeFilter.h" //单色 206 | #import "GPUImageOpacityFilter.h" //不透明度 207 | #import "GPUImageHighlightShadowFilter.h" //提亮阴影 208 | #import "GPUImageFalseColorFilter.h" //色彩替换(替换亮部和暗部色彩) 209 | #import "GPUImageHueFilter.h" //色度 210 | #import "GPUImageChromaKeyFilter.h" //色度键 211 | #import "GPUImageWhiteBalanceFilter.h" //白平横 212 | #import "GPUImageAverageColor.h" //像素平均色值 213 | #import "GPUImageSolidColorGenerator.h" //纯色 214 | #import "GPUImageLuminosity.h" //亮度平均 215 | #import "GPUImageAverageLuminanceThresholdFilter.h" //像素色值亮度平均,图像黑白(有类似漫画效果) 216 | 217 | #import "GPUImageLookupFilter.h" //lookup 色彩调整 218 | #import "GPUImageAmatorkaFilter.h" //Amatorka lookup 219 | #import "GPUImageMissEtikateFilter.h" //MissEtikate lookup 220 | #import "GPUImageSoftEleganceFilter.h" //SoftElegance lookup 221 | 222 | 223 | 224 | 225 | #pragma mark - 图像处理 Handle Image 226 | 227 | #import "GPUImageCrosshairGenerator.h" //十字 228 | #import "GPUImageLineGenerator.h" //线条 229 | 230 | #import "GPUImageTransformFilter.h" //形状变化 231 | #import "GPUImageCropFilter.h" //剪裁 232 | #import "GPUImageSharpenFilter.h" //锐化 233 | #import "GPUImageUnsharpMaskFilter.h" //反遮罩锐化 234 | 235 | #import "GPUImageFastBlurFilter.h" //模糊 236 | #import "GPUImageGaussianBlurFilter.h" //高斯模糊 237 | #import "GPUImageGaussianSelectiveBlurFilter.h" //高斯模糊,选择部分清晰 238 | #import "GPUImageBoxBlurFilter.h" //盒状模糊 239 | #import "GPUImageTiltShiftFilter.h" //条纹模糊,中间清晰,上下两端模糊 240 | #import "GPUImageMedianFilter.h" //中间值,有种稍微模糊边缘的效果 241 | #import "GPUImageBilateralFilter.h" //双边模糊 242 | #import "GPUImageErosionFilter.h" //侵蚀边缘模糊,变黑白 243 | #import "GPUImageRGBErosionFilter.h" //RGB侵蚀边缘模糊,有色彩 244 | #import "GPUImageDilationFilter.h" //扩展边缘模糊,变黑白 245 | #import "GPUImageRGBDilationFilter.h" //RGB扩展边缘模糊,有色彩 246 | #import "GPUImageOpeningFilter.h" //黑白色调模糊 247 | #import "GPUImageRGBOpeningFilter.h" //彩色模糊 248 | #import "GPUImageClosingFilter.h" //黑白色调模糊,暗色会被提亮 249 | #import "GPUImageRGBClosingFilter.h" //彩色模糊,暗色会被提亮 250 | #import "GPUImageLanczosResamplingFilter.h" //Lanczos重取样,模糊效果 251 | #import "GPUImageNonMaximumSuppressionFilter.h" //非最大抑制,只显示亮度最高的像素,其他为黑 252 | #import "GPUImageThresholdedNonMaximumSuppressionFilter.h" //与上相比,像素丢失更多 253 | 254 | #import "GPUImageSobelEdgeDetectionFilter.h" //Sobel边缘检测算法(白边,黑内容,有点漫画的反色效果) 255 | #import "GPUImageCannyEdgeDetectionFilter.h" //Canny边缘检测算法(比上更强烈的黑白对比度) 256 | #import "GPUImageThresholdEdgeDetectionFilter.h" //阈值边缘检测(效果与上差别不大) 257 | #import "GPUImagePrewittEdgeDetectionFilter.h" //普瑞维特(Prewitt)边缘检测(效果与Sobel差不多,貌似更平滑) 258 | #import "GPUImageXYDerivativeFilter.h" //XYDerivative边缘检测,画面以蓝色为主,绿色为边缘,带彩色 259 | #import "GPUImageHarrisCornerDetectionFilter.h" //Harris角点检测,会有绿色小十字显示在图片角点处 260 | #import "GPUImageNobleCornerDetectionFilter.h" //Noble角点检测,检测点更多 261 | #import "GPUImageShiTomasiFeatureDetectionFilter.h" //ShiTomasi角点检测,与上差别不大 262 | #import "GPUImageMotionDetector.h" //动作检测 263 | #import "GPUImageHoughTransformLineDetector.h" //线条检测 264 | #import "GPUImageParallelCoordinateLineTransformFilter.h" //平行线检测 265 | 266 | #import "GPUImageLocalBinaryPatternFilter.h" //图像黑白化,并有大量噪点 267 | 268 | #import "GPUImageLowPassFilter.h" //用于图像加亮 269 | #import "GPUImageHighPassFilter.h" //图像低于某值时显示为黑 270 | 271 | 272 | #pragma mark - 视觉效果 Visual Effect 273 | 274 | #import "GPUImageSketchFilter.h" //素描 275 | #import "GPUImageThresholdSketchFilter.h" //阀值素描,形成有噪点的素描 276 | #import "GPUImageToonFilter.h" //卡通效果(黑色粗线描边) 277 | #import "GPUImageSmoothToonFilter.h" //相比上面的效果更细腻,上面是粗旷的画风 278 | #import "GPUImageKuwaharaFilter.h" //桑原(Kuwahara)滤波,水粉画的模糊效果;处理时间比较长,慎用 279 | 280 | #import "GPUImageMosaicFilter.h" //黑白马赛克 281 | #import "GPUImagePixellateFilter.h" //像素化 282 | #import "GPUImagePolarPixellateFilter.h" //同心圆像素化 283 | #import "GPUImageCrosshatchFilter.h" //交叉线阴影,形成黑白网状画面 284 | #import "GPUImageColorPackingFilter.h" //色彩丢失,模糊(类似监控摄像效果) 285 | 286 | #import "GPUImageVignetteFilter.h" //晕影,形成黑色圆形边缘,突出中间图像的效果 287 | #import "GPUImageSwirlFilter.h" //漩涡,中间形成卷曲的画面 288 | #import "GPUImageBulgeDistortionFilter.h" //凸起失真,鱼眼效果 289 | #import "GPUImagePinchDistortionFilter.h" //收缩失真,凹面镜 290 | #import "GPUImageStretchDistortionFilter.h" //伸展失真,哈哈镜 291 | #import "GPUImageGlassSphereFilter.h" //水晶球效果 292 | #import "GPUImageSphereRefractionFilter.h" //球形折射,图形倒立 293 | 294 | #import "GPUImagePosterizeFilter.h" //色调分离,形成噪点效果 295 | #import "GPUImageCGAColorspaceFilter.h" //CGA色彩滤镜,形成黑、浅蓝、紫色块的画面 296 | #import "GPUImagePerlinNoiseFilter.h" //柏林噪点,花边噪点 297 | #import "GPUImage3x3ConvolutionFilter.h" //3x3卷积,高亮大色块变黑,加亮边缘、线条等 298 | #import "GPUImageEmbossFilter.h" //浮雕效果,带有点3d的感觉 299 | #import "GPUImagePolkaDotFilter.h" //像素圆点花样 300 | #import "GPUImageHalftoneFilter.h" //点染,图像黑白化,由黑点构成原图的大致图形 301 | 302 | 303 | #pragma mark - 混合模式 Blend 304 | 305 | #import "GPUImageMultiplyBlendFilter.h" //通常用于创建阴影和深度效果 306 | #import "GPUImageNormalBlendFilter.h" //正常 307 | #import "GPUImageAlphaBlendFilter.h" //透明混合,通常用于在背景上应用前景的透明度 308 | #import "GPUImageDissolveBlendFilter.h" //溶解 309 | #import "GPUImageOverlayBlendFilter.h" //叠加,通常用于创建阴影效果 310 | #import "GPUImageDarkenBlendFilter.h" //加深混合,通常用于重叠类型 311 | #import "GPUImageLightenBlendFilter.h" //减淡混合,通常用于重叠类型 312 | #import "GPUImageSourceOverBlendFilter.h" //源混合 313 | #import "GPUImageColorBurnBlendFilter.h" //色彩加深混合 314 | #import "GPUImageColorDodgeBlendFilter.h" //色彩减淡混合 315 | #import "GPUImageScreenBlendFilter.h" //屏幕包裹,通常用于创建亮点和镜头眩光 316 | #import "GPUImageExclusionBlendFilter.h" //排除混合 317 | #import "GPUImageDifferenceBlendFilter.h" //差异混合,通常用于创建更多变动的颜色 318 | #import "GPUImageSubtractBlendFilter.h" //差值混合,通常用于创建两个图像之间的动画变暗模糊效果 319 | #import "GPUImageHardLightBlendFilter.h" //强光混合,通常用于创建阴影效果 320 | #import "GPUImageSoftLightBlendFilter.h" //柔光混合 321 | #import "GPUImageChromaKeyBlendFilter.h" //色度键混合 322 | #import "GPUImageMaskFilter.h" //遮罩混合 323 | #import "GPUImageHazeFilter.h" //朦胧加暗 324 | #import "GPUImageLuminanceThresholdFilter.h" //亮度阈 325 | #import "GPUImageAdaptiveThresholdFilter.h" //自适应阈值 326 | #import "GPUImageAddBlendFilter.h" //通常用于创建两个图像之间的动画变亮模糊效果 327 | #import "GPUImageDivideBlendFilter.h" //通常用于创建两个图像之间的动画变暗模糊效果 328 | 329 | 330 | #pragma mark - 尚不清楚 331 | #import "GPUImageJFAVoroniFilter.h" 332 | #import "GPUImageVoroniConsumerFilter.h" 333 | ``` 334 | 335 | #####JSPatch 336 | 337 | **只需要在项目里引入极小的引擎文件,就可以使用 JavaScript 调用任何 Objective-C 的原生接口,替换任意 Objective-C 原生方法。目前主要用于下发 JS 脚本替换原生 Objective-C 代码,实时修复线上 bug。** 338 | 339 | 340 | **JSPatch 执行顺序问题?** 341 | 342 | **JSPatch所有动态替换的函数,都必须在JS执行完了之后,第二次再执行,才会全面以新替换的js代码进行工作。** 343 | 344 | JSPatch 会在当前项目的 bundle 里寻找 main.js 文件执行,效果与最终线上用户下载脚本执行一样,测试完后就可以准备上线这个脚本。 345 | 346 | 注意 +testScriptInBundle 不能与 +startWithAppKey: 一起调用,+testScriptInBundle 只用于本地测试,测试完毕后需要去除。 347 | 348 | **HOT FIX** 349 | 上传新的js脚本可以直接全量下发,也可以选择 开发预览 或 灰度或条件下发,也可以使用自定义 RSA key 对脚本进行加密签名。 350 | 351 | 上传完成后,对应版本的 APP 会请求下载这个脚本保存在本地,以后每次启动都会执行这个脚本。至此线上 bug 修复完成。 352 | 353 | **一般的补丁信息** 354 | 355 | ``` 356 | 357 | @interface YFPatchModel : MTLModel 358 | @property (copy, nonatomic) NSString * patchId; //!< 补丁id.用于唯一标记一个补丁.因为补丁,后期可能需要更新,删除,添加等操作. 359 | @property (copy, nonatomic) NSString * md5; //!< 文件的md5值,用于校验. 360 | @property (copy, nonatomic) NSString * url; //!< 文件的URL路径. 361 | @property (copy, nonatomic) NSString * ver; //!< 补丁对应的APP版本.不需要服务器返回,但需要本地存储此值.这个值在涉及到多个版本的补丁共存时,在应用升级时会很有价值. 362 | @property (assign, nonatomic) YFPatchModelStatus status; //!< 补丁状态.此状态值由本地管理和维护. 363 | 364 | @end 365 | ``` 366 | 367 | *时间顺序* 368 | 369 | ``` 370 | application:didFinishLaunchingWithOptions: 371 | JSPatch发起网络请求拉patch 372 | app的rootViewController触发ViewDidload运行完毕,依然是未修正的错误界面 373 | JSPatch网络请求拉取回来,执行JS 374 | JS已经执行成功ViewDidLoad已经被替换,但是界面已经生成,新的正确的ViewDidLoad并不会再次执行 375 | 效果:我的viewDidLoad为啥不能修改啊? 376 | ``` 377 | 378 | 379 | 比喻: 380 | 381 | viewDidload的函数代码就好比建筑设计图 382 | 运行起来后的界面就好比建好的建筑 383 | 时间顺序: 384 | 385 | viewDidLoad有bug需要改(建筑设计图图纸错了) 386 | 旧viewDidLoad先执行,并且创建好了界面(工人已经按着错图纸把建筑建好了) 387 | JSPatch执行了hotfix(设计师修改设计图纸) 388 | JSPatch看起来没效果(就算你改好了建筑图纸,已经建好的建筑是不会有任何改变的) 389 | 390 | 解决办法:2个 391 | 392 | * 在建造建筑之前,把图纸改好 393 | JSPatch在使用的时候,第一次下载网络请求是要时间的,所以才会发生修改图纸,在建筑建好之后。但是补丁已经下载完成,第二次运行app,新的图纸已经存在本地,是可以在创建rootViewController之前,就先把patch运行,让新图纸生效的。 394 | 395 | * 不要修改图纸了,直接去修改建筑 396 | 当你网络请求在JSPatch下载完Patch之后,通过callback,进行完全自定义的处理,窗户坏了,直接改窗户,门坏了修门,你也可以自定义把房子推倒了重建, 397 | 如果你使用的是JSPatchSDK,那么头文件有一个callback的API,JSPatchSDK提供了JS下载完成的这个时机,具体怎么修,纯看使用者自己。 如果你是用的是Github源码,那么自己按着这个思路自行处理 398 | 399 | 400 | **实现原理 : JS传递字符串给OC,OC通过 Runtime 接口调用和替换OC方法。** 401 | 402 | * Objective-C是动态语言,具有运行时特性,该特性可通过类名称和方法名的字符串获取该类和该方法,并实例化和调用 403 | 404 | ``` 405 | Class class = NSClassFromString(“UIViewController"); 406 | id viewController = [[class alloc] init]; 407 | SEL selector = NSSelectorFromString(“viewDidLoad"); 408 | [viewController performSelector:selector]; 409 | ``` 410 | 411 | * 也可以替换某个类的方法为新的实现 412 | 413 | ``` 414 | static void newViewDidLoad(id slf, SEL sel) {} 415 | class_replaceMethod(class, selector, newViewDidLoad, @""); 416 | ``` 417 | 418 | * 互传消息 419 | 420 | ``` 421 | JS和OC是通过JavaScriptCore互传消息的。OC端在启动JSPatch引擎时会创建一个 JSContext 实例,JSContext 是JS代码的执行环境,可以给 JSContext 添加方法。JS通过调用 JSContext 定义的方法把数据传给OC,OC通过返回值传会给JS。调用这种方法,它的参数/返回值 JavaScriptCore 都会自动转换,OC里的 NSArray, NSDictionary, NSString, NSNumber, NSBlock 会分别转为JS端的数组/对象/字符串/数字/函数类型。 422 | 对于一个自定义id对象,JavaScriptCore 会把这个自定义对象的指针传给JS,这个对象在JS无法使用,但在回传给OC时OC可以找到这个对象。对于这个对象生命周期的管理,如果JS有变量引用时,这个OC对象引用计数就加1 ,JS变量的引用释放了就减1,如果OC上没别的持有者,这个OC对象的生命周期就跟着JS走了,会在JS进行垃圾回收时释放 423 | ``` 424 | 425 | **方法替换** 426 | 427 | 428 | 429 | **Method 保存了一个方法的全部信息,包括SEL方法名,type各参数和返回值类型,IMP该方法具体实现的函数指针。 430 | ** 431 | 432 | **把 UIViewController 的 -viewDidLoad 方法给替换成我们自定义的方法,APP里调用 UIViewController 的 viewDidLoad 方法都会去到上述 viewDidLoadIMP 函数里,在这个新的IMP函数里调用JS传进来的方法,就实现了替换 -viewDidLoad 方法为JS代码里的实现,同时为 UIViewController 新增了个方法 -ORIGViewDidLoad 指向原来 viewDidLoad 的IMP,JS可以通过这个方法调用到原来的实现** 433 | 434 | **当调用一个 NSObject 对象不存在的方法时,并不会马上抛出异常,而是会经过多层转发,层层调用对象的 -resolveInstanceMethod:, -forwardingTargetForSelector:, -methodSignatureForSelector:, -forwardInvocation: 等方法** 435 | 436 | **最后 -forwardInvocation: 是会有一个 NSInvocation 对象,这个 NSInvocation 对象保存了这个方法调用的所有信息,包括 Selector 名,参数和返回值类型,最重要的是有所有参数值,可以从这个 NSInvocation 对象里拿到调用的所有参数值。我们可以想办法让每个需要被JS替换的方法调用最后都调到 -forwardInvocation:,就可以解决无法拿到参数值的问题了** 437 | 438 | 439 | **以替换 UIViewController 的 -viewWillAppear: 方法为例:** 440 | 441 | * 把UIViewController的 -viewWillAppear: 方法通过 class_replaceMethod() 接口指向一个不存在的IMP: class_getMethodImplementation(cls, @selector(__JPNONImplementSelector)),这样调用这个方法时就会走到 -forwardInvocation:。 442 | 443 | * 为 UIViewController 添加 -ORIGviewWillAppear: 和 -_JPviewWillAppear: 两个方法,前者指向原来的IMP实现,后者是新的实现,稍后会在这个实现里回调JS函数。 444 | 445 | * 改写 UIViewController 的 -forwardInvocation: 方法为自定义实现。一旦OC里调用 UIViewController 的 -viewWillAppear: 方法,经过上面的处理会把这个调用转发到 -forwardInvocation: ,这时已经组装好了一个 NSInvocation,包含了这个调用的参数。在这里把参数从 NSInvocation 反解出来,带着参数调用上述新增加的方法 -JPviewWillAppear: ,在这个新方法里取到参数传给JS,调用JS的实现函数。整个调用过程就结束了 446 | 447 | 448 | ![示意图](/Users/mac/Documents/githup/iOS-Collection/屏幕快照 2016-08-24 下午5.42.00.png) 449 | 450 | 451 | **HOTFIX** 452 | 453 | ``` 454 | 455 | - (void)awakeFromObjection 456 | { 457 | /* 基本思路: 安装本地所有补丁 --> 联网更新补丁信息,并安装有更新或新增加的补丁. */ 458 | /* DEBUG模式,总会执行测试函数,以测试某个JS. */ 459 | [self mcDebug]; 460 | 461 | /* 安装本地已有补丁. */ 462 | [self mcInstallLocalPatchs]; 463 | 464 | /* 联网获取最新补丁. */ 465 | [[[[self mcFetchLatestPatchs] flattenMap:^RACStream *(NSArray * models) { 466 | return [self mcUpdateLocalPatchs: models]; 467 | }] then:^RACSignal *{/* 更新本地补丁文件. */ 468 | return [self mcUpdateAllLocalPatchFiles]; 469 | }] subscribeNext:^(id patch) { /* 安装新增或有更新的补丁. */ 470 | [self mcInstallPatch: patch]; 471 | }]; 472 | } 473 | 474 | ``` 475 | 476 | #####GCD 信号量 477 | 478 | *信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。 一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行时,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,直到信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,那么久导致了阻塞。这样就达到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1. 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.次数进程2咱有资源,排他访问资源。 这就是信号量来控制互斥的原理* 479 | 480 | **简单来讲 信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步** 481 | 482 | ``` 483 | dispatch_semaphore_create 创建一个semaphore  484 | dispatch_semaphore_signal 发送一个信号  485 | dispatch_semaphore_wait 等待信号 486 | ``` 487 | **第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。** 488 | 489 | 490 | ``` 491 | // 创建队列组 492 | dispatch_group_t group = dispatch_group_create(); 493 | // 创建信号量,并且设置值为10 494 | dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); 495 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 496 | for (int i = 0; i < 100; i++) 497 | { // 由于是异步执行的,所以每次循环Block里面的dispatch_semaphore_signal根本还没有执行就会执行dispatch_semaphore_wait,从而semaphore-1.当循环10此后,semaphore等于0,则会阻塞线程,直到执行了Block的dispatch_semaphore_signal 才会继续执行 498 | dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 499 | dispatch_group_async(group, queue, ^{ 500 | NSLog(@"%i",i); 501 | sleep(2); 502 | // 每次发送信号则semaphore会+1, 503 | dispatch_semaphore_signal(semaphore); 504 | }); 505 | } 506 | ``` 507 | 508 | #####Aspects AOP 面相切片编程 509 | 510 | **解决复杂的统计问题** 511 | 512 | #####iOS 常用小技巧 513 | 514 | * 打印View所有子视图 515 | 516 | ``` 517 | po [[self view]recursiveDescription] 518 | ``` 519 | 520 | * Cell 的分割线的15像素 521 | 522 | ``` 523 | 首先在viewDidLoad方法加入以下代码: 524 | if ([self.tableView respondsToSelector:@selector(setSeparatorInset:)]) { 525 | [self.tableView setSeparatorInset:UIEdgeInsetsZero]; 526 | } 527 | if ([self.tableView respondsToSelector:@selector(setLayoutMargins:)]) { 528 | [self.tableView setLayoutMargins:UIEdgeInsetsZero]; 529 | } 530 | 然后在重写willDisplayCell方法 531 | - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell 532 | forRowAtIndexPath:(NSIndexPath *)indexPath{ 533 | if ([cell respondsToSelector:@selector(setSeparatorInset:)]) { 534 | [cell setSeparatorInset:UIEdgeInsetsZero]; 535 | } 536 | if ([cell respondsToSelector:@selector(setLayoutMargins:)]) { 537 | [cell setLayoutMargins:UIEdgeInsetsZero]; 538 | } 539 | } 540 | ``` 541 | 542 | * 方法调用时间 543 | 544 | ``` 545 | // 获取时间间隔 546 | #define TICK CFAbsoluteTime start = CFAbsoluteTimeGetCurrent(); 547 | #define TOCK NSLog(@"Time: %f", CFAbsoluteTimeGetCurrent() - start) 548 | ``` 549 | 550 | * KVC 的数组操作 551 | 552 | ``` 553 | NSArray *array = [NSArray arrayWithObjects:@"2.0", @"2.3", @"3.0", @"4.0", @"10", nil]; 554 | CGFloat sum = [[array valueForKeyPath:@"@sum.floatValue"] floatValue]; 555 | CGFloat avg = [[array valueForKeyPath:@"@avg.floatValue"] floatValue]; 556 | CGFloat max =[[array valueForKeyPath:@"@max.floatValue"] floatValue]; 557 | CGFloat min =[[array valueForKeyPath:@"@min.floatValue"] floatValue]; 558 | NSLog(@"%fn%fn%fn%f",sum,avg,max,min); 559 | ``` 560 | 561 | * 比特操作 562 | 563 | ``` 564 | NSArray *products = @[product1,product2,product3]; 565 | NSLog(@"avg:%@",[products valueForKeyPath:@"@avg.price"]); 566 | NSLog(@"count:%@",[products valueForKeyPath:@"@count.price"]); 567 | NSLog(@"max:%@",[products valueForKeyPath:@"@max.price"]); 568 | NSLog(@"min:%@",[products valueForKeyPath:@"@min.price"]); 569 | NSLog(@"sum:%@",[products valueForKeyPath:@"@sum.price"]); 570 | ``` 571 | 572 | * Nil Null nil NSNull 的区别 573 | 574 | ``` 575 | * nil是OC的,空对象,地址指向空(0)的对象。对象的字面零值 576 | * Nil是Objective-C类的字面零值 577 | * NULL是C的,空地址,地址的数值是0,是个长整数 578 | * NSNull用于解决向NSArray和NSDictionary等集合中添加空值的问题 579 | ``` 580 | * backItem 返回按钮 581 | 582 | ``` 583 | [[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault]; 584 | ``` 585 | 586 | * KVC 修改UITextField 中的placeholder 文字颜色 587 | 588 | ``` 589 | [text setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"]; 590 | ``` 591 | 592 | * 让Xcode的控制台支持LLDB类型的打印 593 | 594 | ``` 595 | 打开终端输入三条命令: 596 | touch ~/.lldbinit 597 | echo display @import UIKit >> ~/.lldbinit 598 | echo target stop-hook add -o "target stop-hook disable" >> ~/.lldbinit 599 | ``` 600 | 601 | * block 判空处理 602 | 603 | ``` 604 | #define BLOCK_EXEC(block, ...) if (block) { block(__VA_ARGS__); }; 605 | // 宏定义之前的用法 606 | if (completionBlock) { 607 | completionBlock(arg1, arg2); 608 | } 609 | // 宏定义之后的用法 610 | BLOCK_EXEC(completionBlock, arg1, arg2); 611 | ``` 612 | 613 | ##### NSOperation 614 | 615 | **串行vs并发** 616 | 617 | *串行和并发的主要区别在于允许同时执行的任务数量。串行,指的是一次只能执行一个任务,必须等一个任务执行完成后才能执行下一个任务;并发,则指的是允许多个任务同时执行。* 618 | 619 | **同步vs异步** 620 | 621 | *同步和异步操作的主要区别在于是否等待操作执行完成,亦即是否阻塞当前线程。同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果* 622 | 623 | **并发vs非并发Operation** 624 | 625 | 我们都是通过将 operation 添加到一个 operation queue 的方式来执行 operation 的,然而这并不是必须的。我们也可以直接通过调用 start 方法来执行一个 operation ,但是这种方式并不能保证 operation 是异步执行的。NSOperation 类的 isConcurrent 方法的返回值标识了一个 operation 相对于调用它的 start 方法的线程来说是否是异步执行的。在默认情况下,isConcurrent 方法的返回值是 NO ,也就是说会阻塞调用它的 start 方法的线程。 626 | 627 | 如果我们想要自定义一个并发执行的 operation ,那么我们就必须要编写一些额外的代码来让这个 operation 异步执行。比如,为这个 operation 创建新的线程、调用系统的异步方法或者其他任何方式来确保 start 方法在开始执行任务后立即返回。 628 | 629 | 在绝大多数情况下,我们都不需要去实现一个并发的 operation 。如果我们一直是通过将 operation 添加到 operation queue 的方式来执行 operation 的话,我们就完全没有必要去实现一个并发的 operation 。因为,当我们将一个非并发的 operation 添加到 operation queue 后,operation queue 会自动为这个 operation 创建一个线程。因此,只有当我们需要手动地执行一个 operation ,又想让它异步执行时,我们才有必要去实现一个并发的 operation 。 630 | 631 | 对于一个非并发的 operation ,我们需要做的就只是执行 main 方法中的任务以及能够正常响应取消事件就可以了,其它的复杂工作比如依赖配置、KVO 通知等 NSOperation 类都已经帮我们处理好了。而对于一个并发的 operation ,我们还需要重写 NSOperation 类中的一些现有方法。接下来,我们将会介绍如何自定义这两种不同类型的 NSOperation 子类。 632 | 633 | 634 | **配置并发执行的 Operation** 635 | 636 | * start :必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现; 637 | 638 | * main :可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰; 639 | 640 | * isExecuting 和 isFinished :必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化; 641 | 642 | * isConcurrent :必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES 。 643 | 644 | executing 和 finished 属性都被声明成了只读的 readonly 。所以我们在 NSOperation 子类中就没有办法直接通过 setter 方法来自动触发 KVO 通知,这也是为什么我们需要在接下来的代码中手动触发 KVO 通知的原因。 645 | 646 | ``` 647 | - (void)start { 648 | if (self.isCancelled) { 649 | [self willChangeValueForKey:@"isFinished"]; 650 | _finished = YES; 651 | [self didChangeValueForKey:@"isFinished"]; 652 | return; 653 | } 654 | 655 | [self willChangeValueForKey:@"isExecuting"]; 656 | [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; 657 | _executing = YES; 658 | [self didChangeValueForKey:@"isExecuting"]; 659 | } 660 | ``` 661 | 662 | 663 | #####Dispatch_block 664 | 665 | ``` 666 | // 用法和一般block 一样,可以用来做没有参数的block 回调 667 | @property (nonatomic, copy) dispatch_block_t leftBlock; 668 | ``` 669 | 670 | ##### 后台下载 671 | 672 | ``` 673 | - (NSURLSession *)backgroundSession { 674 | static NSURLSession *session = nil; 675 | static dispatch_once_t onceToken; 676 | dispatch_once(&onceToken, ^{ 677 | //这个sessionConfiguration 很重要, com.zyprosoft.xxx 这里,这个com.company.这个一定要和 bundle identifier 里面的一致,否则ApplicationDelegate 不会调用handleEventsForBackgroundURLSession代理方法 678 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.zyprosoft.backgroundsession"]; 679 | session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; 680 | }); 681 | return session; 682 | } 683 | 684 | //在切换到后台后调用 685 | - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier 686 | completionHandler:(void (^)())completionHandler { 687 | self.backgroundSessionCompletionHandler = completionHandler; 688 | //添加本地通知 689 | [self presentNotification]; 690 | } 691 | 692 | 693 | #pragma mark - NSURLSessionDelegate 694 | //一个session结束之后,会在后台调用 695 | - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session 696 | { 697 | AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 698 | if (appDelegate.backgroundSessionCompletionHandler) { 699 | void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler; 700 | appDelegate.backgroundSessionCompletionHandler = nil; 701 | completionHandler(); 702 | } 703 | NSLog(@"所有任务已完成!"); 704 | } 705 | 706 | ``` 707 | 708 | #####全屏返回手势 709 | 710 | ``` 711 | // 将系统自带的手势禁用掉,将系统自带的手势添加到view中,这样就实现了全屏返回手势。 关键在于找准target action 712 | - (void)viewDidLoad { 713 | [super viewDidLoad]; 714 | NSLog(@"interactivePopGestureRecognizer = %@ \n\n",self.interactivePopGestureRecognizer); 715 | NSLog(@"delegate = %@",self.interactivePopGestureRecognizer.delegate); 716 | //获取系统自带滑动手势的target对象 717 | id target = self.interactivePopGestureRecognizer.delegate; 718 | NSLog(@"target = %@", target); 719 | // 创建全屏滑动手势,调用系统自带滑动手势的target的action方法 720 | UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]initWithTarget:target action:@selector(handleNavigationTransition:)]; 721 | //设置代理手势,拦截手势触发 722 | pan.delegate = self; 723 | //给导航控制器的view添加全屏滑动手势 724 | [self.view addGestureRecognizer:pan]; 725 | // 禁止使用系统自带的滑动手势 726 | self.interactivePopGestureRecognizer.enabled = NO; 727 | } 728 | ``` 729 | 730 | 731 | ##### 读取二维码 732 | 733 | ``` 734 | #pragma mark 读取二维码 735 | -(void)readQRCoder 736 | { 737 | //1.实例化摄像头设备 738 | AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; 739 | 740 | //2.设置输入,把摄像头作为输入设备 741 | //因为模拟器是没有摄像头的,因此在此最好做个判断 742 | NSError *error = nil; 743 | AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; 744 | if (error) { 745 | NSLog(@"没有摄像头%@", error.localizedDescription); 746 | return; 747 | } 748 | //3.设置输出(Metadata元数据) 749 | AVCaptureMetadataOutput *outPut = [[AVCaptureMetadataOutput alloc] init]; 750 | //3.1 设置输出的代理 751 | //使用主线程队列,相应比较同步,使用其他队列,相应不同步,容易让用户产生不好的体验。 752 | [outPut setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()]; 753 | 754 | //4.拍摄会话 755 | AVCaptureSession *session = [[AVCaptureSession alloc]init]; 756 | //添加session的输入和输出 757 | [session addInput:input]; 758 | [session addOutput:outPut]; 759 | //4.1设置输出的格式 760 | [outPut setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]]; 761 | 762 | //5.设置预览图层(用来让用户能够看到扫描情况) 763 | AVCaptureVideoPreviewLayer *preview = [AVCaptureVideoPreviewLayer layerWithSession:session]; 764 | //5.1设置preview图层的属性 765 | [preview setVideoGravity:AVLayerVideoGravityResizeAspectFill]; 766 | //5.2设置preview图层的大小 767 | [preview setFrame:self.view.bounds]; 768 | //5.3将图层添加到视图的图层 769 | [self.view.layer insertSublayer:preview atIndex:0]; 770 | self.previewLayer = preview; 771 | //6.启动会话 772 | [session startRunning]; 773 | self.session = session; 774 | } 775 | 776 | #pragma mark 输出代理方法 777 | //此方法是在识别到QRCode并且完成转换,如果QRCode的内容越大,转换需要的时间就越长。 778 | -(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection 779 | { 780 | //会频繁的扫描,调用代理方法 781 | //1如果扫描完成,停止会话 782 | [self.session stopRunning]; 783 | //2删除预览图层 784 | [self.previewLayer removeFromSuperlayer]; 785 | NSLog(@"%@",metadataObjects); 786 | //设置界面显示扫描结果 787 | if (metadataObjects.count > 0) { 788 | AVMetadataMachineReadableCodeObject *obj = metadataObjects[0]; 789 | //提示:如果需要对url或者名片等信息上扫描,再次扩展就好了。 790 | _label.text = obj.stringValue; 791 | } 792 | } 793 | ``` 794 | 795 | #####隐藏导航栏的细线 796 | ``` 797 | // 方式1.tabbar 也可以这么做 798 | self.navigationController?.navigationBar.shadowImage = UIImage() 799 | 800 | // 方式2:巧妙的写法 801 | self.navigationController?.navigationBar.clipsToBounds = true; 802 | 803 | // 方式三:比较正规 804 | private func findHairlineImageViewUnderView(view: UIView) -> UIImageView? { 805 | if view is UIImageView && view.bounds.height <= 1.0 { 806 | return view as? UIImageView 807 | } 808 | for subView in view.subviews { 809 | guard let i = findHairlineImageViewUnderView(subView) else{ return nil } 810 | return i 811 | } 812 | return nil 813 | } 814 | 815 | // Swift 中升级的属性 816 | navigationController?.hidesBarsOnSwipe = true 817 | 818 | ``` 819 | 820 | ##### 关联 821 | ``` 822 | -(NSString *)associatedObject_retain{ 823 | return objc_getAssociatedObject(self, _cmd); 824 | } 825 | - (void)setAssociatedObject_retain:(NSString *)associatedObject_retain{ 826 | objc_setAssociatedObject(self, @selector(associatedObject_retain), associatedObject_retain, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 827 | } 828 | ``` 829 | 830 | #####keychain 831 | **可以用NSUserDefaults存储数据信息,但是对于一些私密信息,比如账号、密码等等,就需要使用更为安全的keychain了。而Keychain的信息是存在于每个应用(app)的沙盒之外的,所以keychain里保存的信息不会因App被删除而丢失,在用户重新安装App后依然有效,数据还在。比如优步下载app之后不需要登录,自动登录了** 832 | [参考链接](http://blog.sina.com.cn/s/blog_5971cdd00102vqgy.html) 833 | 834 | #####KVO 注册依赖键 835 | ``` 836 | 837 | //重写getter方法 838 | - (NSString *)accountForBank { 839 | return [NSString stringWithFormat:@"account for %@ is %d", self.bankCodeEn, self.accountBalance]; 840 | } 841 | //定义了这种依赖关系后,我们就需要以某种方式告诉KVO,当我们的被依赖属性修改时,会发送accountForBank属性被修改的通知。 842 | //当两个属性之中的一个改变,就会激发kvo 843 | + (NSSet *)keyPathsForValuesAffectingAccountForBank { 844 | return [NSSet setWithObjects:@"accountBalance", @"bankCodeEn", nil]; 845 | } 846 | ``` 847 | 848 | 849 | #####视频压缩 850 | 851 | ``` 852 | 使用系统自带的AVAssetExportSession,代码和网上说的差不多 853 | AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:sourceUrl options:nil]; 854 | NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset]; 855 | if ([compatiblePresets containsObject:AVAssetExportPresetHighestQuality]){ 856 | AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetMediumQuality]; 857 | NSDateFormatter *formater = [[NSDateFormatter alloc] init];//用时间给文件全名,以免重复,在测试的时候其实可以判断文件是否存在若存在,则删除,重新生成文件即可 858 | [formater setDateFormat:@"yyyy-MM-dd-HH:mm:ss"]; 859 | NSString * resultPath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/output-%@.mp4", [formater stringFromDate:[NSDate date]]]; 860 | exportSession.outputURL = [NSURL fileURLWithPath:resultPath]; 861 | exportSession.outputFileType = AVFileTypeMPEG4; 862 | exportSession.shouldOptimizeForNetworkUse = YES; 863 | [exportSession exportAsynchronouslyWithCompletionHandler:^(void){ 864 | switch (exportSession.status) { 865 | case AVAssetExportSessionStatusUnknown: 866 | NSLog(@"AVAssetExportSessionStatusUnknown"); 867 | break; 868 | case AVAssetExportSessionStatusWaiting: 869 | NSLog(@"AVAssetExportSessionStatusWaiting"); 870 | break; 871 | case AVAssetExportSessionStatusExporting: 872 | NSLog(@"AVAssetExportSessionStatusExporting"); 873 | break; 874 | case AVAssetExportSessionStatusCompleted: 875 | NSLog(@"AVAssetExportSessionStatusCompleted"); 876 | break; 877 | case AVAssetExportSessionStatusFailed: 878 | NSLog(@"AVAssetExportSessionStatusFailed"); 879 | break; 880 | } 881 | }]; 882 | } 883 | ``` 884 | 885 | ##### 控制动画执行时间 886 | 887 | ``` 888 | keyFrameAnimation.beginTime = CACurrentMediaTime() + 0.5 889 | ``` 890 | 891 | #### 屏幕旋转 892 | 893 | * 物理方向:[UIDevice currentDevice].orientation (UIDeviceOrientation类型) 894 | 895 | ``` 896 | if (![UIDevice currentDevice].generatesDeviceOrientationNotifications) { 897 | [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; 898 | } 899 | NSLog(@"%d",[UIDevice currentDevice].orientation); 900 | [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications]; 901 | ``` 902 | 903 | * 界面显示方向: viewController.interfaceOrientation 904 | * [UIApplication sharedApplication].statusBarOrientation 905 | 906 | ``` 907 | - (BOOL)shouldAutorotate NS_AVAILABLE_IOS(6_0); 908 | - (NSUInteger)supportedInterfaceOrientations 909 | - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation NS_AVAILABLE_IOS(6_0); 910 | ``` 911 | 912 | ####AVPLay 913 | 914 | ``` 915 | - (void)createTimer{ 916 | __weak typeof(self) weakSelf = self; 917 | self.timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(1, 1) queue:nil usingBlock:^(CMTime time){ 918 | 919 | AVPlayerItem *currentItem = weakSelf.playerItem; 920 | NSArray *loadedRanges = currentItem.seekableTimeRanges; 921 | if (loadedRanges.count > 0 && currentItem.duration.timescale != 0) { 922 | // 当前播放时间 923 | NSInteger currentTime = (NSInteger)CMTimeGetSeconds([currentItem currentTime]); 924 | // 视频总时长 925 | CGFloat totalTime = (CGFloat)currentItem.duration.value / currentItem.duration.timescale; 926 | // 播放进度 927 | CGFloat value = CMTimeGetSeconds([currentItem currentTime]) / totalTime; 928 | [weakSelf.controlView zf_playerCurrentTime:currentTime totalTime:totalTime sliderValue:value]; 929 | } 930 | }]; 931 | } 932 | ``` 933 | 934 | ``` 935 | 936 | - (NSTimeInterval)availableDuration { 937 | NSArray *loadedTimeRanges = [[_player currentItem] loadedTimeRanges]; 938 | CMTimeRange timeRange = [loadedTimeRanges.firstObject CMTimeRangeValue];// 获取缓冲区域 939 | float startSeconds = CMTimeGetSeconds(timeRange.start); 940 | float durationSeconds = CMTimeGetSeconds(timeRange.duration); 941 | NSTimeInterval result = startSeconds + durationSeconds;// 计算缓冲总进度 942 | return result; 943 | } 944 | ``` 945 | 946 | ``` 947 | 948 | - (void)seekToTime:(NSInteger)dragedSeconds completionHandler:(void (^)(BOOL finished))completionHandler 949 | { 950 | if (self.player.currentItem.status == AVPlayerItemStatusReadyToPlay) { 951 | // seekTime:completionHandler:不能精确定位 952 | // 如果需要精确定位,可以使用seekToTime:toleranceBefore:toleranceAfter:completionHandler: 953 | // 转换成CMTime才能给player来控制播放进度 954 | [self.controlView zf_playerActivity:YES]; 955 | [self.player pause]; 956 | CMTime dragedCMTime = CMTimeMake(dragedSeconds, 1); //kCMTimeZero 957 | __weak typeof(self) weakSelf = self; 958 | [self.player seekToTime:dragedCMTime toleranceBefore:CMTimeMake(1,1) toleranceAfter:CMTimeMake(1,1) completionHandler:^(BOOL finished) { 959 | [weakSelf.controlView zf_playerActivity:NO]; 960 | // 视频跳转回调 961 | if (completionHandler) { completionHandler(finished); } 962 | [weakSelf.player play]; 963 | weakSelf.seekTime = 0; 964 | weakSelf.isDragged = NO; 965 | // 结束滑动 966 | [weakSelf.controlView zf_playerDraggedEnd]; 967 | if (!weakSelf.playerItem.isPlaybackLikelyToKeepUp && !weakSelf.isLocalVideo) { weakSelf.state = ZFPlayerStateBuffering; } 968 | 969 | }]; 970 | } 971 | } 972 | 973 | ``` 974 | 975 | ``` 976 | 977 | if (playerItem) { 978 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moviePlayDidEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:playerItem]; 979 | [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil]; 980 | [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; 981 | // 缓冲区空了,需要等待数据 982 | [playerItem addObserver:self forKeyPath:@"playbackBufferEmpty" options:NSKeyValueObservingOptionNew context:nil]; 983 | // 缓冲区有足够数据可以播放了 984 | [playerItem addObserver:self forKeyPath:@"playbackLikelyToKeepUp" options:NSKeyValueObservingOptionNew context:nil]; 985 | } 986 | 987 | ``` 988 | 989 | ``` 990 | [self.imageGenerator generateCGImagesAsynchronouslyForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:dragedCMTime]] completionHandler:handler]; 991 | ``` 992 | 993 | 通常情况下可以通过判断播放器的播放速度来获得播放状态。如果rate为0说明是停止状态,1是则是正常播放状态。 994 | 995 | 当然AVPlayerItem是有通知的,但是对于获得播放状态和加载状态有用的通知只有一个:播放完成通知AVPlayerItemDidPlayToEndTimeNotification。在播放视频时,特别是播放网络视频往往需要知道视频加载情况、缓冲情况、播放情况,这些信息可以通过KVO监控AVPlayerItem的status、loadedTimeRanges属性来获得。当AVPlayerItem的status属性为AVPlayerStatusReadyToPlay是说明正在播放,只有处于这个状态时才能获得视频时长等信息;当loadedTimeRanges的改变时(每缓冲一部分数据就会更新此属性)可以获得本次缓冲加载的视频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。然后就是依靠AVPlayer的- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block方法获得播放进度,这个方法会在设定的时间间隔内定时更新播放进度,通过time参数通知客户端 996 | 997 | ##### oc 结构体玩法 998 | 999 | ``` 1000 | 1001 | struct JMRadius { 1002 | CGFloat topLeftRadius; 1003 | CGFloat topRightRadius; 1004 | CGFloat bottomLeftRadius; 1005 | CGFloat bottomRightRadius; 1006 | }; 1007 | typedef struct JMRadius JMRadius; 1008 | 1009 | static inline JMRadius JMRadiusMake(CGFloat topLeftRadius, CGFloat topRightRadius, CGFloat bottomLeftRadius, CGFloat bottomRightRadius) { 1010 | JMRadius radius; 1011 | radius.topLeftRadius = topLeftRadius; 1012 | radius.topRightRadius = topRightRadius; 1013 | radius.bottomLeftRadius = bottomLeftRadius; 1014 | radius.bottomRightRadius = bottomRightRadius; 1015 | return radius; 1016 | } 1017 | 1018 | static inline NSString * NSStringFromJMRadius(JMRadius radius) { 1019 | return [NSString stringWithFormat:@"{%.2f, %.2f, %.2f, %.2f}", radius.topLeftRadius, radius.topRightRadius, radius.bottomLeftRadius, radius.bottomRightRadius]; 1020 | } 1021 | ``` 1022 | 1023 | 1024 | 1025 | #### 运行时判断类型 1026 | 在Swift中,如果想在运行时判断一个对象的类型是不是可选类型,则可以使用Mirror,所有的可选类型都将返回optional。 1027 | 1028 | Mirror的displayStyle属性的类型是Mirror.DisplayStyle,这是一个枚举类型,用于为Mirror的主体对象提供一种建议性的解析。枚举值范围之外的类型,其Mirror的displayStyle属性将返回nil 1029 | 1030 | ``` 1031 | let value: Int? = 3 1032 | Mirror(reflecting: value as Any).displayStyle 1033 | /** 如果不是`struct` `class` `enum` tuple optional 1034 | collection dictionary set 这些类型,那么返回nil */ 1035 | ``` 1036 | 1037 | #### 自定义操作符 1038 | 在自定义操作符时,可以以dot(.)开头,这种情况下,操作符后面还可以包含其它的dot(.) 1039 | 1040 | 但如果操作符不是以dot开头,则后面不能再包含dot,如"operator +.+"这个声明会被看成是"+"操作符后面跟了个".+"操作符。编译器会给出错误提示。 1041 | 1042 | *首先在全局使用operator关键字来声明操作符,同时用prefix、infix或postfix来声明操作符的位置;然后在所需要的类/结构体中实现操作符。* 1043 | 1044 | #### selector 玩法 1045 | 1046 | ``` 1047 | 1048 | fileprivate extension Selector{ 1049 | /// 这里给统一的注释也ok 1050 | static let loginButtonTaped = #selector(ViewController.buttonTap) 1051 | } 1052 | 1053 | class ViewController: UIViewController{ 1054 | func buttonTap(){ } 1055 | 1056 | func setup() { 1057 | let btn = UIButton() 1058 | btn.addTarget(self, action: .loginButtonTaped, for: .touchUpInside) 1059 | } 1060 | } 1061 | ``` 1062 | 1063 | 1064 | #### 指针 1065 | 1066 | 可以使用withUnsafePointer(to:_:)函数来获取一个变量的指针 1067 | 1068 | withUnsafePointer(to:_:)将第一个参数转换为指针,然后将这个指针作为参数去调用第二个参数指定的闭包。如果闭包有返回值,它将作为函数的返回值。 1069 | 1070 | 需要注意的是,生成的指针的生命周期限定于闭包内部,不能将其指定给外部的变量。 1071 | 1072 | ``` 1073 | 1074 | var age = 5 1075 | withUnsafePointer(to: &age) {p in print(p)} 1076 | 1077 | func printPoint(ptr: UnsafePointer){ 1078 | print(ptr) 1079 | } 1080 | printPoint(ptr: &age) 1081 | ``` 1082 | 1083 | #### HTTPS 1084 | 适配HTTPS做双向验证时,如何放置证书?有两种方式,AFNetworking支持拖拽文件,自动检测并绑定,这个很好用,所以这种方式大家用的多。还有另外一种方式,可以将证书文件转成base64,再用NSURLProtocol缓存起来。原生的NSURLSession支持该方式,当然,AFNetworking也支持该方式。所有如果你看到一个项目,发现项目中没有cer文件,也不一定说 1085 | 明该项目不支持双向认证 1086 | 1087 | HTTPS 请求做 SSL Pinning,即在客户端本地钉一个服务端的证书,在 SSL 握手阶段除了校验证书的有效性和合法性之外,还对服务端返回证书内容与本地的进行比对,只有相同才通过校验,这样可以阻止中间人攻击,以及避免数据被抓包监听。 1088 | 1089 | ####defer 1090 | 1091 | 关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 return 语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)。当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出 1092 | 关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源 1093 | 1094 | ####static class 1095 | static和class这两个关键字都可以修饰类的方法,以表明这个方法是一个类方法。不过这两者稍微有一些区别:class修饰的类方法可以被子类重写,而static修饰的类方法则不能 1096 | 1097 | #### contains 1098 | Swift中如果想判断一个Array中是否包含某个元素,我们可以使用contains方法 1099 | 1100 | 不过这个方法要求数组中的元素类型实现了Equatable协议,否则无法使用,还好Swift为我们提供了另一个contains方法,可以自定义谓词条件作为判断依据 1101 | 1102 | 这个方法会查看数组是否包含满足给定的谓词条件的元素。可以看到这个方法是一个高阶函数,其参数是一个尾随闭包,在闭包内我们可以根据实际需要来实现我们自己的判断。所以上面的判断可以如图3实现。 1103 | 1104 | 当然,对于元素类型实现了Equatable协议的数组,也可以使用这个方法。可以自定义谓词条件,查看数组是否有满足此条件的元素 1105 | 1106 | ``` 1107 | struct Person: Equatable{ 1108 | let age = 20 1109 | var name: String 1110 | 1111 | public static func ==(lhs: Person, rhs: Person) -> Bool{ 1112 | return lhs.name == rhs.name 1113 | } 1114 | } 1115 | 1116 | let arr = [1,2,3,4,5,6] 1117 | arr.contains(3) 1118 | 1119 | let p1 = Person(name: "jack") 1120 | let p2 = Person(name: "rose") 1121 | let arr1 = [p1,p2] 1122 | arr1.contains { (p) -> Bool in 1123 | return p.name == "jack" 1124 | } 1125 | arr1.contains(p1) 1126 | p1 == p2 // 实现了equalable 协议,就能使用== 判等 1127 | ``` 1128 | 1129 | #### 截屏通知 1130 | UIApplicationUserDidTakeScreenshot通知来告诉App用户做了截屏操作,如图1代码所示(Swift+Xcode 8)。我们只需要监听这个通知,并执行想要的操作即可 1131 | 1132 | #### static 1133 | 在Swift中,如果我们希望懒加载一个对象的属性,则可以在类/结构体中给属性加上lazy修饰符。 1134 | 1135 | 而全局变量或常量是自带lazy特性的,它们总是在用到的时候才去初始化,而这种情况下不需要用lazy来修饰。另外存储型类属性也是在初次使用时才初始化,并确保只会初始化一次,不需要用lazy来修饰。如果用lazy去修饰它,则编译器会报错 1136 | 1137 | #### 倒序遍历 1138 | 1139 | ``` 1140 | let nsarr: NSArray = [1,2,3,4,5,6,7,8] 1141 | nsarr.enumerateObjects(options: .reverse) { (item, idx, stop) in 1142 | print(item) 1143 | if idx == 2{ stop.pointee = true } 1144 | } 1145 | ``` 1146 | 1147 | #### oc 类属性 1148 | ``` 1149 | @interface JNDataTool () 1150 | @property (class,nonatomic, strong) NSMutableArray *arrs; 1151 | @end 1152 | 1153 | @implementation JNDataTool 1154 | 1155 | static NSMutableArray *_arr = nil; 1156 | 1157 | +(NSMutableArray *)arrs{ 1158 | if (_arr == nil) { 1159 | _arr = [NSMutableArray array]; 1160 | } 1161 | return _arr; 1162 | } 1163 | 1164 | +(void)setArrs:(NSMutableArray *)arrs{ 1165 | if (arrs != _arr) { 1166 | _arr = arrs; 1167 | } 1168 | } 1169 | @end 1170 | 1171 | void test(){ 1172 | NSLog(@"%@--",JNDataTool.arrs); 1173 | } 1174 | 1175 | ``` 1176 | 1177 | ####xcode 6.3 兼容swift 1178 | 1179 | **苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。** 1180 | 1181 | #### swift options 1182 | Swift不支持C语言中枚举值的整型掩码操作的技巧。在Swift中,一个枚举可以表示一组有效选项的集合,但却没有办法支持这些选项的组合操作(“&”、”|”等)。理论上,一个枚举可以定义选项值的任意组合值,但对于n > 3这种操作,却无法有效的支持。 1183 | 1184 | 为了支持类NS_OPTIONS的枚举,Swift 2.0中定义了OptionSetType协议 1185 | OptionSetType是选项集合类型,它定义了一些基本操作,包括集合操作(union, intersect, exclusiveOr)、成员管理(contains, insert, remove)、位操作(unionInPlace, intersectInPlace, exclusiveOrInPlace)以及其它的一些基本操作 1186 | 1187 | ``` 1188 | struct Directions: OptionSetType { 1189 | var rawValue:Int 1190 | init(rawValue: Int) { 1191 | self.rawValue = rawValue 1192 | } 1193 | static let Up: Directions = Directions(rawValue: 1 << 0) 1194 | static let Down: Directions = Directions(rawValue: 1 << 1) 1195 | static let Left: Directions = Directions(rawValue: 1 << 2) 1196 | static let Right: Directions = Directions(rawValue: 1 << 3) 1197 | } 1198 | let leftUp: Directions = [Directions.Left, Directions.Up] 1199 | if leftUp.contains(Directions.Left) && leftUp.contains(Directions.Up) { 1200 | // ... 1201 | } 1202 | ``` 1203 | 1204 | #### selector 1205 | 1206 | Selector是Objective-C的产物,它用于在运行时作为一个键值去找到对应方法的实现 1207 | 这就要求selector引用的方法必须对ObjC运行时是可见的。而Swift是静态语言,虽然继承自NSObject的类默认对ObjC运行时是可见的,但如果方法是由private关键字修饰的,则方法默认情况下对ObjC运行时并不是可见的,所以就导致了以上的异常:运行时并没找到SwipeCardView类的beginDragged:方法。 1208 | 我们必须将private修饰的方法暴露给运行时。正确的做法是在 private 前面加上 @objc 关键字, 1209 | 另外需要注意的是,如果我们的类是纯Swift类,而不是继承自NSObject,则不管方法是private还是internal或public,如果要用在Selector中,都需要加上@objc修饰符。 1210 | 1211 | #### 加锁 1212 | 加锁解锁是一件非常好性能的事情,尤其是用sync 方式加的锁,如果很多地方加锁都引用这self 的话,那么锁等待的时间会非常长,所以性能方面不是很好,但是可以通过GCD 的信号量来实现加锁的效果, 1213 | 线程等待 ,排他远离 pv操作 互斥原理.yycache 就是用了着了操作对做了优化. 1214 | 1215 | #### 并发与并行 1216 | 1217 | 1. 并发是单个处理器执行多个任务,这些任务在重叠的时间段内交叉执行,但在任何一个时间点都只有一个任务在执行。这些任务在逻辑上是同时执行,而实际上是交叉执行的。 1218 | 2. 并行是多个处理器或多核下执行多个任务,这些任务可以同时执行,即同一时间点可以有多个不同的任务在执行。这些任务在物理上是同时执行的。 1219 | 1220 | 1221 | #### 闭包捕获 1222 | 1223 | ``` 1224 | 1225 | var thing = "cars" 1226 | //let closure = { [thing] in // 注意,这里是捕获列表 1227 | // print("I love \(thing)") // cars 1228 | //} 1229 | let closure = { 1230 | print(thing) // aieplanes 1231 | } 1232 | thing = "airplanes" 1233 | closure() 1234 | 1235 | ``` 1236 | 当声明闭包的时候,捕获列表会创建一份thing的copy,所以被捕获到的值是不会改变的,即使你改变thing的值。 1237 | 如果你去掉闭包中的捕获列表,编译器会使用引用代替copy。在这种情况下,当闭包被调用时,变量的值是可以改变的。 1238 | 1239 | #### IBDesign 1240 | IB_DESIGNABLE的宏的功能就是让XCode动态渲染出该类图形化界面。UIView 或 NSView使用IB_DESIGNABLE宏声明时候,就是让Interface Builder知道它应该在UIStoryboard或者Xib中画布上直接渲染视图,不需要等到编译运行后就能预先展示出来效果 。 1241 | IBInspectable修饰属性,可以是用户自定义的运行时属性,让支持KVC的属性能够在Attribute Inspector中配置。 1242 | 1243 | ``` 1244 | IB_DESIGNABLE 1245 | @interface IBDesignableImageView : UIImageView 1246 | @property(nonatomic,assign) IBInspectable CGFloat cornerRadius; 1247 | 1248 | ``` 1249 | 1250 | 1251 | #### SDWebimage 1252 | 1253 | • UIImageView+WebCache: setImageWithURL:placeholderImage:options: 先显示 placeholderImage ,同时由SDWebImageManager 根据 URL 来在本地查找图片。 1254 | 1255 | • SDWebImageManager: downloadWithURL:delegate:options:userInfo: SDWebImageManager是将UIImageView+WebCache同SDImageCache链接起来的类, SDImageCache:queryDiskCacheForKey:delegate:userInfo:用来从缓存根据CacheKey查找图片是否已经在缓存中 1256 | 1257 | • 如果内存中已经有图片缓存, SDWebImageManager会回调SDImageCacheDelegate : imageCache:didFindImage:forKey:userInfo: 1258 | 1259 | • 而 UIImageView+WebCache 则回调SDWebImageManagerDelegate: webImageManager:didFinishWithImage:来显示图片。 1260 | 1261 | • 如果内存中没有图片缓存,那么生成 NSInvocationOperation 添加到队列,从硬盘查找图片是否已被下载缓存。 1262 | 1263 | • 根据 URLKey 在硬盘缓存目录下尝试读取图片文件。这一步是在 NSOperation 进行的操作,所以回主线程进行结果回调 notifyDelegate:。 1264 | 1265 | • 如果上一操作从硬盘读取到了图片,将图片添加到内存缓存中(如果空闲内存过小,会先清空内存缓存)。SDImageCacheDelegate 回调 imageCache:didFindImage:forKey:userInfo:。进而回调展示图片。 1266 | 1267 | • 如果从硬盘缓存目录读取不到图片,说明所有缓存都不存在该图片,需要下载图片,回调 imageCache:didNotFindImageForKey:userInfo:。 1268 | 1269 | • 共享或重新生成一个下载器 SDWebImageDownloader 开始下载图片。 1270 | 1271 | • 图片下载由 NSURLConnection 来做,实现相关 delegate 来判断图片下载中、下载完成和下载失败。 1272 | 1273 | • connection:didReceiveData: 中利用 ImageIO 做了按图片下载进度加载效果。 1274 | 1275 | • connectionDidFinishLoading: 数据下载完成后交给 SDWebImageDecoder 做图片解码处理。 1276 | 1277 | • 图片解码处理在一个 NSOperationQueue 完成,不会拖慢主线程 UI。如果有需要对下载的图片进行二次处理,最好也在这里完成,效率会好很多。 1278 | 1279 | • 在主线程 notifyDelegateOnMainThreadWithInfo: 宣告解码完成,imageDecoder:didFinishDecodingImage:userInfo: 回调给 SDWebImageDownloader。 1280 | 1281 | • imageDownloader:didFinishWithImage: 回调给 SDWebImageManager 告知图片下载完成。 1282 | 1283 | • 通知所有的 downloadDelegates 下载完成,回调给需要的地方展示图片。 1284 | 1285 | • 将图片保存到 SDImageCache 中,内存缓存和硬盘缓存同时保存。 1286 | 1287 | • 写文件到硬盘在单独 NSInvocationOperation 中完成,避免拖慢主线程。 1288 | 1289 | • 如果是在iOS上运行,SDImageCache 在初始化的时候会注册notification 到 UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在内存警告的时候清理内存图片缓存,应用结束的时候清理过期图片。 1290 | 1291 | • SDWebImagePrefetcher 可以预先下载图片,方便后续使用。 1292 | 1293 | ### 消息转发三个步骤 1294 | 1295 | 动态方法解析 1296 | 1297 | ``` 1298 | 1299 | void functionForMethod1(id self, SEL _cmd) { 1300 | NSLog(@"%@, %p", self, _cmd); 1301 | } 1302 | + (BOOL)resolveInstanceMethod:(SEL)sel { 1303 | NSString *selectorString = NSStringFromSelector(sel); 1304 | if ([selectorString isEqualToString:@"method1"]) { 1305 | class_addMethod(self.class, @selector(method1), (IMP)functionForMethod1, "@:"); 1306 | } 1307 | return [super resolveInstanceMethod:sel]; 1308 | } 1309 | ``` 1310 | 1311 | 备用接收者 1312 | ``` 1313 | - (id)forwardingTargetForSelector:(SEL)aSelector { 1314 | NSLog(@"forwardingTargetForSelector"); 1315 | NSString *selectorString = NSStringFromSelector(aSelector); 1316 | // 将消息转发给_helper来处理 1317 | if ([selectorString isEqualToString:@"method2"]) { 1318 | return _helper; 1319 | } 1320 | return [super forwardingTargetForSelector:aSelector]; 1321 | } 1322 | ``` 1323 | 1324 | 1325 | 完整转发 1326 | 1327 | ``` 1328 | - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 1329 | NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; 1330 | if (!signature) { 1331 | if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) { 1332 | signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector]; 1333 | } 1334 | } 1335 | return signature; 1336 | } 1337 | - (void)forwardInvocation:(NSInvocation *)anInvocation { 1338 | if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) { 1339 | [anInvocation invokeWithTarget:_helper]; 1340 | } 1341 | } 1342 | ``` 1343 | 1344 | #### Swizzle 1345 | +load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。+load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证–事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用。 ----> 单利,饿汉式 --> java 的单利 static 1346 | 1347 | 因为swizzling会改变全局状态,所以我们需要在运行时采取一些预防措施。原子性就是这样一种措施,它确保代码只被执行一次,不管有多少个线程。GCD的dispatch_once可以确保这种行为,我们应该将其作为method swizzling的最佳实践。 1348 | 1349 | ``` 1350 | Selector(typedef struct objc_selector *SEL):用于在运行时中表示一个方法的名称。一个方法选择器是一个C字符串,它是在Objective-C运行时被注册的。选择器由编译器生成,并且在类被加载时由运行时自动做映射操作。 1351 | Method(typedef struct objc_method *Method):在类定义中表示方法的类型 1352 | Implementation(typedef id (*IMP)(id, SEL, ...)):这是一个指针类型,指向方法实现函数的开始位置。这个函数使用为当前CPU架构实现的标准C调用规范。每一个参数是指向对象自身的指针(self),第二个参数是方法选择器。然后是方法的实际参数。 1353 | 理解这几个术语之间的关系最好的方式是:一个类维护一个运行时可接收的消息分发表;分发表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个实现(IMP),即指向底层C函数的指针。 1354 | ``` 1355 | 1356 | 1357 | #### HTTPS 1358 | 1359 | #####客户端发起HTTPS请求 1360 | 1361 | 这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。 1362 | 1363 | #####服务端的配置 1364 | 1365 | 采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。 1366 | 1367 | #####传送证书 1368 | 1369 | 这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。 1370 | 1371 | #####客户端解析证书 1372 | 1373 | 这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。 1374 | 1375 | #####传送加密信息 1376 | 1377 | 这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。 1378 | 1379 | #####服务端解密信息 1380 | 1381 | 服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。 1382 | 1383 | #####传输加密后的信息 1384 | 1385 | 这部分信息是服务段用私钥加密后的信息,可以在客户端被还原 1386 | 1387 | #####客户端解密信息 1388 | 1389 | 客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。 1390 | 1391 | 1392 | ### ASPECT hook方法 面相切片编程 1393 | 1394 | 解决冲突 1395 | 1396 | 1397 | #### runloop 的作用 1398 | 常驻线程。要想runloop一直不退出,那么给他事情做就可以。可以给timer source 那么runloop就会一直工作。所以这就是常驻线程的背后原理 [runloop addport] 本质上就给添加source、那么线程就可以一直在了。达到了场主线程的目的 1399 | 1400 | 1401 | ### super 1402 | 特别注意,super 不是一个指针。self 是一个指针,super 只是编译指示器,就是给编译器看的,只要编译器看到super 这个标志,就会让当前对象调用父类的方法。 本质上还是self 调用的方法, 所以[super class] 返回的还是当前对象的类型 1403 | 1404 | 1405 | 1406 | 1407 | -------------------------------------------------------------------------------- /小知识点.md: -------------------------------------------------------------------------------- 1 | # iOS-Collection 2 | 个人对于iOS的总结 3 | 4 | **1. 第一响应者:叫出键盘的叫做第一响应者** 5 | 6 | ``` 7 | * 辞去第一响应者(退出键盘):[self.textView resignFirstResponder] 8 | * 成为第一响应者(交出键盘): [self.textView becomeFirstResponder] 9 | * 叫出键盘的另一种方式: - (BOOL)endEditing:(BOOL)force; // 让view 或者view的子控件辞去第一响应者的职务 10 | * 注意点:在iPad中退出键盘-->如果为formsheet 的话,那么直接退出键盘的话,是达不到效果的 11 | [searchBar endEditing:YES]; 12 | - (BOOL)disablesAutomaticKeyboardDismissal ->return NO; //需要实现这个方法 13 | Default implementation affects UIModalPresentationFormSheet visibility. 14 | ``` 15 | 16 | **2.UIView 的动画** 17 | ``` 18 | * 首尾式动画:注意,应该在开启动画之后立即设置动画的一些属性(时长,重复次数,代理放动画内部设置能够监听到动画的结束) 19 | [UIView beginAnimations:(NSString *) context:(void *)] -> 中间放执行动画的内容 void *(指向任何数据类型) --> id 20 | [UIView setAnimationDelegate:self]; // 可以监听到动画的开始和动画的结束 21 | [UIView commitAnimations]]; 22 | * block式动画:比较方便,可以延迟动画执行,监听到动画执行完之后做事情 23 | [UIView animateWithDuration: animations:^ { code}]; 24 | [UIView animateWithDuration: animations:<#^(void)animations#> completion:<#^(BOOL finished)completion#>]; 25 | ``` 26 | 27 | **3.枚举** 28 | ``` 29 | 如果多处代码大部分时相同的,只有在不同情况下那么少数代码不同,考虑抽取出方法出来,相同的代码放在方法中,不同的值当做参数。根据不同的情况需求(枚举类型)来做不同的处理 30 | ``` 31 | 32 | **4.UIView 的属性** 33 | ``` 34 | frame bounds center Transfrom 35 | * @property(nonatomic) CGRect frame; 36 | * 控件所在矩形框在父控件中的位置和尺寸(以父控件的左上角为坐标原点) 可以定义控件的位置(oarigin)和⼤小(size) 37 | * @property(nonatomic) CGRect bounds; 38 | * 控件所在矩形框的位置和尺寸(以⾃⼰左上角为坐标原点,所以bounds的x、y⼀一般为0) 可以定义控件的大小(size) 39 | * @property(nonatomic) CGPoint center; 40 | * 控件中点的位置(以父控件的左上角为坐标原点) 可以定义控件的位置(center) 41 | ``` 42 | 43 | ```objc 44 | Transfrom: 可修改控件的位移,缩放比例,旋转 45 | 46 | self.imgView.transform = CGAffineTransformMakeRotation(M_PI_4); // angle ->弧度制 47 | self.imgView.transform = CGAffineTransformRotate(self.imgView.transform, M_PI_4); 48 | self.imgView.transform = CGAffineTransformMakeScale(0.7, 0.7); 49 | self.imgView.transform = CGAffineTransformScale(self.imgView.transform, 0.7, 0.7); 50 | self.imgView.transform = CGAffineTransformMakeTranslation(-50, 100); 51 | self.imgView.transform = CGAffineTransformTranslate(self.imgView.transform, -50, 100); 52 | * 清空transform ->赋值为常量 CGAffineTransformIdentity 53 | * 使用心得:传入transform的值时,那么就会在上次transform的基础上做一些操作。在这种情况下时可以叠加的,如果不传入transform这个参数时,不能叠加。 make:只执行一次.在原来的基础上执行后,就不执行了 54 | 55 | ``` 56 | 57 | **5. 标准的枚举写法** 58 | ``` 59 | * 枚举类型本质上就是整数,定义的时候,如果只指定了第一个数值,后续的数值会依次递增 60 | typedef enum { 61 | kMovingDirTop = 10, 62 | kMovingDirBottom, // 11 63 | kMovingDirLeft, 64 | kMovingDirRight, 65 | } kMovingDir; 66 | ``` 67 | 68 | **6.图片浏览器Demo** 69 | ``` 70 | * 看好plist,plist的根节点是什么就创建什么。 71 | * KVC字典转模型 [self setValuesForKeysWithDictionary:dict]; 72 | * 懒加载(延迟加载)的好处 73 | 重写getter方法可以完成懒加载模式。代码的可读性强.注意循环引用的问题 74 | 用到时再加载,节省了不必要的内存资源,不需要关心什么时候去创建 75 | 只保证创建一次。内存中只有这一个对象.(如果经常用到UI控件,并且都是一样的,不需要重复创建,可以采取懒加载(特例UI控件用strong)) 76 | 懒加载要注意一个清空问题,如果你移除了UI控件,没有清空,但还要重新用重新创建,不清空有可能过不去创建的代码 77 | ``` 78 | 79 | **7.帧动画(针对imgView)** 80 | ``` 81 | 1.最重要的一点是清除缓存 82 | [self.imgView performSelector:@selector(setAnimationImages:) withObject:nil afterDelay:self.imgView.animationDuration + 1]; 83 | [UIImage imageWithContentsOfFile:] ->用图片在沙盒的路径加载,没有缓存(缓存会经过一些操作后清楚) 84 | [UIImage imageNamed:] -> 图片所占用的内存会一直在程序中,有缓存 85 | 2.%02d --> 不够两位补零 86 | ``` 87 | 88 | **8.Xcode 路径问题** 89 | ``` 90 | (iOS8开始 沙盒和bundle位置安装路径改变 :bundle.path打印一下就ok) 91 | 1. Xcode6 bundle 路径:/Users/apple/Library/Developer/CoreSimulator/Devices/45A2D2FE-E2F1-40B2-B0C6-679EB21E93CB/data/Containers/Bundle/Application/-----> Bundle 92 | 2. Xcode6 沙盒路径:Users/apple/Library/Developer/CoreSimulator/Devices/45A2D2FE-E2F1-40B2-B0C6-679EB21E93CB/data/Containers/Data/Application -----> Data/Application 93 | 94 | 3. Xcode文档安装路径 /Applications/Xcode.app/Contents/Developer/Documentation/DocSets 95 | 4. Xcode模拟器安装路径 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs 96 | 97 | 5. Xcode 插件路径/Users/sunhulin/Library/Application Support/Developer/Shared/Xcode/Plug-ins 如果插件失效,删除掉重新装,记住重启之后一定要选择loadBundles 98 | 6.xcode插件失效 解决命令行:获取当前环境下的UUID,添加到插件的plist文件中 99 | find ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins -name Info.plist -maxdepth 3 | xargs -I{} defaults write {} DVTPlugInCompatibilityUUIDs -array-add defaults read /Applications/Xcode.app/Contents/Info.plist DVTPlugInCompatibilityUUID 100 | 101 | 7.Xcode自带头文件的路径 102 | /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.1.sdk/System/Library/Frameworks/UIKit.framework/Headers 103 | * 修改了系统自带头文件后,Xcode会报错 104 | 解决方案:删掉下面文件夹的缓存即可(aplle是电脑的用户名) 105 | /Users/aplle/资源库/Developer/Xcode/DerivedData 106 | /Users/aplle/Library/Developer/Xcode/DerivedData 107 | 108 | ``` 109 | 110 | **9.@property 作用** 111 | ``` 112 | 生成getter方法 生成setter方法 113 | 生成带下划线的成员变量(记录属性内容) 114 | readonly的属性不会生成带下划线的成员变量!要想访问需要这样写{int _age} 这样访问 115 | 116 | ``` 117 | 118 | **10.@synthesize** 119 | ``` 120 | @synthesize合成指令,主动指定属性使用的成员变量名称 ->@synthesize image = _image; 121 | ``` 122 | 123 | **11.超级猜图Demo** 124 | ``` 125 | * 相框效果用按钮:设置contentInset-> background(背景)是白色 -> img 设置图片 126 | * [self.view bringSubviewToFront:self.imgView]; 把指定的View 放在上面,可见->遮盖的时候用 127 | * GCD dispatch_after 和 [self performSelector: withObject: afterDelay:];有延迟做一些事情的效果 128 | * view的封装,需要什么就传什么,但是考虑到代码的先后调用顺序 129 | * 宏的规范写法 #define kJNOptionBtnCount 21 130 | * 让数组中每个元素都执行某个方法,不需要遍历,快捷方法 131 | > [self.answerView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)] 132 | * 控件的懒加载,如果每次都要用,每次都要创建,考虑用懒加载。打印内存地址 133 | * [textView sizeThatFits:CGSizeMake(MAXFLOAT, FLT_MAX)] // 牛逼的方法 134 | * [self.tableView beginUpdates]; // 动态算高,textView有输入 135 | * [self.tableView endUpdates]; 136 | 137 | ``` 138 | 139 | ```objc 140 | 排序-> 方法与返回值,返回排过序的数组 141 | arr = [arr sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { 142 | return [obj1 compare:obj2]; // 返回的是blcok 的返回值 升序 143 | }]; 144 | ``` 145 | 146 | **12.UIScrollViewDemo** 147 | ``` 148 | * bounces:弹簧效果 scrollEnabled:能否滚动 show-:是否显示横竖滚动条 149 | * scrollView的方法很简单,只需要添加scrollView,并且设置contentSize 150 | * contentOffset:表示UIScrollView滚动的位置, 内容左上角和scrollView左上角的间距 151 | * contentInset: 表示能够在UIScrollView的4周增加额外的滚动区域 152 | * 如果在Storyboard中添加了ScrollView的子控件,要想scrollView滚动,必须取消autolayout) 153 | * scrollView实现缩放要设置图片缩放的最大最小尺寸,并且在viewForZoomingInScrollView 方法中返回需要缩放的View,知道缩放那个view才能缩放 154 | * 自带的方法 滚到某个位置 -(void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated; 155 | 156 | ``` 157 | 158 | **13.UITableView 多组汽车Demo** 159 | ``` 160 | *-(NSArray *) sectionIndexTitlesForTableView ->提示标题 161 | * 嵌套模型数组 :NSDictionary *di in dict[@"cars"] -->dict[@"cars"] 不能用self.cars 162 | * 滑动删除代理方法: commitEditingStyle 编辑样式 163 | * cell 重用原理 164 | 当滚动列表时,部分UITableViewCell会移出窗口,UITableView会将窗口外的UITableViewCell放⼊一个对象池中,等待重用。当UITableView要求dataSource返回UITableViewCell时,dataSource会先查看这个对象池,如果池中有未使用的UITableViewCell,dataSource会用新的数据配置这个UITableViewCell,然后返回给 UITableView,重新显示到窗口中,从⽽避免创建新对象 165 | * cell 重用解决方案 166 | UITableViewCell有个NSString *reuseIdentifier属性,可以在初始化UITableViewCell的时候传入⼀个特定的字符串标识来设置reuseIdentifier(一般 用UITableViewCell的类名)。当UITableView要求dataSource返回UITableViewCell时,先通过一个字符串标识到对象池中查找对应类型的UITableViewCell对象,如果有,就重用,如果没有,就传入这个字符串标识来初始化⼀个UITableViewCell对象 167 | 168 | * 为了避免为单元格重复分配内存空间,UITableView提供了循环引用机制 169 | dequeueReusableCellWithIdentifier可以使用指定的标示符在缓冲池中查找可重用Cell 170 | 如果没有找到cell再实例化cell 171 | 使用static修饰符可以保证字符串变量只被分配一次内存空间,在此可以与宏的方式进行一下对比 172 | ``` 173 | 174 | **14.UIAlertView** 175 | ``` 176 | * alertView.alertViewStyle 弹出样式 177 | * [alertView textFieldAtIndex:0] 获取文本框样式下的文本框 178 | * alertView.tag = indexPath.row;在Alert代理方法里用,用alertView.tag记录对应行号的模型,把上面的行数传递给下面 179 | * 内部有很多私有属性->通过KVC获取 180 | ``` 181 | 182 | **15.数据刷新** 183 | ``` 184 | 数据刷新 :永恒的两步走 185 | * 修改模型数据 186 | * 刷新表格-->重新加载数据 187 | 全局刷新:[self.tableView reloadData] 188 | 局部刷新[self.tableView reloadRowsAtIndexPaths: withRowAnimation:] 189 | 190 | ``` 191 | 192 | **16.表格处理** 193 | ``` 194 | * NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; // 获取选中哪一行的cell 195 | * 每个tableview都有自己的编辑样式,tableView.editing = YES,就会显示表格编辑界面 196 | * 实现(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath; 197 | > 如果没有实现-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath 这个代理方法,每个cell 默认的编辑样式都是删除样式。意味着可以滑动删除 198 | > 判断 editingStyle 是哪一种编辑样式,然后做对应的事情 199 | *> 添加 :添加数据,刷新表格 insertRowsAtIndexPaths 200 | *> 删除 :删除数据,刷新表格 deleteRowsAtIndexPaths 201 | 202 | * cell 交换位置代理方法 moveRowAtIndexPath 203 | > 表格交换位置 , 数据交换位置 204 | *>将源从数组中取出 id source = self.dataList[sourceIndexPath.row]; 205 | *>将源从数组中删除 [self.dataList removeObjectAtIndex:sourceIndexPath.row]; 206 | *>将源插入到数组中的目标位置 [self.dataList insertObject:source atIndex:destinationIndexPath.row]; 207 | 208 | ``` 209 | 210 | **17.自定义cell** 211 | ``` 212 | * [self.contentView addSubview:newView]; 如果想自定义cell,把要添加的view放在contentView中 213 | * 代码自定义在initWithStyle里面做一些初始化创建,属性设置的操作。尺寸统一在layoutSubviews里面 214 | * xib自定义在awakeFromNib里面设置 215 | * 动态高度 216 | > 返回cell高度的代理方法会在cell初始化之前调用. 217 | *> 解决办法: 218 | a. 设置两个模型,JNStatus(数据模型) 和JNStatusFrame(尺寸)模型。 219 | b. 在JNStatusFrame 的set status方法里面做拦截,计算所有子控件的尺寸。 220 | c. 在控制器中懒加载的时候 statusF.status = status; 尺寸模型就知道了一切。就早早的知道了高度. 221 | > 优化:如果摸个控件一直时一成不变的,那么考虑懒加载。节省内存 222 | > 数据和模型在一起。在模型里面加载数据.提供返回数据(加载plist)的方法。控制器值负责掉方法.大管家。不应该关心数据 MVC 223 | 224 | * 用于在自定义Cell时,Cell被选中后的处理 : -(void)setSelected:(BOOL)selected animated:(BOOL)animated 225 | 226 | * 问题 227 | 1> JNStatusFrame中的控件尺寸是根据内容计算的,外部不允许修改,应该设置为readonly 228 | 2> JNStatusFrame模型中已经包含了HMStatus模型数据,完全可以用来给Cell赋值,而无需再次创建 229 | 3> JNStatusFrame模型中已经计算好了每一个控件的尺寸,在Cell中应该可以直接使用,不需要再次计算 230 | 231 | 解决方法: 232 | 1> 将所有frame的属性设置为readonly 233 | 提示: 234 | (1) 如果不重写getter方法,则无需使用@synthesize指令,此时_的成员变量是存在的 235 | (2) 一旦重写了getter方法,带_的成员变量则会被覆盖 236 | 237 | 2> 添加类方法,从plist加载直接创建statusFrames的数据数组 +(NSArray *)statusFrames; // 不带参数的类工厂方法 238 | ``` 239 | 240 | **18.代理为什么用weak?** 241 | ``` 242 | 假设自定义控件CustomView有一个代理属性delegate, 他的代理是控制器,意味着控制器对CustomView 强引用。 如果代理时strong,那么会发生循环引用,CustomView 代理属性强引用控制器,控制器强引用CustomView ,循环引用,造成内存泄露,没法释放控制器和CustomView。 243 | ``` 244 | 245 | **19.QQ好友列表** 246 | ``` 247 | * 模型中 的数组里又存放模型 248 | > 代码 249 | -(instancetype)initWithDict:(NSDictionary *)dict; // 好友组:(name online friends(里面装的时好友)) 250 | { 251 | if (self == [super init]) { 252 | self.name = dict[@"name"]; 253 | self.online = dict[@"online"]; 254 | NSMutableArray *array = [NSMutableArray array]; 255 | // 遍历好友数组 256 | for (NSDictionary *di in dict[@"friends"]) { 257 | JNFriend *friend = [JNFriend friendWithDict:di]; 258 | [array addObject:friend]; 259 | } 260 | self.friends = array; // 面向模型开发 261 | } 262 | return self; 263 | } 264 | 265 | * BOOL的运用,标记一些状态 266 | > 两种状态取反 self.group.open = !self.group.isOpen; 267 | > 心得: 268 | 1> 一般用在控制器中拥有它,在某处置NO,那么在相反的情况出置YES,保证具体情况的准确性 269 | 2> 存在模型中,记录特殊情况下的状态,根据YES\NO 来做对应的操作 270 | 271 | *block 的引用 view --> 控制器 272 | 1> typedef void(^headVewBlock)(id); // 参数传id 类型 273 | 2> 在某个view中拥有属性 @property(nonatomic,copy)headVewBlock block; 274 | 3> 在view里面 block 传值,通知做事情 275 | if (self.block) { 276 | self.block(@"block 传值"); // 在内部的blcok里面传值,外面做block里事情 277 | } 278 | 4> 在外面。控制器 279 | view.block= ^(id sender){ 280 | /** 要做的事情,在控制器里面*/ 281 | NSLog(@"---%@",sender); 282 | }; 283 | 284 | 5> 内存泄漏问题 285 | __weak JNViewController *weakSelf = self; // 注意这里需要* 286 | __weak typeof(self) weakSelf = self; //这里不需要* 287 | 288 | * UITableViewHeaderFooterView 也是封装好的,继承就好了 289 | > headerView循环使用,循环使用的机制和cell 类似 290 | 291 | * 小三角形转动的问题 292 | self.headBtn.imageView.contentMode = UIViewContentModeCenter; 293 | self.headBtn.imageView.clipsToBounds = NO; 超出控件边框范围的内容都剪掉 294 | 295 | * UIView 方法 296 | /**系统自动调用(留给子类去实现)**/ 297 | - (void)didAddSubview:(UIView *)subview; 298 | - (void)willRemoveSubview:(UIView *)subview; 299 | - (void)willMoveToSuperview:(UIView *)newSuperview; 300 | 301 | // 当一个控件添加到父控件中系统自动调用。(例子:QQ箭头) 302 | - (void)didMoveToSuperview; 303 | - (void)willMoveToWindow:(UIWindow *)newWindow; 304 | - (void)didMoveToWindow; 305 | /**系统自动调用**/ 306 | ``` 307 | 308 | **20.runloop** 309 | ``` 310 | * 每个线程都有一个runloop对象. 311 | * 作用:程序一启动,在主线程中就开启(runloop)消息循环,保证程序的运行 312 | * runloop 有一个输入源(input source)处理异步消息的,里面有个port的线程接口,用来处理其他线程的消息(performSelector: on thread:) 313 | * 还有一个Timersource(用来定时接受处理一些主线程的ui事件)处理同步消息的; 314 | ``` 315 | 316 | **21.copy** 317 | ``` 318 | *堆:由编译器自动分配并释放,一般存放函数的参数值,局部变量等,代码块一过,会自动释放 319 | *栈:由程序员负责管理,如果程序员不释放,操作系统会自动释放 320 | * 内存管理 321 | > 内存管理范围:只有oc对象才进行内存管理,基本数据类型不需要。 322 | > 原因:oc对象存放在堆中,内存管理由程序员来管理,而基本数据类型存放在栈中,栈内存系统会自动回收. 323 | *> 指针变量也存放在栈内存中.但指针变量指向的对象,存放在堆中[[Car alloc]init];alloc ->分配内存空间 init -> 值得一些初始化 324 | *> 根据引用计数器(有多少人引用这个对象)来判断对象是否应该被回收,每一个oc对象内部都会分配4个字节的存储空间来存放引用计数器 325 | *> block的copy策略 326 | * 1.block 只能用copy 策略,相当于strong 327 | * 2. 栈内存: 自动释放 328 | * 3.堆内存 :释放交给程序员管理 329 | * 4.如果没有对block进行copy操作,block默认存储于栈空间 330 | * 5.如果对block进行copy操作,block就存储于堆空间 331 | * 6.如果block存储于栈空间,不会对block内部所用到的对象产生强引用 332 | * 7.如果block存储于堆空间,就会对block内部所用到的对象产生强引用 333 | * 8.栈时轻量级的,没有copy就在栈,不会发生强应用 334 | * clang -rewrite-objc main.m 可以看出block 的底层实现 335 | 336 | * 深复制:复制的是内容,即复制出来的是新对象 337 | * 浅复制: 复制的是指针。并没有复制出新对象,只是复制的指针引用了这个对象 338 | * copy 和strong 的区别 339 | > 区别在于你是否想别人改变你的属性,如果是copy的话,那么再属性的setter 方法里面拷贝的副本出来,意味着,你只是修改拷贝出来的副本的属性,而不是自身的属性,而strong是可以修改自身的属性,因为修改的不是副本而是本身,是否用copy 取决于需求 340 | 341 | * UI控件为什么用weak? 342 | > 因为控制器有一个强指针引用这自己的view (self.View) View 有一个属性强引用着(self.subview)子控件数组。所以,当UI控件加入到self.view 的时候,子控件数组就有一个强指针引用这UI控件,所以,控制器没有必要对UI控件强引用,因为有self.subview 343 | ``` 344 | 345 | **21.QQTalkDemo** 346 | ``` 347 | tableview 小细节 348 | * self.tableView.separatorStyle =UITableViewCellSeparatorStyleSingleLineEtched; 349 | * self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; 拖拽tableView就会隐藏键盘 350 | * self.tableView.allowsSelection = NO; //不让选中 351 | * 手势注意点:如果在一个可以滚动的view中,比如只能上下滚动的scrollView中添加上下轻扫的手势监听,这个事件会被scrollview 352 | * 键盘处理:->UIKeyboardWillShowNotification 监听键盘的通知 353 | * 自定义键盘:self.inputField.inputView :(可以时datePick,等pickView) 354 | * 键盘的工具条:inputField.inputAccessryView (ToolBar一般放在键盘里面的,只能加BarButtonItem) 355 | > 换掉系统自带的键盘之后一定要退出键盘之后在弹出键盘 356 | > CGRect frame = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue]; 357 | > UITextField 的leftView 可以设置自定义的view,要想显示,必须要设置leftViewMode 358 | ``` 359 | 360 | **22.pickerViewDemo** 361 | ``` 362 | * - (void)selectRow:(NSInteger)row inComponent:(NSInteger)component animated:(BOOL)animated 363 | * numberOfComponentsInPickerView // 有多少组: 364 | * numberOfRowsInComponent // 每组有多少航 365 | * didSelectRow // 选中了某一行 366 | * titleForRow // 为某一行设置标题 367 | * viewForRow // 自定义View 368 | ``` 369 | 370 | **23.autolayout** 371 | ``` 372 | iPhone4S(3.5英寸) 320 * 480 373 | iPhone5 5S(4英寸) 320 * 568 374 | iPhone6(4.7英寸) 375 * 667 375 | iPhone6plus(5.5英寸) 414 * 736 376 | 377 | * autolayout 和autoresizing 在storyboard中只能选中其一 378 | * autoresizing 的不足:autoresizing只满足于父子关系的view的情况,但是满足不了相同层级view的约束(iPad开发用到) 379 | * 满足不了两个view之间的间距是固定的需求 380 | UIViewAutoresizingNone 没有约束 381 | UIViewAutoresizingFlexibleLeftMargin 左边可以伸缩,右边是固定的 382 | UIViewAutoresizingFlexibleRightMargin 右边可以伸缩,左边是固定的 383 | UIViewAutoresizingFlexibleTopMargin 顶部可以伸缩,底部是固定的 384 | UIViewAutoresizingFlexibleBottomMargin 底部可以伸缩,顶部是固定的 385 | UIViewAutoresizingFlexibleWidth 会随着父控件的尺寸改变而做相应的宽度改变 386 | UIViewAutoresizingFlexibleHeight 会随着父控件的尺寸改变而做相应的高度改变 387 | 388 | * autolayout(最好给view起别称,方便参照) 389 | * 取消lauanch screen 的xib -> 项目配置-> 清楚launch screen file->launch images source(use asset catalog)->migarte 390 | * autolayout细节 391 | > 用了autolayout,那么不需要再设置frame,但是用了autolayout必须要设置:宽 高 x y 对应的约束 392 | > 黄色警告: 代表控件当前的约束和实际的位置不匹配-> 一般是update frame 393 | > 红色错误: 缺少某个frame 的尺寸约束 或者是两个约束的冲突,这里约束宽度100 和那里约束宽度110 之间的约束冲突 394 | > top layout guide: 代表上面20的状态栏 395 | > constrians to margins ->iOS8开始 系统从内部调节,为了视图美观,让控制器的view留一点间距。 396 | > 不要装逼,一下子拖四五个view来搞。约束多,线太多很复杂!两个两个慢慢搞 397 | 398 | *全屏图片适配技巧:首先图片的上下部分空出一些不重要的部分,设置图片水平垂直居中,然后设置图片左右间距为0,这样就能保证不管大小的屏幕都能让图片居中显示,头尾两边不重要的部分按情况显示 399 | * 如果一个控件,默认有内容,autolayout会默认算出尺寸 400 | * autolayout 动画:约束 连线,然后改变约束的constant 值,通过动画显示,注意:修改约束的值在动画内动画外写都是可以的,但要注意[self.view layoutIfNeed]self.view的拥有的约束。 401 | * 代码实现autolayout 402 | 公式:view1.attr1 = view2.attr2 * multiplier + contant 403 | button.left = self.view.left*0 + 20 404 | button.right = self.view.width*1 + (-20) 405 | 注意:translateAutoresizingToConstraints这个属性默认是YES,这个属性代表:将视图默认自带的Auto resizing特性是否自动转换为对应的约束。既然使用代码来创建约束,那么就不要让系统自带的转换过来的约束影响添加的自定义约束,所以该属性要设置为NO,为了保证效果,可以将视图以及视图的父视图的该属性都设置为NO 406 | ``` 407 | 408 | **24.Xocde6新特性** 409 | ``` 410 | * 在新建项目的时候,少了empty空项目的选项 411 | * 项目里面没有了pch文件,需要自己编写导入(build Setting 里面设置 profiex header->X6Test/profiex header.pch) 412 | *Xcode6 取消类前缀,因为swift语言有命名空间这一说,类似于java的包。所以取消了类前缀,配置类前缀的方法(工程 - targets --class prefix) 413 | * 工程新增了一个xib文件:LaunchScreen.xib,顾名思义,当程序启动的时候会调用到,和之前的Launch Image类似,这个xib用起来会更加的灵活.LaunchScreen.xib可以拖控件表示显示启动画面,不能设置file‘s owner 不能处理点击事件 414 | * 在Images.xcassets新增了iPhone6和iPhone6+的启动图片和iPhone6+的横屏图片,Xocde6 直接支持jpg图片格式,并且能够像设置sb一样,设置图片的宽高(与sizeClass 类似)能够调整不同状态下显示不同图片 *表示任意的 - 表示压缩 + 表示正常 415 | * Xocde6的控制器变成豆腐干形状,范围很大,是为了方便同时适配iPad 和iPhone,提供预览功能 416 | * sizeClass 的引入。控制不同状态下,显示不同空间,有着类似revel的效果 417 | ``` 418 | 419 | **25.iOS8新特性** 420 | ``` 421 | 最大的特色是为了统一,安全(隐私的授权) 422 | UIAlertController 423 | * UIPopoverPresentationController ->一定要先设置second.modalPresentationStyle = UIModalPresentationPopover;在设置sourceView 424 | * UIPresentationController: 管理所有通过modal 出来的控制器 425 | > 管理所有调用了-(void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion 方法的控制器 426 | > UIPresentationController 的属性 p.presentedViewController p.presentingViewController 427 | * stroyBoard的seague 有所改变,show(push) 428 | 429 | * UIPresentationController 430 | 1> 管理所有Modal出来的控制器 431 | 2> 管理所有通过- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)flag completion:(void (^)(void))completion方法显示出来的控制器 432 | 3> 管理\监听切换控制器的过程 433 | 4> presentingViewController:后面的控制器 434 | 5> presentedViewController:前面的控制器 435 | 6> presentedView:前面的控制器的view 436 | 437 | 屏幕适配 438 | 1> 发展历程 439 | 代码计算frame -> autoreszing(父控件和子控件的关系) -> autolayout(任何控件都可以产生关系) -> sizeclass 440 | 441 | 2> sizeclass 442 | * 仅仅是对屏幕进行了分类, 真正排布UI元素还得使用autolayout 443 | * 不再有横竖屏的概念, 只有屏幕尺寸的概念 444 | * 不再有具体尺寸的概念, 只有抽象尺寸的概念 445 | * 把宽度和高度各分为3种情况 446 | 1) Compact : 紧凑(小) 447 | 2) Any : 任意 448 | 3) Regular : 宽松(大) 449 | 4) 符号代表 450 | - : Compact 451 | * : Any 452 | + : Regular 453 | 5) 继承性 454 | * * : 其它8种情况都会继承 455 | * - : 会被- - \ + -继承 456 | + * : 会被+ - \ + +继承 457 | 6) sizeclass和autolayout的作用 458 | sizeclass:仅仅是对屏幕进行了分类 459 | autolayout:对屏幕中各种元素进行约束(位置\尺寸) 460 | 461 | ``` 462 | 463 | **25.程序启动原理** 464 | ``` 465 | 1.执行main函数 466 | 2.执行UIApplicationMain 函数 467 | * 创建UIApplication对象 468 | * 创建UIApplication的delegate对象 469 | 3.delegate对象开始处理(监听)系统事件 ->(没有storyboard情况 ) 470 | * 程序启动完毕的时候, 就会调用代理的application:didFinishLaunchingWithOptions:方法 471 | * 在application:didFinishLaunchingWithOptions:中创建UIWindow 472 | * 创建和设置UIWindow的rootViewController 473 | * 显示窗口 474 | 475 | 3.(没有storyBoard)根据Info.plist获得最主要storyboard的文件名,加载最主要的storyboard 476 | * 创建UIWindow 477 | * 创建和设置UIWindow的rootViewController 478 | * 显示窗口 479 | applicationDelegate 480 | > applicationWillResignActive 程序失去焦点 481 | > applicationDidEnterBackground 程序进入后台 482 | > applicationWillEnterForeground 程序即将进入前台 483 | > applicationDidBecomeActive 程序获得焦点 484 | > applicationWillTerminate 程序即将退出 485 | * 4. 结束程序 程序正常退出,UIApplicationMain 函数才会返回 486 | 487 | ``` 488 | 489 | **26.控制器的生命周期** 490 | ``` 491 | * loadView -> viewWillAppear -> viewDidLoad -> viewDidAppear -> viewWillDisappear ->viewDidDisappear 492 | -->(收到内存警告) ->didReceiveMemoryWarning ->viewWillUnload ->(销毁view)->viewDidUnload--(如果需要再次显示这个view的时候)-->loadView 493 | 494 | * loadView方法只会调用一次,换掉系统的view能用到 495 | * 控制器的view创建采用懒加载(延迟加载)方式,用到了我才创建 496 | * viewDidUnload 里面做了什么事情 497 | * 一般用来内存管理,因为view已经不再了,那么里面的数据都应该清空 498 | -(void)viewDidUnload{ 499 | [super viewDidUnload]; 500 | self.apps = nil; // 清空指针。控制器不会报错 501 | self.persons = nil; 502 | // [self.apps release]; // 内存管理 503 | // [self.persons release]; 504 | } 505 | 506 | * [super didReceiveMemoryWarning]做了什么事情 507 | > didReceiveMemoryWarning --> 系统会查看是否有一个view不用了(Is there a view?) 508 | > YES -> 这个view能不能被销毁(can it be released?) 509 | > YES ->viewWillUnload-->release the view (view== nil ,(outlet)subview == nil) 510 | > viewDidUnload 511 | ``` 512 | 513 | **27.私人通讯录Demo** 514 | ``` 515 | * 监听文本框TextField文字改变的三种方法:代理,通知 [textField addTarget:](UIControlEventEditingChanged)继承UIControl 516 | * 自动型segue ->自动跳转 517 | * 手动型segue -> 要判断是否符合条件,然后执行segue (手动型segue必须要设置segue 的标示) 518 | > [self performSegueWithIdentifier:@"login2contact" sender:@"testSeague"]; 两个sender是一样的 519 | > -(void)prepareForSegue: sender:这个方法必须在来源控制器里执行 执行跳转的一些准备工作,传值什么的 520 | > segue的三个属性: 目标控制器 ,来源控制器,id标示 521 | * 导航控制器pop的话,应该从栈中取出来(self.navigationController.viewControllers) 在pop,而不是创建alloc init 522 | * 修改导航栏 (需要在info.plist配置 View controller-based status bar appearance == NO) 523 | * 自定义分割线: 要想自定义分割线必须要自定义cell 524 | * 设置UIView背景色为黑色,设置透明度。 525 | * 在layoutSubviews里面设置尺寸(在awakeFromNib方法里设置不准确) 526 | ``` 527 | 528 | ***item*** 529 | ```objc 530 | self.navigationController.navigationBar.barStyle = UIStatusBarStyleDefault; 531 | // 修改了UIBarButtonItem 文字的颜色 箭头颜色 532 | [self.navigationController.navigationBar setTintColor:[UIColor redColor]]; 533 | UIBarButtonItem *backItem = [[UIBarButtonItem alloc]init]; 534 | self.navigationItem.backBarButtonItem = backItem; 535 | 536 | ``` 537 | 538 | **28.数据存储** 539 | ``` 540 | plist 偏好设置 NSCoding归档 541 | * plist 文件存储:能存储一些普通对象(字典,数组,NSString,NSNumber等,不能存储自定义对象,比如person) 542 | > 存 [dict writeToFile:path atomically:YES]; (atomically:原子性操作YES,比较安全,先创建临时文件,直到完全写入才导入) 543 | > 取 NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:path]; 544 | * 偏好设置(preference) 545 | > 存:NSUserDefaults *dfts = [NSUserDefaults standardUserDefaults]; 546 | [dfts setObject:@"江南" forKey:@"name"]; // 用一个key来存储对象,int,double,bool等 547 | [dfts synchronize]; // 必须要发出同步命令,否则取数据的时候不准确 548 | > 取:NSUserDefaults *dfts = [NSUserDefaults standardUserDefaults]; 549 | NSString *name = [dfts objectForKey:@"name"]; 550 | 551 | * NSCoding:能够存自定义对象,存取方法暴力。(注意一定要调用父类的方法) 552 | > 存取的对象要 遵守NSCoding 协议、以JNStudent为例(内部存取实现) 553 | // 每次从文件中恢复(解码)对象时,都会调用这个方法。制定如何解码文件中的数据为对象的实例变量 554 | - (id)initWithCoder:(NSCoder *)decoder 555 | { 556 | if ([super initWithCoder:decoder]) { // 有继承的话,要调用父类的方法,那么就能存下来 557 | self.no = [decoder decodeIntForKey:@"no"]; 558 | self.school = [decoder decodeObjectForKey:@"school"]; 559 | self.score = [decoder decodeIntForKey:@"score"]; 560 | } 561 | return self; 562 | } 563 | // 每次归档对象时都会调用这个方法,制定如何归档对象中的实例变量, 564 | - (void)encodeWithCoder:(NSCoder *)encoder 565 | { 566 | [super encodeWithCoder:encoder]; // 有继承,用到父类的属性的话,一定要调用父类的方法,这样才能存取到父类的属性 567 | [encoder encodeObject:self.school forKey:@"school"]; 568 | [encoder encodeInt:self.no forKey:@"no"]; 569 | [encoder encodeInt:self.score forKey:@"score"]; 570 | } 571 | 572 | >再外面存: 573 | /* 574 | + (NSData *)archivedDataWithRootObject:(id)rootObject; 575 | + (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path; 576 | */ 577 | JNStudent *stu = [JNStudent studentWithNo:10 score:100 school:@"北京大学"]; 578 | [NSKeyedArchiver archiveRootObject:stu toFile:JNStuPath];JNStuPath一般写成宏,这样存取方便 579 | 580 | >再外面取:存是什么对象,取就是什么对象 581 | + (id)unarchiveObjectWithData:(NSData *)data; 582 | + (id)unarchiveObjectWithFile:(NSString *)path; 583 | JNStudent *stu = [NSKeyedUnarchiver unarchiveObjectWithFile:JNStuPath]; 584 | * 数据存储的注意点:只要数据修改(添加,修改,删除),就要保存数据。 一般配合工具类来一起使用 585 | ``` 586 | 587 | **29.Quartz2D** 588 | ``` 589 | * 要用Quartz2D绘图,需要自定义View,在View 的-drawRect方法里面做一些画图操作,drawRect方法系统会默认只调用一次。但是,如果这个自定义View没有尺寸Frame的话,这个方法就不会被调用 590 | * [self setNeedsDisplay] 重绘 591 | * 底层基于c语言的api. [UIBezierPath bezierPathWith..] 利用贝塞尔路径封装了一些基本图形的api 592 | * API 593 | > CGContextRef ctx = UIGraphicsGetCurrentContext(); 获得图形上下文 594 | > CGContextMoveToPoint(ctx, 10, 10); 拼接路径(下面代码是搞一条线段) 595 | > CGContextAddLineToPoint(ctx, 100, 100); 596 | 597 | > CGContextAddRect(CGContextRef c, CGRect rect) 添加一个矩形> 598 | > CGContextAddEllipseInRect(CGContextRef context, CGRect rect) 添加一个椭圆 599 | (x,y)原点 radius:半径 startAngle:开始起点角度,注意要传弧度值 clockwise:0顺时针 1逆时针 600 | > CGContextAddArc(CGContextRef c, CGFloat x, CGFloat y,CGFloat radius, CGFloat startAngle, CGFloat endAngle, int clockwise)添加一个圆弧 601 | > CGContextStrokePath(ctx); // CGContextFillPath(ctx);绘制路径 602 | 603 | > CGContextSaveGState(CGContextRef c) 将当前的上下文copy一份,保存到栈顶(那个栈叫做”图形上下文栈”) 604 | > CGContextRestoreGState(CGContextRef c) 将栈顶的上下文出栈,替换掉当前的上下文 605 | 606 | * 矩阵操作 607 | > CGContextScaleCTM(CGContextRef c, CGFloat sx, CGFloat sy) 缩放 608 | > CGContextRotateCTM(CGContextRef c, CGFloat angle) 旋转 609 | > CGContextTranslateCTM(CGContextRef c, CGFloat tx, CGFloat ty)平移 610 | 611 | * 图片水印 612 | > UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) 开启一个基于位图的图形上下文 613 | > UIGraphicsGetImageFromCurrentImageContext(); 从上下文中取得图片(UIImage) 614 | > UIGraphicsEndImageContext(); 结束基于位图的图形上下文 615 | > NSData *data = UIImagePNGRepresentation(newImg);(图片不能写入文件,转换成二进制数据写入文件) 616 | 617 | * 图片裁剪 618 | > CGContextClip(CGContextRef c) 将当前上下所绘制的路径裁剪出来(超出这个裁剪区域的都不能显示) 619 | > 思路: 先画大圆 -> 再画小圆 -> clip -> 画图片 620 | > CGContextClip的意思是:前面画完的没有裁剪,clip代码一过,对后面的有裁剪效果,这就是为什么 小圆裁剪之后为什么还有外边框 621 | 622 | CGContextAddArc(ctx, centre.x, centre.y, bigRadius, 0, M_PI * 2, 0); // 画大圆 623 | [[UIColor redColor]set]; 624 | CGContextFillPath(ctx); // 大圆画完之后马上就要要渲染出来 625 | CGContextAddArc(ctx, centre.x, centre.y, bigRadius - border, 0, M_PI * 2, 0); // 画小圆 626 | CGContextClip(ctx); // 因为之前的都已经渲染画出去了,所以没有被剪掉,裁剪只针对后面 627 | [oldImg drawInRect:CGRectMake(border, border, oldImgW, oldImgW)]; // 画图片 628 | UIImage *newImg = UIGraphicsGetImageFromCurrentImageContext(); 629 | 630 | *截图-> 开启位图上下文,获取图片(注意针对图层操作) 631 | > - (void)renderInContext:(CGContextRef)ctx; 调用某个view的layer的renderInContext:方法即可 632 | ``` 633 | 634 | 635 | **30.事件处理** 636 | ``` 637 | * 638 | UIView是UIResponder的子类,可以覆盖下列4个方法处理不同的触摸事件 639 | 一根或者多根手指开始触摸view,系统会自动调用view的下面方法 640 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 641 | 642 | 一根或者多根手指在view上移动,系统会自动调用view的下面方法(随着手指的移动,会持续调用该方法) 643 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 644 | 645 | 一根或者多根手指离开view,系统会自动调用view的下面方法 646 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 647 | 648 | 触摸结束前,某个系统事件(例如电话呼入)会打断触摸过程,系统会自动调用view的下面方法 649 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 650 | 651 | 提示:touches中存放的都是UITouch对象 一根手指对应一个UITouch对象 652 | 当用户用一根手指触摸屏幕时,会创建一个与手指相关联的UITouch对象 653 | 654 | UITouch的作用 655 | 保存着跟手指相关的信息,比如触摸的位置、时间、阶段 656 | 当手指移动时,系统会更新同一个UITouch对象,使之能够一直保存该手指在的触摸位置 657 | 当手指离开屏幕时,系统会销毁相应的UITouch对象 658 | 659 | 660 | 触摸产生时所处的窗口@property(nonatomic,readonly,retain) UIWindow *window; 661 | 662 | 触摸产生时所处的视图@property(nonatomic,readonly,retain) UIView *view; 663 | 664 | 短时间内点按屏幕的次数,可以根据tapCount判断单击、双击或更多的点击 665 | @property(nonatomic,readonly) NSUInteger tapCount; 666 | 667 | 记录了触摸事件产生或变化时的时间,单位是秒 668 | @property(nonatomic,readonly) NSTimeInterval timestamp; 669 | 670 | 当前触摸事件所处的状态 671 | @property(nonatomic,readonly) UITouchPhase phase; 672 | 673 | 返回值表示触摸在view上的位置 locationInView 674 | 这里返回的位置是针对view的坐标系的(以view的左上角为原点(0, 0)) 675 | 调用时传入的view参数为nil的话,返回的是触摸点在UIWindow的位置 676 | 677 | 该方法记录了前一个触摸点的位置 previousLocationInView:(UIView *)view; 678 | 679 | UIEvent:称为事件对象,记录事件产生的时刻和类型,每产生一个事件,就会产生一个UIEvent对象 680 | 681 | 事件类型 682 | @property(nonatomic,readonly) UIEventType type;// 转场动画类型 683 | @property(nonatomic,readonly) UIEventSubtype subtype; // 方向 684 | 685 | 事件产生的时间 686 | @property(nonatomic,readonly) NSTimeInterval timestamp; 687 | 688 | UIEvent还提供了相应的方法可以获得在某个view上面的触摸对象(UITouch) 689 | 4个触摸事件处理方法中,都有NSSet *touches和UIEvent *event两个参数 690 | 一次完整的触摸过程中,只会产生一个事件对象,4个触摸方法都是同一个event参数 691 | 如果两根手指同时触摸一个view,那么view只会调用一次touchesBegan:withEvent:方法,touches参数中装着2个UITouch对象 692 | 如果这两根手指一前一后分开触摸同一个view,那么view会分别调用2次touchesBegan:withEvent:方法,并且每次调用时的touches参数中只包含一个UITouch对象 693 | 根据touches中UITouch的个数可以判断出是单点触摸还是多点触摸 694 | 695 | * 如果用到多条线的话,那么考虑多用贝塞尔曲线。一个贝塞尔曲线就相当于一条线里面点的数组,避免了一层数组的操作 696 | * 如果贝塞尔曲线有各自自己的属性,那么考虑继承,创建一个类方法,创建的时候就有了属性 697 | * UIImageWriteToSavedPhotosAlbum -> 写入图片(监听的代理方法用系统推荐的方法) 698 | * 不要一味的封装,要想自定义控件变得牛逼灵活,要设计一些灵活好用的接口给别人调用. 面向对象的设计 699 | 700 | * 事件的产生和传递 701 | • 发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中 702 | • UIApplication会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常,先发送事件给应用程序的主窗口(keyWindow) 703 | • 主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件,这也是整个事件处理过程的第一步 704 | • 找到合适的视图控件后,就会调用视图控件的touches方法来作具体的事件处理 705 | > 注意点: 如果父控件不能接收到触摸事件,那么,子控件就不可能接收到事件 706 | > 还有一种情况,如果你想父控件接收事件,注意不要让这个子控件是触摸事件吞掉(UIView里面的按钮,设置按钮不可交互) 707 | 708 | * 不接收事件的三种情况 709 | 不接收用户交互 userInteractionEnabled = NO 710 | 隐藏 hidden = YES 711 | 透明 alpha = 0.0 ~ 0.01 712 | 提示:UIImageView的userInteractionEnabled默认就是NO,因此UIImageView以及它的子控件默认是不能接收触摸事件的 713 | 714 | * 事件的完整处理过程 715 | 1. 先将事件对象由上往下传递(由父控件传递给子控件),找到最合适的控件来处理这个事件 716 | 2.调用最合适控件的touches...方法 717 | 3. 如果调用了[super touches..],就会将事件顺着相应这链条往上传递,传递给上一个响应者 718 | 4.接着就会调用上一个响应者的 touches..方法 719 | 720 | * 响应者链条 721 | 1.响应者链条是由多个响应者对象连接起来的链条(什么是响应者对象:能够处理事件的对象) 722 | 2.利用响应者链条,能让多个控件处理同一个触摸事件 723 | 3.谁是上一个响应者 724 | > 如果当前这个view是控制器的view,那么控制器就是上一个响应者 725 | > 如果当前这个view不是控制器的view,那么父控件是上一个响应者 726 | 727 | * 响应者链的事件传递过程 728 | 1.如果view的控制器存在,就传递给控制器;如果控制器不存在,则将其传递给它的父视图 729 | 2.在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理 730 | 3.如果window对象也不处理,则其将事件或消息传递给UIApplication对象 731 | 4.如果UIApplication也不能处理该事件或消息,则将其丢弃 732 | 733 | 事件处理底层的核心方法 734 | 735 | -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event 736 | -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event 737 | * 方法调用过程 738 | * 1. 当有了一个touchesBegan事件,那么,系统会遍历所有显示在屏幕上的子控件(从window开始一层一层遍历),调用pointInside方法,询问,那个触摸点是不是你的。直到遍历到了控件返回了YES,停止遍历,找到了发生触摸事件的控件 739 | * 2.然后调用hitTest方法,询问你,这事件是你的,你准备交给谁处理。返回的view就是处理事件的view 740 | * 总之要想来到一个控件的hitTest方法,那么这个控件的pointInside一定是返回YES。因为pointInside是第一步 741 | * 如果没有实现这两个方法,那么系统更具响应者链条来处理事件 742 | 743 | ``` 744 | 745 | **31.CALayer** 746 | ``` 747 | > CALayer负责视图中显示的内容和动画 748 | > UIView负责监听和响应事件 749 | > 属性:圆角、边框、阴影及3D形变 750 | 注意:UIImageView中不仅一个子图层,因此设置圆角时需要使用setMasksToBounds:YES,让所有子图层跟随边框,不过设置该属性后,无法使用阴影效果 751 | 解决办法:可以在底层附加一个UIView实现阴影效果 752 | 753 | @property CGRect bounds; 宽度和高度 754 | @property CGPoint position; 位置(默认指中点,具体由anchorPoint决定) 755 | @property CGPoint anchorPoint; 锚点(x,y的范围都是0-1)layer最重要的属性:决定layer身上的哪一个点放在pos那个点上,默认(0.5,0.5) 756 | @property CGColorRef backgroundColor; 背景颜色(CGColorRef类型) 757 | @property CATransform3D transform; 形变属性 758 | @property CGColorRef borderColor; 边框颜色(CGColorRef类型) 759 | @property CGFloat borderWidth; 边框宽度 760 | @property CGFloat cornerRadius; 圆角半径 761 | @property(retain) id contents; 内容(比如设置为图片CGImageRef) 762 | imgLayer.contents = (__bridge id)[UIImage imageNamed:@"阿狸头像"].CGImage; // 桥接转换 763 | @property CGColorRef shadowColor; 764 | @property float shadowOpacity; 阴影不透明(0.0 ~ 1.0) 765 | @property CGSize shadowOffset; 阴影偏移位置 766 | 767 | > 隐式动画 768 | 每一个UIView内部都默认关联着一个CALayer,称这个Layer为Root Layer。所有的非Root Layer都存在着隐式动画,隐式动画的默认时长为1/4秒。 769 | 当修改非Root Layer的部分属性时,相应的修改会自动产生动画效果,能执行隐式动画的属性被称为“可动画属性”,诸如: 770 | bounds: 缩放动画 771 | position: 平移动画 772 | opacity: 淡入淡出动画(改变透明度) 773 | 在文档中搜素animatable可以找到所有可动画属性 774 | 775 | 如果要关闭默认的动画效果,可以通过动画事务方法实现: 776 | [CATransaction begin]; // 取消系统默认的效果 777 | [CATransaction setDisableActions:YES]; 778 | // ... 779 | [CATransaction commit]; 780 | 781 | > 要在CALayer上绘图,有两种方法: 782 | 1.创建一个CALayer的子类,然后覆盖drawInContext:方法,可以使用Quartz2D API在其中进行绘图 783 | 2.设置CALayer的delegate,然后让delegate实现drawLayer:inContext:方法进行绘图 784 | 785 | 注意:不能再将UIView设置为这个CALayer的delegate,因为UIView对象已经是内部层的delegate,再次设置会出问题 786 | 无论使用哪种方法,都必须向层发送setNeedsDisplay消息,以触发相应绘图方法的调用 787 | ``` 788 | 789 | **32.核心动画** 790 | ``` 791 | * 开发步骤: 792 | 1.初始化一个动画对象(CAAnimation)并设置一些动画相关属性 793 | 2.CALayer中很多属性都可以通过CAAnimation实现动画效果,包括:opacity、position、transform、bounds、contents等(可以在API文档中搜索:CALayer Animatable Properties) 794 | 3.添加动画对象到层(CALayer)中,开始执行动画 795 | 4.通过调用CALayer的addAnimation:forKey增加动画到层(CALayer)中,这样就能触发动画了。通过调用removeAnimationForKey可以停止层中的动画 796 | 5.Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程 (刷新UI界面再主线程中) 797 | 798 | * CAAnimation 799 | 是所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类 800 | 属性说明:(所有子类的动画都有这些属性) 801 | duration:动画的持续时间 802 | repeatCount:重复次数,无限循环可以设置HUGE_VALF或者MAXFLOAT 803 | repeatDuration:重复时间 804 | removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards 805 | fillMode:决定当前对象在非active时间段的行为。比如动画开始之前或者动画结束之后 806 | beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间 807 | timingFunction:速度控制函数,控制动画运行的节奏 808 | delegate:动画代理 809 | 810 | *CABasicAnimation (功能不强大,只能从一个数值(fromValue)变到另一个数值(toValue)) 811 | basic.keyPath = @"position"; 812 | NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(200, 200)]; basic.toValue = toValue; 813 | basic.removedOnCompletion = NO; // 默认一执行完动画就移除动画,取消系统自带的效果 814 | basic.fillMode = kCAFillModeForwards; 815 | 816 | # key的作用 817 | 1.keyAnimate.keyPath = @"transform.translation.x";可以当做keypath来用,但是keypath功能比key强 818 | 2.用来 当做移除动画时的标记key 819 | [self.layer removeAnimationForKey:@"shake"]; 820 | [self.layer addAnimation:basic forKey:nil]; 821 | 822 | * CAKeyframeAnimation: 关键帧动画 823 | values:NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧 824 | path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略 825 | keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的 826 | * CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation 827 | 828 | * CAAnimationGroup——动画组(多个动画组合起来,可以做出很炫的效果) 829 | animations:用来保存一组动画对象的NSArray 830 | 默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间 831 | 832 | *CATransition:转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。转场动画执行过程中无法交互 833 | UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 834 | 动画属性: 835 | type:动画过渡类型 836 | subtype:动画过渡方向(最好设置出你想要的效果) 837 | startProgress:动画起点(在整体动画的百分比) 838 | endProgress:动画终点(在整体动画的百分比) 839 | 840 | #warning 注意点 :核心动画都是假象,不能改变layer的真实属性的值。展示的位置和实际的位置不同。实际位置永远在最开始位置 841 | UIView封装的转场动画 方法 842 | > 单视图 843 | + (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion; 844 | duration:动画的持续时间 845 | view:需要进行转场动画的视图 846 | options:转场动画的类型 847 | animations:将改变视图属性的代码放在这个block中 848 | completion:动画结束后,会自动调用这个block 849 | ``` 850 | 851 | 852 | **33.网易彩票Demo** 853 | ``` 854 | 1. 类似于QQ那种主流框架,项目的起点都是tabbar控制器,导航控制器,自定义tabbar开始(继承UIView) 855 | 2. 再系统自带的tabbar里面添加自定义的Mytabbar,大师特别注意的是 要在viewWillAppear 方法里面,遍历系统自带的tabbar。 删掉UITabbarButton (继承自UIControl) 856 | 3. 控制器父类的抽取,self继承注意点,子类的各有特色 857 | 4. 宏的引用 858 | #define JNUserDefaults [NSUserDefaults standardUserDefaults] 859 | // 加载json 对象 860 | #define ILJson(name) [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@#name withExtension:nil]] options:NSJSONReadingAllowFragments error:nil] 861 | 5. 抽取存储的工具类,内部封装存取的两个方法. 注意self.title 作为key的妙用 862 | 6. 应用的操作(打电话,发短信,打开iTunes 等) 863 | 7. CollectionViewController 864 | * 必须要有流水布局,重写init方法,从内部拦截设置流水布局 865 | - (id)init 866 | { 867 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc]init]; 868 | if (self =[super initWithCollectionViewLayout:layout]) { 869 | layout.itemSize = CGSizeMake(80, 80);// 拿到布局很重要,可以设置很多属性 870 | layout.minimumInteritemSpacing = 10; // 水平方向的间距 871 | layout.minimumLineSpacing = 10; // 垂直方向的间距 872 | layout.sectionInset = UIEdgeInsetsMake(20, 0, 0, 0); 873 | } 874 | return self; 875 | } 876 | * 注册代码cell [self.collectionView registerClass:[JNProductCell class] forCellWithReuseIdentifier:reuseIdentifier] 877 | * 注册xib加载的cell [self.collectionView registerNib:nib forCellWithReuseIdentifier:JNProductCellID]; 878 | * 小坑:collectionView默认是cell 个数超出了范围才会滚动,但是如果不够,下拉刷新的情况下,设置self.collectionView.alwaysBounceHorizontal = YES 就可以了 879 | ``` 880 | 881 | **34.Core Fundation ** 882 | ``` 883 | Core Fundation : 基于c语言的一套框架 884 | > Core Fundation 中的数据类型一般和Fundation 都是可以互相转换的,这里用到桥接转换(不同框架之间架起一个桥梁,类似于强制类型转换,只不过要加上__bridge 表示不同框架的类型转换) 885 | NSString *str = @"haha"; 886 | CFStringRef str2 = (__bridge CFStringRef)str; 887 | NSString *str3 = (__bridge NSString*)str2; 888 | > Core Fundation 中的数据类型创建(含有creat,copy,new)一般都要自己手动释放CFRelease(); 889 | /** 桥接总结: 890 | * Foundation 和 Core Foundation 相互转换. 桥接 891 | * 以后在使用C语言的函数时, 只要函数名称包含creat/copy/retain 就必须自己手动释放CFRelease,因为c语言的东西不归OC的ARC管理 892 | * 桥接转换再MRC情况下: 直接强制转换 893 | * CFStringRef strC = (CFStringRef)strOC; 894 | * NSString *strOC = ( NSString *)strC; 895 | * 896 | * 桥接转换再MRC情况下: 桥接转换 __bridge / __bridge_retained / __bridge_transfer 897 | * __bridge : CFStringRef strC = (__bridge CFStringRef)strOC 898 | > 不会转移对象的所有权,意味着strOC 释放不能用了,strC就不能用 899 | * __bridge_retained: CFStringRef strC = (__bridge_retained CFStringRef)strOC; 900 | > 会转移对象的所有权,意味着strOC 释放了,strC 还能用, 901 | > 但是注意,虽然能用,strC 必须要手动释放,c语言的东西不归arc 管理,需要自己释放 902 | > CFStringRef strC = CFBridgingRetain(strOC);// retain 给你了,表明strC持有拥有权,内存归自己管理要自己手动释放 903 | * __bridge_transfer: NSString *strOC = (__bridge_transfer NSString *)strC; 904 | > C-->OC:它会将对象的所有权转移给strOC, 也就是说, 即便strC被释放了, strOC也可以使用 905 | > 他会自动释放strC, 也就是以后我们不用手动释放strC 906 | > NSString *strOC = CFBridgingRelease(strC); // 转换给你,我会自动释放 907 | * 908 | */ 909 | ``` 910 | 911 | **35.static 关键字作用** 912 | ``` 913 | * static 修饰全局变量 914 | > 会让这个全局变量只在当前的这个文件中能使用,其他文件不能使用。 915 | > 举例: 单例static ,只在类的.m 文件中声明,别的文件中不能使用修改我的全局变量,如果不加static,那么别人可以拿到我全局变量修改,这样的话就失去了单例的意义 916 | * static 修饰局部变量 917 | > 修饰局部变量 : 918 | > 局部变量的生命周期 跟 全局变量 类似,但是不能改变作用域 919 | > 能保证局部变量永远只初始化1次,在程序运行过程中,永远只有1分内存 920 | > 举例: tableview cell 重用中,修饰了局部的ID 变量,这样保证这个ID 只初始化一次,只有一次内存 921 | ``` 922 | 923 | **36.extern关键字** 924 | ``` 925 | > extern 是 C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器, 926 | 其声明的函数和变量可以在本模块或 其它模块中使用。 927 | ``` 928 | 929 | **37.图片的加载** 930 | ``` 931 | [UIImage imageNamed:@"home"]; 加载png图片 932 | 933 | 一、非retina屏幕 934 | 1、3.5 inch(320 x 480) 935 | * home.png 936 | 937 | 二、retina屏幕 938 | 1、3.5 inch(640 x 960) 939 | * home@2x.png 940 | 941 | 2、4.0 inch(640 x 1136) 942 | * home-568h@2x.png(如果home是程序的启动图片,才支持自动加载) 943 | 944 | 三、举例(以下情况都是系统自动加载) 945 | 1、home是启动图片 946 | * iPhone 1\3G\3GS -- 3.5 inch 非retina :home.png 947 | * iPhone 4\4S -- 3.5 inch retina :home@2x.png 948 | * iPhone 5\5S\5C -- 4.0 inch retina :home-568h@2x.png 949 | 950 | 2、home不是启动图片 951 | * iPhone 1\3G\3GS -- 3.5 inch 非retina :home.png 952 | * iPhone 4\4S -- 3.5 inch retina :home@2x.png 953 | * iPhone 5\5S\5C -- 4.0 inch retina :home@2x.png 954 | 955 | 3、总结 956 | * home.png :3.5 inch 非retina 957 | * home@2x.png :retina 958 | * home-568h@2x.png :4.0 inch retina + 启动图片 959 | 960 | ``` 961 | 962 | 963 | **38.创建了一个控件,就是看不见** 964 | ``` 965 | 1.当前控件没有添加到父控件中 966 | 2.当前控件的hidden = YES 967 | 3.当前控件的alpha <= 0.01 968 | 4.没有设置尺寸(frame.size、bounds.size) 969 | 5.位置不对(当前控件显示到窗口以外的区域) 970 | 6.背景色是clearColor 971 | 7.当前控件被其他可见的控件挡住了 972 | 8.当前控件是个显示图片的控件(没有设置图片\图片不存在,比如UIImageView) 973 | 9.当前控件是个显示文字的控件(没有设置文字\文字颜色跟后面的背景色一样,比如UILabel、UIButton) 974 | 10.检查父控件的前9种情况 975 | 976 | 一个控件能看见,但是点击后没有任何反应: 977 | 1.当前控件的userInteractionEnabled = NO 978 | 2.当前控件的enabled = NO 979 | 3.当前控件不在父控件的边框范围内 980 | 4.当前控件被一个背景色是clearColor的控件挡住了 981 | 5.检查父控件的前4种情况 982 | 6.。。。。。。 983 | 文本输入框没有在主窗口上:文本输入框的文字无法输入 984 | ``` 985 | 986 | **39.父子控制器的问题 ** 987 | ``` 988 | * 如果发现:控制器的view还在,但是view上面的数据不显示,极大可能是因为:控制器被提前销毁了 989 | * 一个控制器的view是可以随意调整尺寸和位置的 990 | * 一个控制器的view是可以随意添加到其他view中 991 | * 如果将一个控制器的view,添加到其他view中显示,那么要想办法保证控制器不被销毁 992 | * 原则:只要view在,view所在的控制器必须得在,这样才能保证view内部的数据和业务逻辑正常 993 | * 方案: self.childViewControllers(属性强引用着,并且并且,添加的控制器成为了self 的子控制器) [self addChildViewController:] 994 | * 规范: 如果两个控制器的互为父子关系,那么,控制器的view 一定要是父子关系,不然父子控制器之间的交互会受很大的影响(监听屏幕旋转等(如果子控制器的view 显示在了父控制器的view上面,那么子控制器就能监听到父控制器的屏幕旋转,没有显示子控制器view的不会通知)) 995 | ``` 996 | 997 | **40.const** 998 | ``` 999 | const的用法: 看const 修饰的是什么 1000 | * const int age = 10; // 表示age只读并且不能修改 1001 | * int const age = 10; // 和上面的一样 1002 | * const NSString *name = @"tom"; *name是只读,不能修改,,但name指针可以修改 1003 | * NSString *const name = @"tom"; name指针只读,不能修改,一般用这一个来修饰一些常量,指针指向的数是可以修改的,但指针是不可修改的 1004 | * 用法 再JNConst.h 声明, .m文件实现 1005 | > UIKIT_EXTERN const CGFloat JNHeight 1006 | > const CGFloat JNHeight = 1.65; 1007 | > 在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值; 1008 | > 对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改类的成员变量; 1009 | ``` 1010 | 1011 | **41.lazy init** 1012 | ``` 1013 | -(UILabel *)msgLabel 1014 | { 1015 | if (!_msgLabel) { 1016 | _msgLabel = ({ 1017 | UILabel *msgLabel = [UILabel new]; 1018 | msgLabel.textAlignment = NSTextAlignmentCenter; 1019 | msgLabel; 1020 | }); 1021 | } 1022 | return _msgLabel; 1023 | } 1024 | 1025 | ``` 1026 | 1027 | **42.小技巧** 1028 | ``` 1029 | 1.自定义了leftBarbuttonItem左滑返回手势失效了怎么办? 1030 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithImage:img style:UIBarButtonItemStylePlain target:self action:@selector(onBack:)]; 1031 | self.navigationController.interactivePopGestureRecognizer.delegate = (id 1032 | 1033 | 2.TableView不显示没内容的Cell怎么办? 1034 | self.tableView.tableFooterView = [[UIView alloc] init]; 1035 | 1036 | 3.ScrollView莫名其妙不能在viewController划到顶怎么办? 1037 | self.automaticallyAdjustsScrollViewInsets = NO; 1038 | 1039 | 4.键盘事件写的好烦躁,使用IQKeyboardManager(github上可搜索) 1040 | 5.app滑动流畅 KMCGeigerCounter 1041 | 1042 | 6怎么在不新建一个Cell的情况下调整separaLine的位置? 1043 | _myTableView.separatorInset = UIEdgeInsetsMake(0, 100, 0, 0); 1044 | 1045 | 7.导航条返回键带的title太讨厌了,怎么让它消失! 1046 | 1047 | [[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -60) forBarMetrics:UIBarMetricsDefault]; 1048 | 1049 | 8.CoreData--> MagicalRecord 1050 | 1051 | 9.CollectionView 怎么实现tableview那种悬停的header? 1052 | CSStickyHeaderFlowLayout 1053 | 1054 | 注:参考叶孤城简书tip 1055 | ``` 1056 | 1057 | **43.新浪微博项目总结** 1058 | ``` 1059 | 1.授权 1060 | > 第一步(利用webView 加载 授权页面) 1061 | * URL:https://api.weibo.com/oauth2/authorize 1062 | 请求参数: 1063 | * client_id:申请应用时分配的AppKey 1064 | * redirect_uri:授权成功后的回调地址 1065 | > 第二步(webView 代理方法中,拦截回调页面的加载,截取毁掉地址后面的code码。那其实就是requestToken(code)) 1066 | > 第三步:发送请求,获取AccessToken 1067 | * URL:https://api.weibo.com/oauth2/access_token 1068 | * 请求参数: 1069 | * client_id:申请应用时分配的AppKey 1070 | * client_secret:申请应用时分配的AppSecret 1071 | * grant_type: 填写 authorization_code 1072 | * redirect_uri:授权成功后的回调地址 1073 | * code:授权成功后返回的requestToken(截串的code) 1074 | 1075 | 2. 工具类的使用 1076 | > 加载表情的一些操作不应该再控制器中完成,要避免给予控制器的压力,这个时候应该抽取工具类来专门的加载表情 1077 | > 表情info。plist的加载,注意,当文件托在项目中,Xcode默认生成的是虚拟文件夹,如果遇到相同的文件名,再不同的文件夹中,应该creat foler reference,这个时候文件再bundle 中就是以文件夹的目录情况显示 1078 | > 因为这是一个类方法,不是面向对象的方法。所以不能够通过self.recent 拿到这个数组,保证内存中只有一个数组,因为没必要每次都创建或者访问 1079 | 1080 | static NSMutableArray *_recent; // 因为这是一个类,不是实力变量,不能通过self.recent 来获取成员变量,面向的是类方法,而不是对象方法。这个时候考虑到这样写法。同时也保证了内存中只有这一个对象,每次修改我都保存了 1081 | +(void)initialize // 当使用到这个类的时候就会调用一次,再这里进行懒加载,牛逼 1082 | { 1083 | _recent = [NSKeyedUnarchiver unarchiveObjectWithFile:JNEmotionFilePath]; 1084 | if (_recent == nil) { 1085 | _recent = [NSMutableArray array]; 1086 | } 1087 | } 1088 | 1089 | * 数组遍历删除问题 1090 | * 最好不要再遍历数组的的同时删除数组的元素,因为,遍历数组时刻依赖着数组的个数,而个数影响着次数,所以即使要遍历删除,应该要时刻监听着数组个个数,for 循环应该写arr.count(保证每次都能拿到准确的数组格式) 1091 | * removeObject 内部会调用isEquall 的方法,来判断你要删除的数组元素和我数组里面的对象是否是同一个对象,是同一个对象的话我才给你删除,但是有时候不一定要删除的是同一个对象,你数组元素和我数组元素数组相同(emotion情况)的特殊需求我也要删除,这个时候考虑到重写对象的isEquall 的方法 1092 | 1093 | -(BOOL)isEqual:(JNEmotion *)e 1094 | { 1095 | [super isEqual:e]; 1096 | // 改变系统自带的判断方法 isEqualToString 判断的是内容, == 判断的是否是同一个对象 1097 | return [self.chs isEqualToString:e.chs] || [self.code isEqualToString:e.code]; 1098 | } 1099 | 1100 | > 存储数据一般都使用工具类封装起来,再内部实现存和取的方法,统一管理。 1101 | * MJCodingImplementation 内部实现了存取的code 方法.内部的原理是利用runtime 来获取ivarlist 列表,然后遍历做一些归档解档的操作 1102 | 1103 | > 网络请求的工具类,为了避免项目对框架的依赖,自己再套一层外套 (注意block 做回调参数的写法) 1104 | + (void)get:(NSString *)url parames:(NSDictionary *)parame success:(void(^)(id responseObj))success failure:(void(^)(NSError *error))failure; 1105 | > 但是大多数情况下,如果在控制器中利用HTTPTool工具类发送网络请求,控制器还是知道了太多了,而且也应该面向模型开发。这个时候,应该在抽取一个业务类。业务类专门做自己的业务。项目的层次结构性强、 1106 | 1107 | 3.良好的见哦按交互体验应该在viewDidAppear 方法里面弹出键盘 viewWillDisappear 退出键盘,如果在viewDidload 里面,或造成一些卡的情况,因为在加载键盘造成卡顿 1108 | * 键盘处理,监听键盘的通知 1109 | * self.textView.inputAccessoryView // inputAccessoryView是工具条. 1110 | * self.textView.inputView 是键盘 // 自定义键盘 1111 | * 键盘的切换。 根据self.textView.inputView == nil 来判断键盘是系统自带的还是自定义键盘. 1112 | 1113 | if (self.textView.inputView == nil) { // 用的是系统自带的键盘 1114 | self.toolbar.showKeyboard = YES; 1115 | self.textView.inputView = self.emotionKeyboard; 1116 | }else{ // 已经是表情键盘了 1117 | self.textView.inputView = nil; // 清空,方便下次判断 1118 | self.toolbar.showKeyboard = NO; 1119 | } 1120 | [self.textView endEditing:YES]; 1121 | #warning 更换了系统自带的键盘,那么必须要退出键盘之后再重新叫出键盘,不然键盘不显示 1122 | // 延迟0.1s 左动画 1123 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 1124 | [self.textView becomeFirstResponder]; // 键盘的弹出动画。 1125 | }); 1126 | 1127 | * BOOL 的巧妙应用可是实现加锁的情况,再特殊的情况代码前后置 YES 和 NO, 再判断的地方做YES 或者NO 的return 操作. 1128 | * [self.textView deleteBackward]; // 系统自带的删除功能,会在光标后面删除 1129 | * [self insertText:string]; 会自动再光标后面凭借字符(emoji本质是字符串) 1130 | 1131 | * 调用系统自带的 相册,相机,因为代码很相似,可以考虑将sourceType抽取出来,打开不同的情况 1132 | 1133 | ``` 1134 | 1135 | ```objc 1136 | -(void)openCamera 1137 | { 1138 | // 判断是否支持照相功能(只有真机才能打开相机,不然会crash) 1139 | if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) return; // 不支持那么直接返回 1140 | #warning 如果觉得每次选图片都只能选一张,可以用collectionView来实现多选,通过assetsFramework 框架能拿到系统的图片url 1141 | JNPhotoController *photoVC = [[JNPhotoController alloc]init]; 1142 | photoVC.sourceType = UIImagePickerControllerSourceTypeCamera; 1143 | photoVC.allowsEditing = YES; 1144 | photoVC.delegate = self; 1145 | self.modalPresentationStyle = UIModalPresentationOverCurrentContext; 1146 | [self presentViewController:photoVC animated:YES completion:nil]; 1147 | } 1148 | 1149 | ``` 1150 | 1151 | **44.弹出菜单的动画** 1152 | ```objc 1153 | -(void)makeOpen 1154 | { 1155 | // 每隔按钮隔0.1秒做动画 1156 | [self.menubtns enumerateObjectsUsingBlock:^(JNComposeMenuButton *btn, NSUInteger idx, BOOL *stop) { 1157 | [UIView animateWithDuration:0.5 delay:0.05 * idx usingSpringWithDamping:0.7 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{ 1158 | btn.y -= 300; 1159 | } completion:^(BOOL finished) { 1160 | }]; 1161 | }]; 1162 | } 1163 | 1164 | ``` 1165 | 1166 | **版本新特性的判断 : 拿到上次的版本号于当前的版本号相比较,不同就显示新特性** 1167 | ```objc 1168 | +(void)chooseController 1169 | { 1170 | // 版本号的key 1171 | NSString *versionKey = (__bridge NSString*)kCFBundleVersionKey; 1172 | 1173 | // 取出上次的版本号 1174 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 1175 | NSString *lastVersion = [defaults objectForKey:versionKey]; 1176 | 1177 | // 取出当前版本号 1178 | NSString *currentVersion = [NSBundle mainBundle].infoDictionary[versionKey]; 1179 | 1180 | // 取出window 1181 | UIWindow *window = [UIApplication sharedApplication].keyWindow; 1182 | 1183 | // 判断 。版本号不相同就显示新版本 1184 | if (![lastVersion isEqualToString:currentVersion]) { // 不相同 1185 | window.rootViewController = [[JNNewFeatureController alloc]init]; 1186 | }else{ // 相同,微博界面 1187 | window.rootViewController = [[JNTabbarController alloc]init]; 1188 | } 1189 | // 存储版本号 1190 | [defaults setObject:currentVersion forKey:versionKey]; 1191 | [defaults synchronize]; // 同步 1192 | } 1193 | 1194 | ``` 1195 | 1196 | **45.坐标系转换** 1197 | ``` 1198 | 坐标系转换: 最重要的是找准坐标系的坐标原点 bounds 是以自己的左上角为原点,自身就是一个坐标系。不用bounds 那么就要有superView 1199 | ``` 1200 | 1201 | **46.拦截代理的setter方法,有代理才做事情** 1202 | ```objc 1203 | - (void)setDelegate:(id)delegate 1204 | { 1205 | _delegate = delegate; 1206 | #warning 刚创建时候self 的代理为为空,所以再setdelegate 里面拦截,有代理我才选中 1207 | [self btnClick:self.defaultButton]; 1208 | } 1209 | 1210 | ``` 1211 | 1212 | **47.系统的自动渲染** 1213 | ```objc 1214 | UIImage *img = [UIImage imageNamed:emotion.png]; 1215 | img = [img imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] // 系统会自动渲染成蓝色的,所以不需要渲染,总是保持原生的 1216 | 1217 | ``` 1218 | 1219 | ``` 1220 | * 避免控件之间的循环引用, 不用的隐藏,用的显示,有YES一定要有NO 1221 | #warning 避免循环引用的出现还有一个比较好的方案是告诉模型,是否显示或者隐藏,然后刷新数据(MVC 通过模型来修改界面) 1222 | self.check.hidden = !self.deal.isChecking(再setdeal 方法里->内部掉刷新cell 的界面) 1223 | 1224 | self.deal.checking = !self.deal.isChecking 1225 | self.check.hidden = !self.check.hidden 1226 | 1227 | * 如果你自定义系统自带的控件,例如titleView,即便你设置了xy,依然会被系统自带的覆盖,但是你一定要设置宽高,不然没有尺寸、显示不出来 1228 | * get方法与set方法拦截的选择。 如果重写了get方法,意味着在外面每次获取都会来到get方法。这样保证了数据的实时更新,但是,每次都来都get方法,会怎加一些不必要的开支,而如果进行set方法拦截的话,优势在于不必平凡的调用,只计算一次,但是不能保证实时的一些更新操作 1229 | ``` 1230 | 1231 | **48.日期** 1232 | ``` 1233 | > 设置日期格式(声明字符串里面每个数字和单词的含义) 1234 | > E:星期几 M:月份 d:几号(这个月的第几天) H:24小时制的小时 m:分钟 s:秒 y:年 1235 | > NSLocale *locale = [[NSLocale alloc]initWithLocaleIdentifier:@"en_US"]; 1236 | fmt.locale = locale; // 如果是真机调试,转换这种欧美时间,需要设置locale 1237 | > 日历对象 1238 | NSCalendar *calendar = [NSCalendar currentCalendar]; // 一定要是current。获取当前的日历对象 1239 | // 表明要获取那个时间段的差值 1240 | NSCalendarUnit unit = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay |NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 1241 | NSDateComponents *cmps = [calendar components:unit fromDate:creatDate toDate:now options:0]; // from 与to 两个位置随便写。 option 写0 1242 | 1243 | ``` 1244 | 1245 | **49. 通知 后台** 1246 | ```objc 1247 | iOS8开始,对用户的隐私的加强,需要注册配置信息 1248 | if ([[UIDevice currentDevice].systemVersion floatValue] > 8.0 ) { // iOS8 1249 | UIUserNotificationSettings *setting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge categories:nil]; 1250 | [application registerUserNotificationSettings:setting]; 1251 | } 1252 | 1253 | * 后台处理 1254 | // 开启后台任务 1255 | __block UIBackgroundTaskIdentifier taskID = [application beginBackgroundTaskWithExpirationHandler:^{ 1256 | // 过期就停止 1257 | [application endBackgroundTask:taskID]; 1258 | }]; 1259 | 1260 | app的状态 1261 | 1.死亡状态:没有打开app 1262 | 2.前台运行状态 1263 | 3.后台暂停状态:停止一切动画、定时器、多媒体、联网操作,很难再作其他操作 1264 | 4.后台运行状态 1265 | 1266 | * 后台任务总结 1267 | 1.在代理的applicationDidEnterBackground 方法中写开启后台任务,返回任务ID 1268 | 2.配置plist文件添加Required background modes, 1269 | 3.string里面添加 App plays audio or streams audio/video using AirPlay 1270 | 在Info.plst中设置后台模式:Required background modes == App plays audio or streams audio/video using AirPlay 搞一个0kb的MP3文件,没有声音 1271 | 循环播放 1272 | 以前的后台模式只有3种:1.保持网络连接 2.多媒体应用 3.VOIP:网络电话 1273 | ``` 1274 | 1275 | ``` 1276 | bool 类型和set方法联合使用 1277 | 再外面设置情况,重写set方法,再里面setter方法拦截,因为这时候在里面bool已经有值,根据不同的bool 类型的值。有对应的情况 1278 | ``` 1279 | 1280 | ``` 1281 | 遍历scrollView 内部子控件可以发现,自带了两个view,这两个view 是系统自带的水平垂直滚动条。计算尺寸不方便,所以当对应的属性设置为no 的时候,那么scrollView 的子控件就没有这两个多余的滚动条了 1282 | ``` 1283 | 1284 | **50.分页小算法** 1285 | ``` 1286 | NSUInteger count = (emotions.count + JNPageEmotionCount - 1) / JNPageEmotionCount; // JNPageEmotionCount == 20 1287 | ``` 1288 | 1289 | **51.touch 事件** 1290 | ``` 1291 | 有的时候,我们会利用touch 来监听点击,但是这种情况下,点击事件会被子控件吞掉。所以不是太可靠。可以通过长按手势来做到只监听自己的触摸,而不监听子控件的触摸事件 1292 | ``` 1293 | 1294 | ``` 1295 | 取消emoji 显示时系统自带动画,但是这是系统的动画,一旦取消了。整个西东的动画行为就会取消,所以应该立马设置回去 1296 | ``` 1297 | 1298 | ```objc 1299 | UIView setAnimationsEnabled:NO]; 1300 | // DO WHAT U WANNA DO 1301 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 1302 | [UIView setAnimationsEnabled:YES]; // 代码一过掉,马上恢复动画 1303 | }); 1304 | * 注意,dispatch_after 是延迟提交,并不是延迟运行 1305 | Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行,dispatch_after只是延时提交block,并不是延时后立即执行。所以想用dispatch_after精确控制运行状态的朋友可要注意了 1306 | ``` 1307 | 1308 | ``` 1309 | 设置attach 的属性 1310 | CGFloat fontWH = self.font.lineHeight; // 字体的行高 1311 | NSAttributedString ,NSTextAttachment 附件对象attach的bounds的xy可以设置。字体的行高 font.lineHeight 1312 | ``` 1313 | 1314 | 1315 | **52.性能优化** 1316 | ``` 1317 | 1. 用ARC管理内存 1318 | 2. 在正确的地方使用reuseIdentifier 1319 | 3. 尽可能使Views不透明 1320 | 4. 避免庞大的XIB 1321 | 5. 不要block主线程 1322 | 6. 在Image Views中调整图片大小 1323 | 7. 选择正确的Collection 1324 | 8. 打开gzip压缩 1325 | 中级(这些是你可能在一些相对复杂情况下可能用到的) 1326 | 9. 重用和延迟加载Views 1327 | 10. Cache, Cache, 还是Cache! 1328 | 11. 权衡渲染方法 1329 | 12. 处理内存警告 1330 | 13. 重用大开销的对象 1331 | 14. 使用Sprite Sheets 1332 | 15. 避免反复处理数据 1333 | 16. 选择正确的数据格式 1334 | 17. 正确地设定Background Images 1335 | 18. 减少使用Web特性 1336 | 19. 设定Shadow Path 1337 | 20. 优化你的Table View 1338 | 21. 选择正确的数据存储选项 1339 | 进阶级(这些建议只应该在你确信他们可以解决问题和得心应手的情况下采用) 1340 | 22. 加速启动时间 1341 | 23. 使用Autorelease Pool 1342 | 24. 选择是否缓存图片 1343 | 25. 尽量避免日期格式转换 1344 | ``` 1345 | 1346 | **53.静态库的制作** 1347 | ``` 1348 | ->static libary ->写一些核心代码-->找到.a 文件,真机模拟器分别build一次->showinFinder -->打包资源(bundle) 1349 | 如果没有暴露头文件的解放方法:build pharse ->copy files 1350 | 1351 | * 真机文件夹下得静态库只能用于真机上, 模拟器文件夹下得静态库只能用于模拟器下 1352 | 1353 | * 可以借助 lipo -info 静态库文件地址 指令查看当前静态库支持的平台 1354 | * 可以借助 lipo -create libdev/lib08-staticDemo.a libPro/lib08-staticDemo.a -output HMTool.a 指令将模拟器和真机的静态库合并为一个静态库 1355 | * lipo -create 需要合并的静态库1 需要合并的静态库2 -output 合并之后的文件名称 1356 | * Xcode6之后自动导入框架,但是如果用.a静态库的话,那么必须要导入 1357 | 注意: 虽然将真机和模拟器的静态库合并在一起之后, 以后我们就不用关心当前是允许在模拟器还是真机了, 但是如果在程序发布时还是建议大家使用真机的静态库. 小 1358 | lipo -create libsim/libJNTool.a libdev/libJNTool.a -output JNXIXI.a 1359 | 1360 | ``` 1361 | 1362 | **54.正则表达式** 1363 | ``` 1364 | * 正则表达式使用步骤 1365 | * 1.创建正则表达式对象:定义规则(正则表达式)(贪婪匹配) 1366 | * 2.利用正则表达式匹配 1367 | 1368 | * 常见字符含义 1369 | . 匹配除换行符以外的任意字符 1370 | \w 匹配字母或数字或下划线或汉字 1371 | \s 匹配任意的空白符 1372 | \d 匹配数字 1373 | \b 匹配单词的开始或结束 1374 | ^ 匹配字符串的开始 1375 | $ 匹配字符串的结束 1376 | * 重复零次或更多次 1377 | + 重复一次或更多次 1378 | ? 重复零次或一次 1379 | {n} 重复n次 1380 | {n,} 重复n次或更多次 1381 | {n,m} 重复n到m次 1382 | ``` 1383 | 1384 | 1385 | **55.缓存逻辑** 1386 | ``` 1387 | * 首先从本地缓存加载 1388 | * 本地没有数据再去进入下拉刷新状态 加载服务器端数据 1389 | * 否则不加载服务器数据 只让 用户主动加载数据 1390 | ``` 1391 | 1392 | **56.image Mode** 1393 | ``` 1394 | > UIViewContentModeScaleToFill : 图片拉伸至填充整个UIImageView(图片可能会变形) 1395 | > UIViewContentModeScaleAspectFit : 图片拉伸至完全显示在UIImageView里面为止(图片不会变形) 1396 | 1397 | > UIViewContentModeScaleAspectFill:图片拉伸至 图片的宽度等于UIImageView的宽度 或者 图片的高度等于UIImageView的高度 为止 1398 | > UIViewContentModeRedraw : 调用了setNeedsDisplay方法时,就会将图片重新渲染 1399 | 1.凡是带有Scale单词的,图片都会拉伸 1400 | 2.凡是带有Aspect单词的,图片都会保持原来的宽高比,图片不会变形 1401 | ``` 1402 | 1403 | **57.性能测试(性能分析,内存分析)** 1404 | ``` 1405 | 1.静态分析(Anaylize) 1406 | * 检测代码是否有潜在的内存泄露 1407 | * 编译器觉得不太合适的代码(32位,64位的适配问题) 1408 | 1409 | 2.动态分析(profile-instrument) 1410 | * 检测程序再运行过程中的内存变化 1411 | * allocations:能看清楚app的内存分配情况 1412 | * leaks :能看清楚app再何时产生内存泄露(有红色的代表有内存泄露,注意官方webView就有内存泄露) 1413 | * timeprofile 表示程序的运行时间分析(耗时间的操作,算法的优化) 1414 | 内存泄露:改释放的对象没有被释放 1415 | 内存溢出:内存爆了,不够用 1416 | 1417 | 注意点:APNs限制了每个notification的payload最大长度是256字节,超长的消息是不能发送的 1418 | 1419 | 预编译指令 1420 | c 提供的预处理功能主要有以下三种: 1 )宏定义  2 )文件包含  3 )条件编译 1421 | ``` 1422 | 1423 | **58.Autoreleasepool的工作原理** 1424 | ``` 1425 | Autorelease的对象是在什么时候被release的? 1426 | > autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object(就是autorelease的对象)会被release。那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。那什么是一个Runloop呢? 一个UI事件,Timer call, delegate call, 都会是一个新的Runloop。 1427 | 1428 | > 在线程方法内不创建线程池,会在控制台有警告。系统认为分线程是独立运行,需要有一个独立的 NSAutoreleasePool 1429 | > 为了对pool内部的一些局部变量的释放,避免引起内存泄露 1430 | ``` 1431 | 1432 | **59.分类的作用 ** 1433 | ``` 1434 | 1 可以使本来需要在.h中声明的方法放到.m文件中声明,使方法变成私有。 1435 | 2 可以扩展或覆盖一个类的功能,包括系统类,维护了代码原本的结构不受影响。 1436 | 3 可以分散代码到不同的文件之中,比如系统类库里有一个NSObject的类别,并没有写在NSObject类里,而写到另外一个类里,主要是因为这个类别扩展的功能跟那个类相关,便于将来查看。(利于团队协作,分模块开发) 1437 | ``` 1438 | 1439 | **60.copy ** 1440 | ``` 1441 | * 需要遵守NSCopying的协议,调用copy方法,内部会调用copywithZone 方法 1442 | - (id)copyWithZone:(NSZone *)zone { 1443 | MyObject *copy = [[[self class] allocWithZone: zone] init]; 1444 | copy.username = [self.username copyWithZone:zone]; 1445 | return copy; 1446 | } 1447 | 示例:NSString *str1 = @"111",NSString *str2 = @"111" 1448 | > 这种创建对象方式会造成内存地址共享,s1和s2完全指向同一个地址空间,也就是同一个对象了,改变或释放这个地址空间的内容就意味着s1和s2都变了。为了避免修改一方影响到另外一方,字符串赋值时常copy一个新的对象比较安全。 1449 | ``` 1450 | 1451 | **61.支付宝** 1452 | ``` 1453 | 1. 取支付宝官网申请“开通支付宝视同权限” 1454 | * 填写个人信息\公司信息(绝对真实,可靠,不是随便给你用的) 1455 | * 签约 1456 | * 等待审核 1457 | 1458 | 2.审核通过 1459 | * seller id 1460 | * partner id 1461 | * 后面加密用到的文件(公钥\私钥) 1462 | 1463 | 3.去支付宝官网下载支付宝sdk(网页版\无线版) 1464 | 1> 生成订单信息 1465 | 2> 签名信息 1466 | 3> 利用订单信息,签名信息,签名类型生成一个订单字符串 1467 | 4> 打开客户端支付 1468 | ``` 1469 | 1470 | ```objc 1471 | - (IBAction)buy { 1472 | // 1.生成订单信息 1473 | // 订单信息 == order == [order description] 1474 | AlixPayOrder *order = [[AlixPayOrder alloc] init]; 1475 | order.productName = self.deal.title; 1476 | order.productDescription = self.deal.desc; 1477 | order.partner = PartnerID; 1478 | order.seller = SellerID; 1479 | order.amount = [self.deal.current_price description]; 1480 | // 2.签名加密 1481 | id signer = CreateRSADataSigner(PartnerPrivKey); 1482 | // 签名信息 == signedString 1483 | NSString *signedString = [signer signString:[order description]]; 1484 | 1485 | // 3.利用订单信息、签名信息、签名类型生成一个订单字符串 1486 | NSString *orderString = [NSString stringWithFormat:@"%@&sign=\"%@\"&sign_type=\"%@\"", 1487 | [order description], signedString, @"RSA"]; 1488 | 1489 | // 4.打开客户端,进行支付(商品名称,商品价格,商户信息)(有seletor,target 是用来处理网页支付的回调) 1490 | [AlixLibService payOrder:orderString AndScheme:@"tuangou" seletor:@selector(getResult:) target:self]; 1491 | } 1492 | 1493 | // 网页处理结果回调 1494 | - (void)getResult:(NSString *)result{} 1495 | 1496 | // 客户端处理回调 1497 | /** 当从其他应用跳转到当前应用时,就会调用这个方法 */ 1498 | - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation 1499 | { 1500 | AlixPayResult * result = nil; 1501 | if (url != nil && [[url host] compare:@"safepay"] == 0) { 1502 | NSString * query = [[url query] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 1503 | #if ! __has_feature(objc_arc) 1504 | result = [[[AlixPayResult alloc] initWithString:query] autorelease]; 1505 | #else 1506 | result = [[AlixPayResult alloc] initWithString:query]; 1507 | #endif 1508 | } 1509 | if (result.statusCode == 9000) { // 表面上交易成功,但是为了安全,判断是否结果正确 1510 | // 用公钥验证签名 严格验证请使用result.resultString与result.signString验签 1511 | id verifier = CreateRSADataVerifier(AlipayPubKey); 1512 | if ([verifier verifyString:result.resultString withSign:result.signString]) { 1513 | //验证签名成功,交易结果无篡改 1514 | //交易成功 1515 | } else { 1516 | // 失败 1517 | } 1518 | } else { 1519 | // 失败 1520 | } 1521 | return YES; 1522 | } 1523 | /*打开Demo 的错误:依赖系统库libcrypto.a libssl.a CG SystemconFiguaration**/ 1524 | 1525 | ``` 1526 | 1527 | **63.美团地图总结** 1528 | ``` 1529 | 1. 首先,,iOS8开始 需要用户的授权[self.mgr requestAlwaysAuthorization];并在plist中配置NSLocationAlwaysUsageDescription允许在后台获取GPS的描述 1530 | 1531 | 2. 要设置追踪模式,customMapView.userTrackingMode = MKUserTrackingModeFollow 1532 | 1533 | 3. 再mapView 的更新到用户位置的地方,利用反地理编码获取城市名,注意地标placemark的lociaty 和addressdict 中 省级市,和市的取值.获取到城市名,再区域改变的时候哪一个代理方法里面,发送网络请求,获取附近的团购 1534 | 1535 | 4. 因为用户刚进入地图的时候也需要知道周边的团购,那么就应该再刚更新到用户位置的代理方法里面主动掉一下代理方法、 1536 | 1537 | 5. 获取团购数据插大头针。但是要注意,因为地图每滚动一次就会发一次请求,运行的性能太差,应该用一个bool 常量过滤,如果我正在处理网络请求,那么就不要再发送请求了。 1538 | 1539 | 6. 每次滚动都插大头针。如果,我插的大头症的位置存在于现在地图上的大头针,[contains objc],所以要重写isEquall 方法,告诉系统,只要你的位置和我的大头针样,那么我就认为,这两个大头针相同,我没有必要再在这个地方插大头针了。所以continue ,不插已经有的大头针 1540 | 1541 | 7. 如果想改变大头针的图片,只能用父类annotationView。并且一般情况下大头针模型都需要绑定一个团购模型 1542 | ``` 1543 | 1544 | 1545 | **64.推送** 1546 | ``` 1547 | 注意点:APNs限制了每个notification的payload最大长度是256字节,超长的消息是不能发送的 1548 | ``` 1549 | 1550 | **65.GCD** 1551 | ``` 1552 | > dispatch_once_t必须是全局或static变量 1553 | 非全局或非static的dispatch_once_t变量在使用时会导致非常不好排查的bug,正确写法如下 1554 | //静态变量,保证只有一份实例,才能确保只执行一次 1555 | static dispatch_once_t onceToken; 1556 | dispatch_once(&onceToken, ^{ 1557 | //单例代码 其实就是保证dispatch_once_t只有一份实例 1558 | }); 1559 | 1560 | > 注意,dispatch_after 是延迟提交,并不是延迟运行 1561 | Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行,dispatch_after只是延时提交block,并不是延时后立即执行。所以想用dispatch_after精确控制运行状态的朋友可要注意了 1562 | > Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行! 1563 | > 在main线程使用“同步”方法提交Block,必定会死锁。 1564 | ``` 1565 | 1566 | ``` 1567 | SEL:其实是对方法的一种包装,将方法包装成一个SEL类型的数据,去找对应的方法地址。找到方法地址就可以调用方法 1568 | 方法的存储位置 1569 | > 每个类的方法列表都存储在类对象中 1570 | > 每个方法都有一个与之对应的SEL类型的对象 1571 | > 根据一个SEL对象就可以找到方法的地址,进而调用方法 1572 | > SEL类型的定义 1573 | typedef struct objc_selector *SEL; 1574 | 1575 | ``` 1576 | 1577 | **66.runtime** 1578 | ``` 1579 | Objective-C 类也是对象,runtime 通过创建 Meta Classes 来处理这些。当你发送一个消息像这样 [NSObject alloc] 你正在向类对象发送一个消息,这个类对象需要是 MetaClass 的实例,MetaClass 也是 root meta class 的实例。当你说继承自 NSObject 时,你的类指向 NSObject 作为自己的 superclass。然而,所有的 meta class 指向 root metaclass 作为自己的 superclass。所有的 meta class 只是简单的有一个自己响应的方法列表。所以当你向一个类对象发送消息如 [NSObject alloc],然后实际上 objc_msgSend() 会检查 meta class 看看它是否响应这个方法,如果他找到了一个方法,就在这个 Class 对象上执行(译注:class 是一个实例对象的类型,Class 是一个类(class)的类型。对于完全的 OO 来说,类也是个对象,类是类类型(MetaClass)的实例,所以类的类型描述就是 meta class) 1580 | 1581 | * 日志输出 1582 | > __FILE__ :源代码文件名 1583 | > __LINE__ :NSLog代码在第几行 1584 | > __func__ :当前的方法 1585 | > _cmd :代表着当前方法的SEL 1586 | > - (void)test { //下面的代码会引发死循环 1587 | [self performSelector:_cmd] 1588 | } 1589 | 1590 | ``` 1591 | 1592 | **67. 通知的安全性用法** 1593 | ``` 1594 | > 收到通知的对象被称为观察者,而且必须添加到NSNotificationCenter。除非你有很强的理由不去添加到NSNotificationCenter,这样他就总是defaultCenter。在init方法 viewDidLoad方法 viewWillAppear方法里面添加观察者是比较好的选择,你应该尽可能晚地添加观察者和尽快地删除它来提高性能并避免一些不希望出现的bug 1595 | > 如果想让一个控制器的view显示出来的时候才监听,不需要后台监听的时候,那么最严谨的方法是在viewWillAppear 添加通知,viewWillDisAppear 移除通知 1596 | ``` 1597 | 1598 | 1599 | **68.MVVM 黑魔法** 1600 | ``` 1601 | 不要在viewDidLoad里面初始化你的view然后再add,这样代码就很难看。在viewDidload里面只做addSubview的事情,然后在viewWillAppear里面做布局的事情,最后在viewDidAppear里面做Notification的监听之类的事情。至于属性的初始化,则交给getter去做 1602 | 1603 | ViewController基本上是大部分业务的载体 1604 | 先是life cycle,然后是Delegate方法实现,然后是event response,然后才是getters and setters 1605 | 数据管理者,数据加工者,数据展示者 中Model就是作为数据管理者,View作为数据展示者,Controller作为数据加工者 1606 | M应该做的事: 1607 | 给ViewController提供数据 1608 | 给ViewController存储数据提供接口 1609 | 提供经过抽象的业务基本组件,供Controller调度 1610 | 1611 | C应该做的事: 1612 | 管理View Container的生命周期 1613 | 负责生成所有的View实例,并放入View Container 1614 | 监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。 1615 | V应该做的事: 1616 | 响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。 1617 | 界面元素表达 1618 | 1619 | MVCS 1620 | 苹果自身就采用的是这种架构思路,从名字也能看出,也是基于MVC衍生出来的一套架构。从概念上来说,它拆分的部分是Model部分,拆出来一个Store。这个Store专门负责数据存取。但从实际操作的角度上讲,它拆开的是Controller。 1621 | 1622 | 这算是瘦Model的一种方案,瘦Model只是专门用于表达数据,然后存储、数据处理都交给外面的来做。MVCS使用的前提是,它假设了你是瘦 Model,同时数据的存储和处理都在Controller去做。所以对应到MVCS,它在一开始就是拆分的Controller。因为 Controller做了数据存储的事情,就会变得非常庞大,那么就把Controller专门负责存取数据的那部分抽离出来,交给另一个对象去做,这个 对象就是Store。这么调整之后,整个结构也就变成了真正意义上的MVCS。 1623 | 1624 | 胖Model包含了部分弱业务逻辑 1625 | 胖Model要达到的目的是,Controller从胖Model这里拿到数据之后,不用额外做操作或者只要做非常少的操作,就能够将数据直接应用在View上 1626 | 胖Model相对比较难移植,虽然只是包含弱业务,但好歹也是业务 1627 | 瘦Model只负责业务数据的表达,所有业务无论强弱一律扔到Controller。瘦Model要达到的目的是,尽一切可能去编写细粒度Model,然后配套各种helper类或方法来对弱业务做抽象,强业务依旧交给Controller 1628 | 1629 | MVVM着重想要解决的问题是尽可能地减少Controller的任务。不管MVVM也好,MVCS也好,他们的共识都是Controller会随着软件的成长,变很大很难维护很难测试 1630 | 1631 | ,MVCS是认为Controller做了一部分Model的事情,要把它拆出来变成Store,MVVM是认为Controller做了太多数据加工的事情,所以MVVM把数据加工的任务从Controller中解放了出来,使得Controller只需要专注于数据调配的工作,ViewModel则去负责数据加工并通过通知机制让View响应ViewModel的改变。 1632 | 1633 | MVVM是基于胖Model的架构思路建立的,然后在胖Model中拆出两部分:Model和ViewModel。关于这个观点我要做一个额外解释:胖Model做的事情是先为Controller减负,然后由于Model变胖,再在此基础上拆出ViewModel,跟业界普遍认知的MVVM本质上是为Controller减负这个说法并不矛盾,因为胖Model做的事情也是为Controller减负。 1634 | 1635 | 另外,我前面说MVVM把数据加工的任务从Controller中解放出来,跟MVVM拆分的是胖Model也不矛盾。要做到解放Controller,首先你得有个胖Model,然后再把这个胖Model拆成Model和ViewModel。 1636 | 1637 | AOP(Aspect Oriented Programming),面向切片编程,这也是面向XX编程系列术语之一哈,但它跟我们熟知的面向对象编程没什么关系。 1638 | 1639 | 什么是切片?(runtime Swizzling 就是通过这个来实现面向切片编程的) 1640 | 1641 | 程序要完成一件事情,一定会有一些步骤,1,2,3,4这样。这里分解出来的每一个步骤我们可以认为是一个切片。 1642 | 什么是面向切片编程? 1643 | 你针对每一个切片的间隙,塞一些代码进去,在程序正常进行1,2,3,4步的间隙可以跑到你塞进去的代码,那么你写这些代码就是面向切片编程。 1644 | 1645 | AOP一般都是需要有一个拦截器,然后在每一个切片运行之前和运行之后(或者任何你希望的地方),通过调用拦截器的方法来把这个jointpoint扔到外面,在外面获得这个jointpoint的时候,执行相应的代码。 1646 | 1647 | 在iOS开发领域,objective-C的runtime有提供了一系列的方法,能够让我们拦截到某个方法的调用,来实现拦截器的功能,这种手段我们称为Method Swizzling。Aspects通过这个手段实现了针对某个类和某个实例中方法的拦截 1648 | ``` 1649 | 1650 | **69.美团总结** 1651 | ``` 1652 | > 元数据工具类 1653 | 1、如果项目中一些数据是一成不变的并且比较多,最好的是应该抽取元数据的工具类,你需要什么数据我给你,并且,最好的情况下我只加载一次,没有必要加载多次,最好的办法是static 加载一次 1654 | 2. 工具类就应该有工具类的责任,把一些复杂的操作屏蔽再工具类中,比如,你给我一个名字,那么我就返回这个名字的城市模型给你,屏蔽业务细节 1655 | > 数据库工具类(initialize方法里面创建数据库) 1656 | 1.将数据库的一些CURD操作屏蔽再内部,不要外界关心 1657 | 2.分页小算法。加载哪一页的数据 1658 | int size = 20; 1659 | int pos = (page - 1) * size; 1660 | > 网络请求工具类 1661 | * 本来这里可以用类方法请求数据的,但是,DPRequest 需要有一个代理对象来监听网络请求的过程,返回数据给你,如果用了类方法,那么意味着我不能发请求,因为代理只能由对象来充当,而不能由类担任,所以这地方用对象方法,因为是对象发放,那么最好是一个单例 1662 | * 面向模型开发 1663 | 1664 | > 有的时候,如果再xib中我用autolayout来约束控件,但是这个控件没有尺寸,那么有很大的可能性是系统自带的autoresizing 搞得鬼,这个时候,可以把这个控件的autoresizingMask值none 试一试,防止系统内部的一下拉升操作 1665 | > UICollectionView的一些小坑 1666 | * iOS8的新方法,监听viewWillTransitionToSize 横竖屏的滚动. 1667 | * iPad中cell的间距一般是通过横竖屏的滚动来给以不同的分布,结合布局的flow.sectionInset\minimumLineSpacing等一些属性来调整好看的样子 1668 | * WebView小坑 1669 | > 如果加载的不是你想加载的页面,那么就隐藏webView。这是一个小技巧 1670 | > [webView stringByEvaluatingJavaScriptFromString:js]; // oc与jsp的交互 1671 | > NSString *html = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementsByTagName('html')[0].outerHTML;"]; // 先获取源码,再网页上德源码进行jsp操作更快 1672 | 1673 | > supportedInterfaceOrientations,返回控制器支持的方向,返回的枚举类型要包含mask字眼 1674 | > searchBar做titleView 的问题。默认情况下,searchbar会拉的很长,一直到导航栏的宽度差不多。这个时候外面包装了UIView,以后发现系统自带的不好改变尺寸,可以尝试一下这么搞. 1675 | 1676 | ``` 1677 | 1678 | **70.自定义输出日志** 1679 | ``` 1680 | #ifdef DEBUG // 处于开发阶段 1681 | #define JNLog(...) NSLog(__VA_ARGS__) 1682 | #else // 处于发布阶段 1683 | #define JNLog(...) 1684 | #endif 1685 | 1686 | fmt输出日志 #define NSString(...) [NSString stringWithFormat:__VA_ARGS__] 1687 | 1688 | ``` 1689 | 1690 | **开发常用宏** 1691 | ``` 1692 | /**判断是真机还是模拟器 */ 1693 | #if TARGET_OS_IPHONE 1694 | //iPhone Device 1695 | #endif 1696 | 1697 | #if TARGET_IPHONE_SIMULATOR 1698 | //iPhone Simulator 1699 | #endif 1700 | 1701 | /**使用ARC和不使用ARC */ 1702 | #if __has_feature(objc_arc) 1703 | //compiling with ARC 1704 | #else 1705 | // compiling without ARC 1706 | #endif 1707 | 1708 | // 方正黑体简体字体定义 1709 | #define FONT(F) [UIFont fontWithName:@"FZHTJW--GB1-0" size:F] 1710 | //定义一个API 1711 | #define APIURL @"http://xxxxx/" //登陆API #define APILogin [APIURL stringByAppendingString:@"Login"] 1712 | //设置View的tag属性 1713 | #define VIEWWITHTAG(_OBJECT, _TAG) [_OBJECT viewWithTag : _TAG] 1714 | //程序的本地化,引用国际化的文件 1715 | #define MyLocal(x, ...) NSLocalizedString(x, nil) 1716 | //G-C-D 1717 | #define BACK(block) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block) 1718 | #define MAIN(block) dispatch_async(dispatch_get_main_queue(),block) 1719 | //NSUserDefaults 实例化 1720 | #define USER_DEFAULT [NSUserDefaults standardUserDefaults] 1721 | //由角度获取弧度 有弧度获取角度 1722 | #define degreesToRadian(x) (M_PI * (x) / 180.0) 1723 | #define radianToDegrees(radian) (radian*180.0)/(M_PI) 1724 | //读取本地图片 1725 | #define LOADIMAGE(file,ext) [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]pathForResource:file ofType:ext]] 1726 | //定义UIImage对象 1727 | #define IMAGE(A) [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:A ofType:nil]] 1728 | ``` 1729 | 1730 | **71.Tableview 性能优化** 1731 | 1732 | ``` 1733 | 1734 | 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法; 1735 | 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口; 1736 | 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!(SDWebImage已经实现异步加载,配合这条性能杠杠的)。 1737 | 除了上面最主要的三个方面外,还有很多几乎大伙都很熟知的优化点: 1738 | 1739 | 正确使用reuseIdentifier来重用Cells 1740 | 尽量使所有的view opaque,包括Cell自身 1741 | 尽量少用或不用透明图层 1742 | 如果Cell内现实的内容来自web,使用异步加载,缓存请求结果 1743 | 减少subviews的数量 1744 | 在heightForRowAtIndexPath:中尽量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果 1745 | 尽量少用addView给Cell动态添加View,可以初始化时就添加,然后通过hide来控制是否显示 1746 | ``` 1747 | 1748 | 1749 | 1750 | --------------------------------------------------------------------------------