1. 了解Objective-C语言的起源
2. 在类的头文件中尽量少引入其他头文件
3. 多用字面量语法,少用与之等价的方法
例子:
/// 创建
NSNumber *numObj = @(1)
NSArray *arr = @[@"cat", @"dog"]
NSString *str = @"sdfsd"
NSDictionary *dic = @{@"first": @"tom"}
/// 取下标
NSString *dog = arr[1]
NSString *name = dic[@"first"]
4. 多用类型常量,少用#define
预处理指令
#define
不含有类型信息, 即使有人重新定义了,也不会报错,但是会导致常量值不一致static const
定义 static限制作用域仅在当前文件,外部不可以通过extern引用 const使常量不可以改变- 当需要公开某个变量的时候。
// header file
extern NSString *const 变量名
// implementation file
NSString *const 变量名 = @"";
5. 用枚举表示状态,选项,状态码
6. 理解属性这一概念
@synthesize
指定实例变量的名字。将属性生成的实例变量改成自定义的名字。@dynamic
告诉编译器不要自动合成存取方法,访问属性的代码也不会报错,因为它相信这些方法可以在运行期被找到- 属性的所有权语义
- 若是自己来实现存取方法的时候,应该保证其具备相关属性申明的特质。比如在自定义的构造方法中。
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
if((self = [super init])) {
_firstName = [firstName copy];
_lastName = [lastName copy];
/// 这里不能使用属性所对应的设置方法 原因见第七条
}
return self;
}
7. 在对象内部尽量直接访问实例变量
- self.属性名和直接访问实例变量的区别
- 对象内部读取数据时,应该使用实例变量来读,写入数据是,应该通过属性来写
- 初始化方法和delloc中,使用实例变量,原因时因为子类可能会重写属性的设置方法,在初始化时,父类的初始化方法中,会调用子类的设置方法。
8. 理解“对象等同性”这一概念
- == 操作符比较的是两个指针
- NSObject两个判断等同性的方法,isEqual,hash,isEqual默认实现是当其指针完全相等时返回true。但是当isEqual判断两个对象相等时,hash必须返回同一个值
- 重写hash方法的一种思路,将所有属性的hash值按位异或
- 可以根据需求重写isEqual方法,比如根据唯一标识判断
- 在容器中放入可变对象的时候,就不应该再改变其内容了。
NSMutableSet *set = [NSMutableSet new];
NSMuatableArray *mutaArrA = [@[@(1), @(2)] mutableCopy];
NSMuatableArray *mutaArrB = [@[@(1), @(2)] mutableCopy];
NSMuatableArray *mutaArrC = [@[@(1) mutableCopy];
[set addObject: mutaArrA];
[set addObject: mutaArrB]; // 此时set中只会有一个元素 因为AB两个数组在等通行判断上返回true
[set addObject: mutaArrC]; // 此时set中含有两个元素
[mutaArrC addObject: @(2)]; // 现在set中含有两个相同的两个元素
NSSet *setB = [NSMutableSet copy]; // setB又只会含有一个元素了
9.以“类族模式”隐藏实现细节
- 例子,
-(UIButton)buttonWithType:(UIButtonType)type
通过type返回不同的UIButton的子类,但是子类对外使用统一的接口 - Cocoa里的类族,大部分的容器类都是类族
- 向类族中增加子类实体需要遵循的规则如下:
10. 在既有类中使用关联对象存放自定义数据
- 对象关联类型
关联类型 等效的@property属性 OBJC_ASSOCIATION_ASSIGN assign OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy OBJC_ASSOCIATION_RETAIN retain OBJC_ASSOCIATION_COPY copy objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
此方法以给定的键和策略为某对象设置关联对象值。objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
此方法根据给定的键从某对象中获取相应的关联对象值。objc_removeAssociatedObjects(id _Nonnull object)
此方法移除指定对象的全部关联对象- 键的等同性判断只会看两个指针的地址,而不是isEqual方法。
- 例子,在创建UIKAlertView的时候,将该弹窗的处理事件的block和该弹窗关联,然后在弹窗的代理方法里面取出Block执行,实现弹窗处理逻辑写在弹窗哪一块
11. 理解objc_msgSend的作用
- OC发送消息
id returnValue = [someObject messageName: parameter];
编译器会转变为id returnValue = objc_msgSend(id Self, SEL cmd, ...)
该方法需要在接收者所属的的类中搜寻其方法列表,匹配成功后将该方法缓存在快速映射表里面,并跳转到具体执行。
12. 理解消息转发机制
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O8DMWib4-1662015588609)(https://gitee.com/Lcmzy89/drawing-bed/raw/master/image/20220629161133.png “消息转发”)]
- 对象在收到无法解读的消息后,调用
+(BOOL)resolceInstanceMethod:(SEL)selector
,可以在这个方法可以新增一个处理此选择子的方法 - 备援接收者 当前接收者还有第二次机会可以处理未知的选择子,
-(id)forwardingTargetForSelector:(SEL)selector
,可以经由此方法,将能够处理这个选择子的其他对象返回。在外界看来,好想是对象亲自处理了这些消息。 - 完整的消息转发
首先创建NSInvocation对象,把与尚未处理的那条消息的有关细节全部都封于其中,包含选择子,目标对象及参数。消息派发系统将消息指派给目标对象。此步骤会调用-(void)forwardInvocation:(NSInvocation*)invocation
,过程中可以改变消息内容,比如追加另外一个参数,或者该换选择子 - 为了说明消息转发机制的意义,下面示范如何以动态方法解析来实现@dynamic属性。假设要编写一个类似于“字典”的对象,它里面可以容纳其他对象,只不过开发者要直接通过属性来存取其中的数据。这个类的设计思路是:由开发者来添加属性定义,并将其声明为@dynamic,而类会自动处理相关
13. 用“方法调配技术”调试“黑盒方法”
- 在运行期间,可以向类中新增洪战辉替换选择子所对应的方法实现
- 使用另一份实现来替换原有的方法实现,叫“方法掉配”,可以通过这种方式向原有实现中添加新功能。
/// 给NSStirng的lowercaseString添加日志功能 @implemention NSString(EOCMyAddtions) /// 在NSString中自己新添加方法 实现日志功能 - (NSStirng)eco_myLowercaseString { NSString *lowercase = [self eco_myLowercaseString]; NSLog(@"%@ => %@", self, lowercase) return lowercase; } Method originaMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swapped = class_getInstanceMethod([NSString class], @selector(eco_myLowercaseString)); /// 交换两个方法实现 method_exchangeImplementations(originaMethod, swapped) /// 现在调用[NSString lowercaseString]就会实现在原有方法上添加日志的效果
14. 理解“类对象”的用意
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X3HLwuQx-1662015588609)(https://gitee.com/Lcmzy89/drawing-bed/raw/master/image/111.png “objc_class的结构体”)]
- 在继承体系中查询类型信息
- isMemberOfCalss,判断对象是否是某个特定类的实例。isKindOfClass判断随想是否是某个类或其派生类的实例。
- 也可以使用 == 判断类对象是否等同的方式(不建议)。因为类对象都是单例,在应用程序范围内,每个类的Class仅有一个实例。即便能这样做,我们也应该尽量使用类型信息查询方法,而不应该直接比较两个类对象是否等同,因为前者可以正确处理那些使用了消息传递机制(参见第12条)的对象。比方说,某个对象可能会把其收到的所有选择子都转发给另外一个对象。这样的对象叫做“代理”(proxy),此种对象均以NSProxy为根类。通常情况下,如果在此种代理对象上调用class方法,那么返回的是代理对象本身(此类是NSProxy的子类),而非接受的代理的对象所属的类。然而,若是改用“isKindOfClass:”这样的类型信息查询方法,那么代理对象就会把这条消息转给“接受代理的对象”(proxiedobject)。也就是说,这条消息的返回值与直接在接受代理的对象上面查询其类型所得的结果相同。因此,这样查出来的类对象与通过class方法所返回的那个类对象不同,class方法所返回的类表示发起代理的对象,而非接受代理的对象。
- isa指针所指的对象是另外一个类,叫做元类,用来表述类对象本身所具备的元数据。“类方法”就定义于此处,可以理解成类对象的实例方法。每个类仅有一个“类对象”,而每个类对象,仅有一个与之相关的“元类”。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXKDIkbY-1662015588610)(https://gitee.com/Lcmzy89/drawing-bed/raw/master/image/sjdhfvdjshfvjdshvfhjds.png “某个实例的继承体系”)] isKindOfClass
和obj.class == SomeClass.class
的区别
如果某个对象将其所有的选择子都转发给另外一个对象,isKindofClass会将被代理的对象返回。class方法只会将自己的类型值返回,推荐用isKindOfClass.
15. 用前缀避免命名空间冲突
16. 提供全能初始化方法
17. 实现description方法
18. 尽量使用不可变对象
- 尽量把对外公布的属性设置为只读,而且只在确有必要的时候才将属性对外公布。
- 可哟在对象内部将readonly属性重新声明为readwrite。这样会有线程安全问题,比如内部在写入某属性时,外部在读取数据。
- 不要把可变的collection作为属性公开,而应提供相关方法,以此修改对象中的可变collection。
19. 使用清晰而协调的命名方式
- 如果方法的返回值是新创建的,那么方法名的首个词应是返回值都得类型,比如intValue,属性的存取方法除外
- 应该把表示参数类型的名词放在参数前面
- 如果方法要在当前对象上执行操作,那么就应该包含动词,若执行操作时还需要参数,则应该把在动词后面加上一个或多个名词
- 不要使用str这种简称,应该使用string这样的全称
Boolean
属性应加is前缀,如果某个方法返回非属性的Boolean值,那么应该根据其功能,选用has或is当前缀- 将get这个前缀留给那些借由“输出函数”来保存返回值的方法
20. 为私有方法名加前缀
21. 理解Objective-C错误模型
- 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常
- NSError封装的信息
- NSError的用法
NSError *error = nil; BOOL ret = [object doSomething: &error]; // 传递该指针的地址 方法里面给error的指针赋值,实现功能 if(error) {// 处理错误 }
- (BOOL)doSomething:(NSError **)error { if(/*该条件为true会由错误*/) { if(error) { *error = [NSErrorWithDomain: domain code: code userInfo: userInfo]; } return NO; } else { } return YES; }
22.理解NSCopying协议
- 拷贝对象通过copy方法完成,需要实现NSCopying协议。真正需要实现的是copyWithZone,Copy方法由NSobject实现,内容是以默认区为参数调用copyWithZone。
例:-(id)copywithZone:(NSZone*)zone { EOCPerson *copy = [[[self class] allocWithZone:zone] initwithFirstName:firstName andLastName:lastName]; return copy; }
23.通过委托与数据源协议进行对象间通信
- 调用代理的方法时候需要判断代理是否实现了该方法。
- 判断代理是否实现了某个方法,只有第一次的检测结果有用。将方法响应能力缓存起来的最佳途径是使用“位段”。我们可以把结构体中的某个字段所占用的二进制位数设为特定的值。
例:struct data { unsigned int fieldA : 8; // 8个二进制位 unsigned int fieldB : 4; unsigned int fieldC : 2; unsigned int fieldD : 1; }
24.将类的实现方法分散到便于管理的各个分类之中
25.总是为第三方类的分类名称加前缀
26.勿在分类中声明属性
- 除了class-continuation分类,其他分类都无法向类中新增实例变量。
27.使用“class-continuation分类”隐藏实现细节
28.通过协议提供匿名对象
29.理解引用计数
- NSObject协议声明了下面三个方法用于操作计数器,以递增或递减其值。
** retain 递增保留计数
** release递减保留计数
** autorelease 待稍后清理“自动释放池”时,再递减保留计数。
30.以ARC简化引用计数
31.在delloc方法中只释放引用并解除监听
- 在delloc方法中,应该做的事就是释放指向其他对象的引用,并取消原来订阅的“键值观测”(KVO),或NSNotificationCenter等通知,不要做其他事情。
- 如果对象持有文件描述符等系统资源,那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定:用完资源后 必须调用close方法。
- 执行异步的方法不应该在delloc里面调用。
32.编写“异常安全代码”时留意内存管理问题
33.以弱引用避免保留环
34.以“自动释放池”降低内存峰值
35.用“僵尸对象”调试内存管理问题
- 将NSZombieEnable环境变量设为YES,即可开启“僵尸对象”功能。运行期系统会把所有已经回收的实例转换成特殊的僵尸对象。而不会真正的回收它们。这种对象所在的核心内存无法重用。僵尸对象收到消息后,会抛出异常,其中准确说明了发送过来的消息,并描述了回收之前的那个对象。
- 也可以在XCode里打开此选项。
- 系统会修改对象的isa指针,令其指向特殊的僵尸类,从而使该对象变为僵尸对象。僵尸类能够响应所有的选择子。
36.不要使用retainCount
37.理解“块”这一概念
- 块的内部结构
invoke是一个函数指针,指向块的实现代码。
descriptor变量是指向结构体的指针,每个块都包含此结构体,其中申明了块对象的总体大小,还申明了copy与dispose这两个辅助函数所对应的函数指针。 - 块可以分配在栈和堆上,也可以是全局的,分配在栈上的块可以拷贝到堆里。这样的话就和标准的Objective-C对象一样,具备引用计数了。
38.为常用块类型创建typedef
39.用handler块降低代码分散程度
40.用块引用其所属对象时不要出现保留环
- 找适当的时机解除保留环,比如在不再需要调用块的时候,放弃块的持有。
41.多用派发队列,少用同步锁
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);
- (NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomestring = somestring;
});
return localsomestring;
}
-(void)setSomeString:(NSString *)somestrin {
dispatch_sync(syncQueue, ^{
_someString = somestring;
});
}
** 把设置操作与获取操作安排在序列化的队列里执行,这样的话,所有针对属性的访问操作都同步了。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
** 在队列中,栅栏块必读单独执行,不能与其他块并行。只对并发队列有意义。并发队列如果发现接下来要处理的块是个栅栏块,那么就一直要等当前的所有并发块都执行完毕,才会单独执行这个栅栏块。可以使用栅栏块来实现属性的Set方法。对属性的读取操作依然可以并行执行。
42.多用GCD,少用performSelector系列方法
43.掌握GCD及操作队列的使用时机
- 使用NSOperation及NSOperationQueue
44.通过Diapatch Group机制,根据系统资源状况来执行任务
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
timeout参数标识函数在等待dispatch group执行完毕时,应该阻塞多久。如果执行dispatch group所需时间小于timeout,则返回0,否则返回非0值。此参与也可以去DISPATCH_FOREVER,这表示函数会一直等待dispatch group执行完。void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
等dispatch group执行完毕之后,块会在特定的线程上执行。void dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t));
该函数会反复执行一定的次数,每次传给块的参数值都会递增,从0开始,直至iterations-1。dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL); dispatch_apply(10, queue, ^(size_t i){ //perform task });
使用的队列可以是并发队列。
45.使用dispatch_once来执行只需运行一次的线程安全代码。
46.不要经常使用dispatch_get_current_queue
47.熟悉系统框架
- Foundation框架,所有Objective-C应用程序的基础。CoreFoundation,Foundation中的许多功能和对象,可以在CoreFoundtion无缝衔接。
- CFNetwork,此框架提供了C语言级别的网络通信能力。
- CoreAudio,此框架提供的C语言API可用来操作硬件上的音频硬件。
- AVFoundtion,此框架所提供的Objecti-C可用来回放并录制音频以及视频,比如可以在UI视图类里播放视频。
- CoreData,此框架所提供的Objective-C接口,可将对象放入数据库中,便于持久保存。
- CoreText,此框架提供的C语言接口可以高效执行文字排版及渲染操作。
48.多用块枚举,少用for循环
- NSEnumerator是个抽象基类,其中只定义了两个方法,供具体子类来实现。
- (NSArray *)allObjects - (id)nextObject
其中nextObject,返回枚举的下个对象,Foundation框架中内建的collection类都实现了这种遍历方式。
NSArray *anArr = /*...*/ NSEnumerator *enumerator = [anArr objectEnumerator]; id object; while((object = [enumerator nextObject]) != nil) { // Do somethimg }
- 快速遍历 for in
- 基于块的遍历方式
- (void)enumerateObjectsUsingBlock:(void(^)(id object, NSUInteger idx, BOOL *stop))block /// *stop可以用来停止遍历
49.对自定义其内存管理语义的collection使用无缝桥接
50.构建缓存时选用NSCache而非NSDictionary
* NSCache可以提供优雅的自动删减功能,而且时线程安全的。
* NSPurgeableData,是NSMutableData的子类,这个类实现了NSDiscardableContent协议。系统资源紧张时,可以把这个对象的那一块内存释放掉。可以访问isContentDiscarded查询相关内存是否已释放。
* NSPurgeableData加入NSCache,那么该对象为系统所丢弃时,也会自动从缓存中移除。
51.精简initialize与load的实现代码
* 对于加入运行期系统中的每个类(class)及分类来说,必定会调用此方法,而且仅调用一次。
* initialize和load的区别是initialize是惰性调用。initialize是线程安全的。
* 如果某个类没有实现initialize,但是其超类实现了,就会调用超类的initialize方法。load方法则不然。
* 无法在编译器预设的全局变量,可以放在initialize方法里初始化。比如数组。
52.别忘了NSTimer会保留其目标对象
* NSTimer对象对保留其目标,知道计时器本身失效为止。调用invalidate方法可令计时器失效。
* 注意保留环。
原文地址:https://blog.csdn.net/qq_43613781/article/details/126643514
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_11707.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。