《禅与Objective-C编程艺术》阅读总结

注:《Zen and the Art of the Objective-C Craftsmanship》 中文翻译版阅读总结

原文在这里 ,感谢作者

1、常量命名方法

常量的命名方法 以驼峰法命名,并以相关类名作为前缀
static const NSTimeInterval ZOCSignInViewControllerFadeOutAnimationDuration = 0.4;

static NSString * const ZOCCacheControllerDidClearCacheNotification = @"ZOCCacheControllerDidClearCacheNotification";

常量在头文件中以这样的形式暴露给外部
extern NSString *const ZOCCacheControllerDidClearCacheNotification;

2、类名的规范

(1)类名以是哪个大写字母作为前缀,双字母前缀为Apple的类预留。解决Objective-C没有命名空间所带来的问题。
(2)创建一个子类时,把说明性的部分放在前缀和父类名的中间。如一个 UIViewController 的子类会是 ZOCTimelineViewController

3、Initializer 和 dealloc

(1)推荐将dealloc方法放在实现文件的最前面(直接在 @synthesize 以及 dynamic 之后),
(2)为什么设置 self[super init] 的返回值?
申请分配内存和初始化被分离为两步,allocinit。这个特性叫两步创建

  • alloc负责创建对象,这个过程包括分配足够的内存来保存对象,写入isa指针,初始化引用计数,以及重置所有实例变量。
  • init负责初始化对象,这意味着使对象处于可用状态。通常意味着为对象的实例变量赋予合理有用的值。

alloc方法将返回一个有效的未初始化的对象实例。每一个对这个实例发送的消息会被转换成一次obj_msgSend()函数的调用,形参self的实参是alloc返回的指针,这样self在所有方法的作用域内都能够被访问。

init方法可以通过返回 nil来告诉调用者,初始化失败了。

4、Designated(指定) 和 Secondary (间接)初始化方法

(1)designated 初始化方法是提供所有的参数,每一个类总有且只有一个。Seconary 初始化方法可以是一个或多个,他们仅仅是提供一个或者更多的默认参数来调用designated初始化的初始化方法。

(2)在类继承中调用任何designated初始化方法都是合法的,应该保证所有的 designated initializer 在类继承中是从祖先(通常是NSObject)到你的类向下调用的。
定义一个新类的三种方式:

  • 不需要重载任何的初始化函数
  • 重载 designated initializer
  • 定义一个新的 designated initializer

第一个方案最简单,不需要天剑类的任何初始化逻辑,只需要依照父类的designated initializer

@implementation YOEViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // custom initialization(自定义的初始化过程)
    }
return self;
}

第三种 希望提供自己的初始化函数时,遵循三个步骤来保证获得正确的行为:

  • 定义自己designated initializer,确保调用了直接超类的designated initializer
  • 重载直接超类的 designated initializer。调用新的 designated initializer
  • 为新的designated initializer写文档

正确的实现例子:
- (id)initWithNews:(NSString *)news
{
//call the immediate superclass's designated initializer(调用直接超类的designated initializer)
self = [super initWithNibName:nil bundle:nil];
if (self) {
_news = news;
}
return self;
}

// Override the immediate superclass's designated initializer(重载直接父类的 designated initializer)
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    //Call the new designated initializer
    return  [self initWithNews:@"defaultNews"];
}

注:如果没有重载initWithNibName: bundle:方法,而调用者决定使用这个方法来初始化这个类(这是完全合法的)。initWithNews:永远也不会调用,这就导致了不正确的初始化流程,新类的特定初始化逻辑没有被执行。

可以使用编译器的指令 NS_DESIGNATED_INITIALIZER__attribute__((objc_designated_initializer))来明确的支出那个方法是designated initializer。这样如果新的designated initializer没有调用超类的designated initializer,编译器会发出警告。

写法:

- (id)initWithNews:(NSString *)news __attribute__((objc_designated_initializer));

- (id)initWithNews:(NSString *)news NS_DESIGNATED_INITIALIZER;

通过另一个编译器指令attribute((unavailable("Invoke the designated initializer")))来修饰一个方法,这样会使在试图调用这个方法的时候产生一个编译错误(实际上代码提示都不会有该方法了)。
写法:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil attribute((unavailable("Invoke the designated initializer")));

处理一个例外
如果一个对象遵循NSCoding协议,并且它通过initWithCoder:初始化。此时应该看超类是否符合NSCoding协议来区别对待。符合的时候,如只是调用了[super initWithCoder:],就需要在designated initializer里面写一些通用的初始化代码,处理这种方法的是把这些代码放在私有方法里面。当超类不符合NSCoding协议的时候,推荐把initWithCoder:作为Secondary initializer来对待,并且调用self的designated initializer。

5、单例的写法

+ (instancetype)sharedInstance
{
    static id sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc]init];
    });
    return sharedInstance;
}

6、属性定义

(1)属性定义参数按顺序排列:原子性,读写,内存管理。
@property(nonatomic,readwrite,copy) NSString *name;
(2)属性可以存储一个代码块,必须使用copyblock最早在栈里面创建,使用copyblock拷贝到堆里面去。
(3)声明一个公有的 getter 和一个私有的 setter,应该声明公开的属性为 readonly,并在类的扩展中重新定义属性为readwrite

//.h文件中
@interface YOEClass : NSObject

@property(nonatomic,readonly,strong) NSObject *obj;

@end

//.m文件中
@interface YOEClass ()

@property(nonatomic,readwrite,strong) NSObject *obj;

@end

@implementation YOEClass

- (id)init{
    self = [super init];
    if (self) {
        _obj = @"AA";
    }
    return self;
}

@end

(4)声明一个BOOL属性,setter不应该带is前缀,对应的getter应该带上前缀,So:

@property(assign,getter=isEditable) BOOL editable;

注:在实现文件中应该避免使用@synthesize,编译器已经实现了

7、NSNotification

定义一个NSNotification 的时候,应该把通知的名字作为一个字符串常量

//.h
extern NSString * const ZOCFooDidBecomeBarNotification;
//.m
NSString * const ZOCFooDidBecomeBarNotification = @"ZOCFooDidBecomeBarNotification";

8、利用代码块

代码块如果在闭合的圆括号里,会返回最后语句的值

NSURL *url = ({
    NSString *urlStr = @"http://www.baidu.com";
    [NSURL URLWithString:urlStr];
});

9、self的循环引用

当使用代码块和异步分发的时候,需要避免引用循环。应使用 weak 来引用对象,避免引用循环。

__weak __typeof(self) weakSelf = self;

例子:
单个语句时:

 __weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
    [weakSelf doSomethingWithData:data];
}];

多个语句时:

[self executeBlock:^(NSData *data, NSError *error) {
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [strongSelf doSomethingWithData:data];
        [strongSelf doSomethingWithData:data];
    }
}];

简单记录self在block俩面的三种情况

  • 直接在block里面使用关键字 self。只能在 block 不是作为一个property 的时候使用,否则会导致 retain cycle。
  • 在 block 外定义一个 __weak 的引用到self,并且在 block 里面使用这个弱引用。 当 block 被声明一个 property 的时候使用。
  • 在block 外定义一个 __weak 的引用到 self,并且在 block 内部通过这个弱引用定义一个 __strong 的引用。 和并发执行有关。当涉及异步的服务的时候,block 可以在之后被执行,并且不会发生关于self是否存在的问题。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容