iOS开发中的一些tips吧

最近比较忙,事也多,这些tips有些乱,没有进行整理,先贴出来,之后会好好整理,大家多包涵。
文件目录管理:

Utils:这里放一些与应用无关的帮助类文件和全局工具类文件

Components:(项目名)这是重头戏,所有的模块都会被放到这里主工程文件

Vendors:这里放一些第三方的类库,如MagicalRecord / CCUIViewWrapper等等

Models:与数据打交道的Model都会放到这里(数据,业务,viewmodel)

Config:应用的默认设置文件,plist文件等

模块粒度划分:

按需要拆分如果其他地方很少会单独使用到这些实体,就可以整体作为一个模块。当然如果将来其他模块会用到其中的某一部分,就需要重新拆分了

xcode:

昨天手贱升级到iOS10,然后Xcode就得升级到8......于是各种失效......我也是无语了

百度到Xcode8注释快捷键的失效解决方法,这里码一下:

命令运行: sudo /usr/libexec/xpccachectl

然后必须重启电脑后生效

objection:

objection 是一个轻量级的依赖注入框架;原理是先定义一个协议(protocol),然后通过objection来注册这个协议对应的class(在+(void)load里),需要的时候,可以获取该协议对应的object([injector getObject:@protocol(….Protocol)];)。对于使用方无需关心到底使用的是哪个Class,反正该有的方法、属性都有了(在协议中指定)。这样就去除了对某个特定Class的依赖。也就是通常所说的「面向接口编程」;

CopyOnWrite:

读写分离技术,读的时候读原来的对象,写的时候会复制一个原对象的副本,往新对象修改,改完之后把指针指向新对象。优点:1.减少扩容开销。根据实际需要,初始化CopyOnWriteMap的大小,避免写时CopyOnWriteMap扩容的开销;2.使用批量添加。因为每次添加,容器每次都会进行复制,所以减少添加次数,可以减少容器的复制次数。如使用上面代码里的addBlackList方法; 缺点:1.即内存占用问题;2.数据一致性问题;

alloc init:

init 还有一个需要注意的问题。某些情况下,init 会造成 alloc 的原本空间不够用,而第二次分配内存空间。所以下面的写法是错的

根据设计模式的 Single Responsibility 的设计原则,苹果觉得 alloc 和 init 是做的 2 件不同的事情,把这两件事情分开放在 2 个函数中,对于程序员更加清楚明了。更详细查阅文档后,我觉得这是由于历史原因,让苹果觉得 alloc 方法过于复杂,在历史上,alloc 不仅仅是分配内存,还可以详细的指定该内存所在的内存分区

@defs 关键字用于返回一个 Objective-C 类的 struct 结构,这个 struct 与原 Objective-C 类具有相同的内存布局。就象你所知的那样,Objective-C 类可以理解成由基本的 C struct 加上额外的方法构成 struct { @defs( NSObject) }

@compatibility_alias AliasClassName ExistingClassName 用于给一个类设置一个别名

view渲染和内存使用:

    CALayer 是一个 bitmap 图象的容器类,当 UIView 调用自身的 drawRect 时,CALayer 才会创建这个 bitmap 图象类。具体占内存的其实是一个 bitmap 图象类,CALayer 只占 48bytes, UIView 只占 96bytes。而一个 iPad 的全屏 UIView 的 bitmap 类会占到 12M 的大小!

