Effective Objective-C 总结

1、类的头文件中尽量少引入其他头文件,有可能的话使用@class代替

2、字面量语法在可读性上优于与之等价的方法,但是要做好数据校验,不能将nil数据直接存入其中。
例如:

NSArray *arr = @[@"1",@"asd",nil];

3、能用常量定义,就尽量不要使用#define预处理指令。常量定义带有数据类型,编译前会进行数据格式校验。
只在本文件使用,在.m文件中

static const NSTimeInterval kAnimationDuration = 0.3; 

要做为全局常量,在.h文件中

 extern NSString *const EOCStringConstant;
 extern const NSTimeInterval  EOCAnimationDuration;

在.m文件中

NSString *const EOCStringConstant = @"Value";
const NSTimeInterval  EOCAnimationDuration = 0.3;

4、枚举类型定义使用 typedef NS_ENUM/NS_OPTIONS (type, name),
NS_ENUM做状态枚举时,在switch中不要实现default,或者在default中添加断言。防止枚举中添加了状态而switch中忘记修改。
NS_OPTIONS用于实现枚举多选,各选项的值定义为2的幂,通过按位或操作进行组合。通过位与运算判断是否包含某个值。

5、属性在@interface中通过@property定义,在@implementation中可以使用@synthesize 自定义属性对应的实例变量名。@dynamic会告诉编译器不要为属性创建实例变量,也不要创建存取方法。
属性特性:
原子性 : atomic会给属性添加同步锁,防止多线程中修改属性出现问题。nonatomic则不使用同步锁。使用同步锁后,属性的读取效率会降低,一般如果不是非常必要,属性会定义为nonatomic。
读写权限:readwrite为默认特性,默认编译器会自动生成getter 和 setter方法。readonly只读特性,一般的使用方法是在头文件中定义只读属性,在实现文件的分类中重新将其定义为readwrite。
例如
.h文件中

@interface Class: NSObject
@property (nonatomic, readonly) NSString  *parmString;
@end

.m文件中

@interface Class()
@property (nonatomic, readwrite) NSString  *parmString;
@end

内存管理语义:影响setter方法在设置时属性的引用计数变化。

  • assign:只针对纯量类型 CGFloat、NSInteger等简单的赋值使用。
  • strong:会导致传入数据的引用计数加一。当前类会持有该属性
  • weak:不会增加属性的引用计数,当前类不持有该属性。
  • unsafe_unretained:该特性与weak的区别在于,对象释放时属性的值不会清空。
  • copy:与strong的区别在于,copy特性会直接copy一份setter方法传进来的值,而不是直接持有该值。copy特性经常用于保护属性的封装性。例如NSString类型的属性,外部传过来的是NSMutableString类型,这时如果不copy一份不可变的数据 ,属性的值就有可能被外部修改。

方法名:getter=<name> 指定一个自定义的getter方法,例如

@property (nonatomic, getter = isOn) BOOL on;

setter=<name>指定一个自定义的设置方法,这种用法不太常见。

6、直接使用属性的实例变量要比调用属性的getter/setter方法效率高。

7、NSObject 中定义的isEqual:方法要比在子类中定义的isEqualXXXX:方法慢。
例如 NSString调用 isEqualToString:方法比调用isEqual:快,因为isEqual:还需要检测对象类型。

8、hash在collection(集合)中作为对象的索引,对象相等时其hash值一定相等,相反hash相等并不一定对象就相等。在重写hash函数时,一定要满足此条件。为了让hash能起到索引作用,又不会带来额外的性能代价,一般使用子类的hash进行与操作。
可变对象放入collection(集合)中后,其hash就不能再次改变。因为在对象会根据hash索引放入不同的“箱子数组”中,这时如果改变了hash值,那对象存放的位置就会变成错误的。这时要保证对象的hash值是根据其不可变部分生成的。

9、数据库对象判断相等可以简化为判断属性 identifier相等,identifier在数据库中作“主键”(primary key)

10、NSArrayNSMutableArrayNSDictionaryNSMutableDictionary都是个类簇,通过其方法创建的对象真正类型是一些内部类型,可以使用[obj class]打印真正的类型。

11、用obj_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)方法给对象添加动态属性。
objc_AssociationPolicy@property等价特性

关联类型 等效的@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

12、objc_msgSend是objective-C语言实现的最核心方法。所有的objective-C的消息发送最终都是转换为了objc_msgSend进行传递。其函数原型如下
<return_type> Class_selector(id self, SEL _cmd, …)

objc_msgSend根据对象所属类搜寻其方法列表(list of methods)。如果能找到与selector名字相同的方法,就跳转至实现代码。找不到则沿着继承体系继续向上查找。如果最终找不到相符合的方法,就执行“消息转发”(message forwarding)。

objc_msgSend将匹配结果缓存至“快速映射表”(fast map),下次再向该类发送改方法,会直接从快速映射表中查找,提高查询效率。

13、消息转发机制。
消息转发分为两大阶段。第一阶段询问所属类是否能动态添加方法,处理这个unknown selector,此过程叫“动态方法解析”(dynamics method resolution)。第二阶段涉及“完整的消息转发机制”(full forwarding mechanism),首先会询问是否有其他对象可以处理这条消息,若存在备援接收者(replacement receiver),则将消息转发给备援接收者,否则启动完整的消息转发机制。
消息转发全流程

14、类对象
Objective-C中的每个实例对象都是指向某块内存数据的指针。通用数据类型id本身就是指针。
id类型的定义:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

Class对象的定义:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
}