在 iOS6 时,当系统发出 MemoryWarning 时,系统会自动回收 bitmap 类。但是不回收 UIView 和 CALayer 类。这样即回收了大部分内存,又能在需要 bitmap 类时,通过调用 UIView 的 drawRect: 方法重建

    当一段内存被分配时,它会被标记成 “In use“, 以防止被重复使用。当内存被释放时,这段内存会被标记成 “Not in use”,这样,在有新的内存申请时,这块内存就可能被分配给其它变量。CALayer 包括的具体的 bitmap 内容的私有成员变量类型为 [CABackingStore](http://blog.spacemanlabs.com/2011/08/calayer-internals-contents/), 当收到 MemroyWarning 时,CABackingStore 类型的内存区会被标记成 volatile 类型(这里的 volatile 和 C 以及 Java 语言的 volatile 不是一个意思),volatile 表示,这块内存可能被再次被原变量重用。这样,有了上面的优化后,当收到 Memoy Warning 时,虽然所有的 CALayer 所包含的 bitmap 内存都被标记成 volatile 了,但是只要这块内存没有再次被复用,那么当需要重建 bitmap 内存时,它就可以直接被复用,而避免了再次调用 UIView 的 drawRect: 方法

支付宝的插件机制(应用内嵌别的应用):

支付宝的插件机制整体上就是通过 html 和 javascript 方式实现的,主要的好处是:

跨平台 (可以同时用在 iOS 和 Android 客户端)

省流量(不需要的插件不用下载,插件本地缓存长期存在不会过期,自己管理插件更新逻辑)

更新方便(不用每次提交 AppStore 审核)

坏处如果非要说有的话,就是用 javascript 写 iOS 界面,无法提供非常炫的 UI 交互以及利用到 iOS 的所有平台特性。不过象支付宝这种工具类应用,也不需要很复杂的 UI 交互效果。

另外教大家一个小技巧,如果你不确定某个页面是不是 UIWebView 做的,直接在那个页面长按,如果弹出 “ 拷贝,定义,学习 “ 这种菜单,那就是确定无疑是 UIWebView 的界面了。如下图所示

结构体:

如下 2 个结构体 SampleA 和 SampleB 在内存上是完全一样的,原因是结构体本身并不带有任何额外的附加信息

struct SampleA {

        int a;

        int b;

        int c;

};

struct SampleB {

        int a;

struct Part1 {

        int b;

};

struct Part2 {

        int c;

};

};

特殊字体:

使用动态下载中文字体的 API 可以动态地向 iOS 系统中添加字体文件,这些字体文件都是下载到系统的目录中(目录是/private/var/mobile/Library/Assets/com_apple_MobileAsset_Font/),所以并不会造成应用体积的增加。并且,由于字体文件是 iOS 系统提供的,也免去了字体使用版权的问题。虽然第一次下载相关的中文字体需要一些网络开销和下载时间,但是这些字体文件下载后可以在所有应用间共享,所以可以遇见到,随着该 API 使用的普及,大部分应用都不需要提示用户下载字体,因为很可能这些字体在之前就被其它应用下载下来了

Objective-C对象模型:

方法的定义列表是一个名为 methodLists的指针的指针(如下图所示)。通过修改该指针指向的指针的值,就可以实现动态地为某一个类增加成员方法。这也是Category实现的原理。同时也说明了为什么Category只可为对象增加成员方法,却不能增加成员变量;

系统提供的 KVO 的实现,就利用了动态地修改 isa 指针的值的技术,所以不应依赖isa确定所属类,可能不准确,而应依赖类方法去确定

Objective-C 提供了以下 API 来动态替换类方法或实例方法的实现:

class_replaceMethod 替换类方法的定义

method_exchangeImplementations 交换 2 个方法的实现(内部实现相当于调用了 2 次method_setImplementation方法)

method_setImplementation 设置 1 个方法的实现

这 3 个方法有一些细微的差别,给大家介绍如下:

class_replaceMethod在苹果的文档(如下图所示)中能看到,它有两种不同的行为。当类中没有想替换的原方法时,该方法会调用class_addMethod来为该类增加一个新方法,也因为如此,class_replaceMethod在调用时需要传入types参数,而method_exchangeImplementations和method_setImplementation却不需要

initWithFrame:

用[UIView new]或者[[UIView alloc] init]都会调用initWithFrame这个函数(有些UIView的子类有特殊情况,比如UITableViewCell,怀疑apple对其做过特殊处理

响应者链:

响应者树的构造过程是在ViewDidLoad周期中来完成的,能够在viewDidAppear:方法中调用起效

view在什么情况下能够变成第一响应者?条件是什么?

只要继承自UIResponder的子类都可使以成为第一响应者的,但是除了UITextField,必须在实现文件中覆盖canBecomeFirstResponder方法,如果按钮的target是设置为nil的,所以系统将去寻找当前第一响应者,如果第一响应者有pressPrint这个方法,那么他会调用它的pressPrint方法。而现在viewD中是实现了此方法

UITouch事件和响应者链走的不是同一套机制,如果UITouch事件响应了手势就不再响应UIEvent事件;或者 UIButton 上加UIView, 因为不是走同一套机制,所以会不响应UIEvent事件;(如果药响应,可以重写最上层view的 hittest 返回需要点击的button为 hitest 响应的对象(穿透原理))

优化:

处理图片时,你也可以让GPU为你工作来代替使用Core Graphics。使用Core Image,你不必用CPU做任何的工作就可以在图片上建立复杂的效果。你可以直接在OpenGL上下文上直接渲染,所有的工作都在GPU上完成

重写drawRect:,默认的模式是将内容缩放以填充视图的范围,并且当视图的frame改变时并不会重新绘制

如果你使用的层引发了离屏渲染,那么你最好避免这种方式。增加遮罩,设置圆角,设置阴影都造成离屏渲染。(动画也会,但是动画中可以提升性能)

当一个层上面的所有像素和屏幕上面的像素完美对应,我们就说这个层是像素对齐的。主要有2个原因导致可能不对齐。第一个是放大缩小;当放大或是缩小是,纹理的像素和屏幕像素不对齐。另一个原因是当纹理的起点不在一个像素边界上。

这2种情况,GPU不得不做额外的计算。这个需要从源纹理中混合很多像素来创建一个像素用来合成。当所有像素对齐时,GPU就可以少做很多工作

NSNumber:

所以给一个weak对象赋值,它并不会马上释放,而是会放到autorelease pool中,与autorelease pool一起释放

Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate

Tagged Pointer指针的值不再是地址了,而是真正的值(其实是将值存在指针内,即指针也就是值)。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已!所以,它的内存并不存储在堆中,也不需要malloc,free。

在内存读取上有着3倍的效率(以前是寻址->发消息->获取值,现在直接获取值),创建时比以前快106倍

load:

所以库的初始化顺序可以如下:

  1. 初始化我们引用的库
  2. 执行我们自己库的Objective-C的load函数
  3. 执行C++和C的static初始化变量
  4. 初始化引用我们库的其他库

在我们的编写的库中,会有很多类重写load函数,他们之间的执行顺序是不确定的。

当父类和子类都实现load函数时,父类的load函数会被先执行。load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。

在Category中写load函数是不会替换原始类中的load函数的,原始类和Category中的load函数都会被执行,原始类的load会先被执行,再执行Category中的load函数。当有多个Category都实现了load函数,这几个load函数执行顺序不确定

如果类包含继承关系,父类的initialize函数会比子类先执行。由于是系统自动调用,也不需要显式的调用父类的initialize,否则父类的initialize会被多次执行。

假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的

但是假如我们是修改系统的类,一般会通过添加Category来添加功能,但是Category中如果修改initialize会导致原生的intialize不会执行,所以放在load中会比较妥当

bridge:

如之前提到的,MRC 下的 Toll-Free Bridging 因为不涉及内存管理的转移,相互之间可以直接交换使用:

NSString *nsStr = (NSString *)cfStr;CFStringRef cfStr = (CFStringRef)nsStr;// 调用函数或者方法NSUInteger length = [(NSString *)cfStr length];NSUInteger length = CFStringGetLength((CFStringRef)nsStr);// releaseCFRelease((CFStringRef)nsStr);[(NSString *)cfStr release];

而在 ARC 下,事情就会变得复杂一些,因为 ARC 能够管理 Objective-C 对象的内存,却不能管理 CF 对象,CF 对象依然需要我们手动管理内存。在 CF 和 ObjC 之间 bridge 对象的时候,问题就出现了,编译器不知道该如何处理这个同时有 ObjC 指针和 CFTypeRef 指向的对象。

如何实现父类的私有方法子类可以访问:

这时候,我们需要使用__bridge (不做任何处理,指向同一对象,由转化前的对象管理内存,后来的对象相当于一个弱指针), __bridge_retained(做一次retain,共同管理内存), __bridge_transfer (转化,由转化后的对象管理内存)

有Father类和Son类,继承关系,可以考虑建一个如FatherPrivate.h的私有header:

<colgroup><col style="width: 603px;"></colgroup>
|

// FatherPrivate.h

@interface Father ()

@property (nonatomic, copy) NSString *privateThingSonNeed;

  • (void)privateMethodNeedsSonOverride;

@end

|

同时在Father.m和Son.m中同时import这个私有header,这样,Father和Son内部对于定义的属性和方法都是透明的,而对外部是隐藏的(因为两个类的header中都没有import这个私有header

2D绘图:

这里我们发现,UIKit的代码并没有传递context。这是因为UIKit或AppKit的context是隐形的。UIKit和UIKit维护着一个context栈。这些UIKit的方法始终在最上面的context绘制。你可以使用UIGraphicsPushContext()和 UIGraphicsPopContext()来push和pop对应的context。

BDD和基于bdd的测试框架kiwi:

一个典型的BDD的测试用例包活完整的三段式上下文,测试大多可以翻译为Given..When..Then的格式,即在一个上下文下,当给予一个的条件时,应该有的行为或特性;

可变参数的函数:

-(id)initWithTitle:(NSString)title message:(NSString)message clickedBlock:(void (^)(CYAlertView *alertView, BOOL cancelled, NSInteger buttonIndex))clickedBlock cancelButtonTitle:(NSString )cancelButtonTitle otherButtonTitles:(NSString)otherButtonTitles,…NS_REQUIRES_NIL_TERMINATION; (可变参数列表)

collectionView自定义动画:

给系统方法返回初始布局和终止布局,系统会自动过度,如果需要区分状态,只要使用系统的枚举来记录判断当前出去插入,删除,或者转场等等状态予以区分即可

绘图:

使用UiKit,你只能在当前上下文中绘图,所以如果你当前处于UIGraphicsBeginImageContextWithOptions函数或drawRect:方法中,你就可以直接使用UIKit提供的方法进行绘图。如果你持有一个context:参数,那么使用UIKit提供的方法之前,必须将该上下文参数转化为当前上下文。幸运的是,调用UIGraphicsPushContext 函数可以方便的将context:参数转化为当前上下文,记住最后别忘了调用UIGraphicsPopContext函数恢复上下文环境

运行时:

我们经常在方法中使用self关键字来引用实例本身,但从没有想过为什么self就能取到调用当前方法的对象吧。其实self的内容是在方法运行时被偷偷的动态传入的。

当objc_msgSend找到方法对应的实现时,它将直接调用该方法实现,并将消息中所有的参数都传递给方法实现,同时,它还将传递两个隐藏的参数:

1.接收消息的对象(也就是self指向的内容)

2.方法选择器(_cmd指向的内容)

[super class]等价于[self class]

尽管转发很像继承,但是NSObject类不会将两者混淆。像respondsToSelector: 和 isKindOfClass:这类方法只会考虑继承体系,不会考虑转发链。比如上图中一个Warrior对象如果被问到是否能响应negotiate消息:结果是NO,因为它虽然能够接受negotiate消息而不报错,是因为它靠转发消息给Diplomat类来响应消息而不是实现了Diplomat方法;

1)、category会在编译时将方法和属性链接到类的方法和属性列表中,category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA

2)、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休_,殊不知后面可能还有一样名字的方法。

3)、附加category到类的工作会先于+load方法的执行,所以可以在类的+load方法中调用category方法。