此结构体存放的“元数据”(metadata)。例如类的实例实现了几个方法。具备多个实例变量等信息。此结构体的首个变量也是isa指针,这说明Class本身也是Objective-C对象。
super_class:结构体里还有个变量叫做super_class,它定义了本类的超类。
isa:类对象所属的类型(也就是isa指针所指向的类型)是另一个类,叫做“元类”(metaclass),用来表达类对象本身所具备的元数据。“类方法”就定义在isa中,这些方法可以理解为isa的实例方法。每个类只有一个isa,每个isa仅有一个与之相关的“元类”。

假设有个名为ComeClass的字类从NSObject中继承而来,则其继承体系如下图:


super_class指针确立了继承关系,isa指针描述了实例所属的类。

15、-(id)initWithCoder:(NSCoder *)decoder 方法通过“解码器”(decoder)将对象数据解压缩,例如:

-(id) initWithCoder:(NSCoder *)decoder
{
    If ([self = [super initWithCoder:decoder]]){
        _property = [decoder decodeFloatForKey:@"width"];
    }
    return self;
}

16、利用description实现NSLog打印对象的自定义信息,在NSLog一个自定义对象时,obj对象会收到description消息,返回的描述信息将输出到console中。系统类重写了description类,所以在NSLog中可以看到比较详细的输出数据,自定义的Class需要用户重写description方法,以便能打印真正用户的信息。例如:

-(NSString *)description
{
    return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">",self.class, self, _firstName, _lastName];
}

上面的信息打印了对象对应的类,对象地址,和包含的属性信息。

17、为了更好的区分私有方法和共有方法,可以给私有方法添加p_前缀,p表示“private”

18、NSCopyingNSMutableCopying协议
在对象调用copy方法时,真正进行复制的方法是copyWithZone:(NSZone *)zone, copy方法只是使用了默认的zone来调用copyWithZone:。在一些自定义的类中,如果要实现copy方法,需要声明该类遵从NSCopying协议,并重写copyWithZone:方法。例如:

@interface EOCPerson : NSObject <NSCopying>

@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;

- (id)initWithFirstName:(NSString *)firstName
            andLastName:(NSString *)lastName;

@end

然后,实现协议中规定的方法:

- (id)copyWithZone:(NSZone *)zone{
    EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName
                                                               andLastName:_lastName];
    return copy;
}

如果要拷贝一份可变数据,比如 [NSArray mutableCopy];则需要重写mutableCopyWithZone:方法。
Foundation框架中所有collection类默认 情况下都是执行浅拷贝,只拷贝容器本身,不复制其中数据。原因是容器中的对象不一定支持拷贝,而调用者也未必想要拷贝容器数据。

19、在循环语句中使用autoreleasepool降低内存峰值,如果在for循环中创建了占用大量内存的临时变量,因为自动释放池要等线程执行下一次事件循环时才会清空。在for循环过程中内存无法释放,就会导致内存持续上涨。在for循环中嵌套 autoreleasepool使用,在每次循环结束,都会把分配的临时内存回收。如下:

    NSArray *databaseRecords = /*...*/;
    
    NSMutableArray *people = [NSMutableArray array];
    for (NSDictionary *record in databaseRecords) {
        @autoreleasepool {
            EOCPerson *person = [[EOCPerson alloc] initWithRecord:record];
            [people addObject:person];
        }
    }

20、block其实是objective-C对c函数指针的封装。block本身也是对象。
块的内部结构
每个Objective-C对象都占据着某个内存区域。因为实例变量的个数及对象所包含的关联数据互不相同,所以每个对象所占的内存区域也有大有小。块本身也是对象,在存放块对象的内存区域中,首个变量是指向Class对象的指针,该指针叫做isa(参见第14条)。其余内存里含有块对象正常运转所需的各种信息。下表详细描述了块对象的内存布局。

image4.jpg

在内存布局中,最重要的就是invoke变量,这是个函数指针,指向块的实现代码。函数原型至少要接受一个void*型的参数,此参数代表块。刚才说过,块其实就是一种代替函数指针的语法结构,原来使用函数指针时,需要用“不透明的void指针”来传递状态。而改用块之后,则可以把原来用标准C语言特性所编写的代码封装成简明且易用的接口。

descriptor变童是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了 copy与dispose这两个辅助函数所对应的函数指针。辅助函数在拷贝及丟弃块对象时运行,其中会执行一些操作,比方说,前者要保留捕获的对象,而后者则将之释放。

块还会把它所捕获的所有变量都拷贝一份。这些拷贝放在descriptor变量后面,捕获了多少个变量,就要占据多少内存空间。请注意,拷贝的并不是对象本身,而是指向这些对象的指针变量。invoke函数为何需要把块对象作为参数传进来呢?原因就在于,执行块时,要从内存中吧这些捕获到的变量读出来。

21、利用GCD 栅栏(barrier)实现同步锁功能。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block block); void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block block);
在队列中,栅栏快必须单独执行,不能语其他块执行。并发队列如果发现接下来要处理的块是栅栏块(barrier block),那么就一直等当前所有并发块执行完毕,才会单独执行这个栅栏块。栅栏块执行过后,在按正常方式继续向下处理。代码实现可以参见:FileCache

这里使用dispatch_barrier_sync代替dispatch_barrier_aync会让setSomeString:方法效率更高,原因是dispatch_barrier_sync会在执行完当前操作后,才会插入后续要添加到queue中代码。
dispatch_barrier_syncdispatch_barrier_aync的区异同:
共同点:
1、等待在它前面插入队列的任务先执行完
2、等待他们自己的任务执行完再执行后面的任务
不同点:
1、dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们
2、dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务。

本文作者: ctinusdev
原文链接: https://ctinusdev.github.io/2017/07/30/EffectiveObjcLearn
转载请注明出处!

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

推荐阅读更多精彩内容