4)、AssociationsManager里面是由一个静态AssociationsHashMap 来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。而在对象的销毁逻辑里面runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作

静态库, 动态链接库,动态加载库:

沙盒目录及使用:

[NSUserDefaults standardUserDefaults] Library下的Preference,使用系统的plist文件;如果想要新建一个plist文件可以:

[[NSUserDefaults alloc]initWithSuiteName:@"gxw”]; 创建一个叫 gxw.plist的文件,如果已有就直接获取,没有就重新创建一个;

UIWindow:

UIWindow有 UIWindowLevel 属性,是一个 cgfloat 类型 包含三个枚举 normal = 0.0,statusbar = 1000.0,alert = 2000.0 当前keywindow队列中level值越大的window显示在最上面; 系统默认的是 normal = 0.0; statusbar状态栏的window 是 1000.0; 系统 alert 的是1996.0 左右; 设置 key 用来接收键盘输入等非触摸事件;如果想实现一些遮盖状态栏的弹窗可以利用 UIWindowLevel 实现;

编译警告:

在Build Phases中的 Compile Sources中每个文件加入 -w 可以忽略掉该文件的编译警告, 加入 -Wno-unused-variable 可以忽略未使用的变量名警官

对象的转换:

为什么要在主线程刷新UI:(自线程的UI操作需要在自线程生命周期结束后执行,实际上也是返回给主线程执行)

1.(google的一套UI刷新规则)多线程操作UI控件,由于移动设备UI控件多,复杂,还有动画,交互等等复杂的逻辑容易出错,且移动设备设计之初内存小,CPU数量和性能原因,所以把UI刷新统一放在UI线程执行,简化了逻辑也符合移动端较强UI展示的需要,也方便管理提高UI的使用效率;

2.如果能解决多线程的弊端确实可以提升效率,但是与其对规则推倒重来使用复杂的规则不如专注于设备硬件性能提升上;

3.自线程执行耗时操作,主线程执行重量级操作;

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,115评论 29 470
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,277评论 0 6
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,530评论 33 466
  • 【背景】 大家在日常生活当中有没有发现一个很普遍的现象:平时什么事情也做不好,但总是瞧不上别人,动不动就觉得自己比...
    夜猫子谭娟阅读 371评论 0 0