《Effective Objective-C 2.0》读书笔记---第一章

第一条:了解Objective-C语言的起源


Objective-C语言使用“消息结构”(messaging structure)而非“函数调用”(function calling).Objective-C语言由Smalltalk演化而来。

使用消息结构的语言,其运行时所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。

Objective-C的重要工作都由“运行期组件”(runtime component)而非编译器来完成。使用Objective-C的面向对象特性所需的全部数据结构及函数都在运行期组件里面。举例来说,运行期组件中含有全部内存管理方法。运行期组件本质上就是一种与开发者所编写代码相链接的“动态库”(dynamic library),其代码能把开发者编写的所有程序粘合起来。这样的话,只需要更新运行期组件,即可提升应用程序性能。而那种许多工作都在“编译器”(compile time)完成的语言,若想获得类似的性能提升,则要重新编译应用程序代码。


扩展知识:

之前看苹果的runtime源码,一直有个疑问,就是Class信息是在什么时候初始化的,首先我们来看看main函数之前发生了什么

通过如下命令查看APP所依赖的dylib信息

otool -L APP文件

注意APP文件是你的APP可执行文件的目录,而不是.app文件,.app文件展开就能找到

执行结果如下:

/Users/wanglijun/Library/Developer/Xcode/DerivedData/HealthKitTest-fcazmzejspvgeudcrmbtkzepvkbl/Build/Products/Debug-iphoneos/HealthKitTest.app/HealthKitTest:

/System/Library/Frameworks/HealthKit.framework/HealthKit (compatibility version 1.0.0, current version 1.0.0)

/System/Library/Frameworks/Foundation.framework/Foundation (compatibility version 300.0.0, current version 1349.1.0)

/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)

/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (compatibility version 150.0.0, current version 1348.0.0)

/System/Library/Frameworks/UIKit.framework/UIKit (compatibility version 1.0.0, current version 3600.5.2)


可以看到除了APP里面引入的CoreFoundation.frameworkUIKit.frameworkHealthKit.frameworkFoundation.framework框架以外,还有libobjc.A.dyliblibSystem.B.dylib两个动态库。


我们继续执行以下命令查看这两个动态库还有什么依赖:

otool -L /usr/lib/libobjc.A.dylib

结果如下:

/usr/lib/libobjc.A.dylib:

/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

/usr/lib/libauto.dylib (compatibility version 1.0.0, current version 1.0.0)

/usr/lib/libc++abi.dylib (compatibility version 1.0.0, current version 307.2.0)

/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.4.0)

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)


otool -L /usr/lib/libSystem.B.dylib

结果如下:

/usr/lib/libSystem.B.dylib:

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)

/usr/lib/system/libcache.dylib (compatibility version 1.0.0, current version 79.0.0)

/usr/lib/system/libcommonCrypto.dylib (compatibility version 1.0.0, current version 60092.20.1)

/usr/lib/system/libcompiler_rt.dylib (compatibility version 1.0.0, current version 62.0.0)

/usr/lib/system/libcopyfile.dylib (compatibility version 1.0.0, current version 1.0.0)

/usr/lib/system/libcorecrypto.dylib (compatibility version 1.0.0, current version 442.20.2)

/usr/lib/system/libdispatch.dylib (compatibility version 1.0.0, current version 703.20.1)

/usr/lib/system/libdyld.dylib (compatibility version 1.0.0, current version 421.2.0)

/usr/lib/system/libkeymgr.dylib (compatibility version 1.0.0, current version 28.0.0)

/usr/lib/system/liblaunch.dylib (compatibility version 1.0.0, current version 972.20.3)

/usr/lib/system/libmacho.dylib (compatibility version 1.0.0, current version 894.0.0)

/usr/lib/system/libquarantine.dylib (compatibility version 1.0.0, current version 85.0.0)

/usr/lib/system/libremovefile.dylib (compatibility version 1.0.0, current version 45.0.0)

/usr/lib/system/libsystem_asl.dylib (compatibility version 1.0.0, current version 349.1.1)

/usr/lib/system/libsystem_blocks.dylib (compatibility version 1.0.0, current version 67.0.0)

/usr/lib/system/libsystem_c.dylib (compatibility version 1.0.0, current version 1158.20.4)

/usr/lib/system/libsystem_configuration.dylib (compatibility version 1.0.0, current version 888.20.5)

/usr/lib/system/libsystem_coreservices.dylib (compatibility version 1.0.0, current version 41.2.0)

/usr/lib/system/libsystem_coretls.dylib (compatibility version 1.0.0, current version 121.1.1)

/usr/lib/system/libsystem_dnssd.dylib (compatibility version 1.0.0, current version 765.20.4)

/usr/lib/system/libsystem_info.dylib (compatibility version 1.0.0, current version 503.0.0)

/usr/lib/system/libsystem_kernel.dylib (compatibility version 1.0.0, current version 3789.21.3)

/usr/lib/system/libsystem_m.dylib (compatibility version 1.0.0, current version 3121.4.0)

/usr/lib/system/libsystem_malloc.dylib (compatibility version 1.0.0, current version 116.0.0)

/usr/lib/system/libsystem_network.dylib (compatibility version 1.0.0, current version 1.0.0)

/usr/lib/system/libsystem_networkextension.dylib (compatibility version 1.0.0, current version 1.0.0)

/usr/lib/system/libsystem_notify.dylib (compatibility version 1.0.0, current version 165.20.1)

/usr/lib/system/libsystem_platform.dylib (compatibility version 1.0.0, current version 126.1.2)

/usr/lib/system/libsystem_pthread.dylib (compatibility version 1.0.0, current version 218.20.1)

/usr/lib/system/libsystem_sandbox.dylib (compatibility version 1.0.0, current version 592.21.2)

/usr/lib/system/libsystem_secinit.dylib (compatibility version 1.0.0, current version 24.0.0)

/usr/lib/system/libsystem_symptoms.dylib (compatibility version 1.0.0, current version 1.0.0)

/usr/lib/system/libsystem_trace.dylib (compatibility version 1.0.0, current version 518.20.8)

/usr/lib/system/libunwind.dylib (compatibility version 1.0.0, current version 35.3.0)

/usr/lib/system/libxpc.dylib (compatibility version 1.0.0, current version 972.20.3)


我们可以在+ (void)load;方法处打个断点,会发现,每个类的这个类方法都会在main函数调用之前被调用,而且仅仅调用一次,不管有没有被import

Objective-C动态运行库会自动注册我们代码中定义的所有的类。我们也可以在运行时创建类定义并使用objc_addClass函数来注册它们。

也就是说类的信息实际上是在main函数调用之前就已经加载到内存中。而我们在创建对象的时候就是开辟了一块内存,然后将isa指针指向先前已加载好的class。


第二条:在类的头文件中尽量少引用其他头文件


尽量使用@class在头文件中引入类,而在类的实现文件引入所使用类的头文件,编译时不需要知道类的全部细节,只需要知道有这么一个类就够了。这叫做“向前声明”(forward declaring)

那些不需要接口使用者知道的数据,以及该类遵循的协议声明都放到类扩展中,

向前声明可以解决两个类相互引用的问题(在依赖关系过于复杂的时候),还可以减少编译时间,降低类之间的耦合。

尽量把协议单独放在一个文件中(委托协议例外),以免引入协议的时候引入了不必要的类。


第三条 多用字面量语法,少用与之等价的方法


//    NSString
    NSString *string = @"string";
    
//    NSNumber
    NSNumber *intNumber = @1;
    NSNumber *floatNumber = @2.5f;
    NSNumber *doubleNumber = @3.1415926;
    NSNumber *boolNumber = @YES;
    NSNumber *charNumber = @'a';
    
//    NSArray
    NSArray *animals = @[@"cat",@"dog",@"mouse",@"badger"];
    NSString *dog = animals[1];
//    备注:数组中不能有nil,否则会抛出异常,因为字面量语法实际上只是一种“语法糖”,其效果等于是先创建一个数组,然后把方括号内的所有对象都加到这个数组中。向数组中添加nil对象是会崩溃的,所以,我们在用字面量创建数组的时候,要确保里面的对象都不为nil。
    
//    NSDictionary
    NSDictionary *personData = @{@"firstName":@"Matt",
                                 @"lastName":@"Galloway",
                                 @"age":@28};
    NSString *lastName = personData[@"lastName"];
//    备注:和数组一样,如果遇到nil对象会抛出异常
    
//    如果数组和字典是可变的,那么可以通过下表来修改内容
     mAnimals[0] = @"tiger";
     mPersonData[@"age"] = @30;
    
//    说明:使用字面量创建的对象都是不可变的


第四条 多用类型常量,少用#define预处理指令


预处理如下:

#define ANIMATION_DURATION 0.3


缺点:没有类型信息,作用域内所有该字符都会被替换,而且易被人修改或覆盖掉。


解决方法:

static const NSTimeInterval kAnimationDuration = 0.3;


若常量局限于某“编译单元”(translation unit,也就是”实现文件“,implementation file)之内,则在前面加字母k


备注:由于Objective-C没有“名称可见”(namespace)这一概念,引入该静态变量所在头文件的文件都可以使用它,你如果把它放在头文件中,就相当于声明了一个全局变量,因此这个静态变量最好放在实现文件中,这样就实现了对外不公开。


实际上,如果一个变量既声明为static,又声明为const,那么编译器根本不会创建符号,而是会像#define预处理指令一样,把所有遇到的变量都替换成常量值,不过仍然要记住:用这种方式定义的常量带有类型信息。


若常量对外可见,则通常以类名为前缀,以避免名称冲突,因为这种常量要出现在全局符号表中,如:

//In the header file
extern NSString *const EOCStringConstant;
//In the implementation file
NSString *const EOCStringConstant = @"VALUE";

//常量定义应从右至左解读,EOCStringConstant是一个常量,这个常量是指针,指向NSString对象,这样别人就无法将指针指向别的字符串了


第五条 用枚举表示状态,选项,状态码


Foundation框架定义了一些辅助的宏,用这些宏来定义枚举类型时,也可以指定用于保存枚举值的低层数据类型。用法如下:

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
    SDWebImageRetryFailed = 1 << 0,
    SDWebImageLowPriority = 1 << 1,
    SDWebImageCacheMemoryOnly = 1 << 2,
    SDWebImageProgressiveDownload = 1 << 3,
    SDWebImageRefreshCached = 1 << 4,
};

这些宏的定义如下:

#if (__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))
#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type
#if (__cplusplus)
#define NS_OPTIONS(_type, _name) _type _name; enum : _type
#else
#define NS_OPTIONS(_type, _name) enum _name : _type _name; enum _name : _type
#endif
#else
#define NS_ENUM(_type, _name) _type _name; enum
#define NS_OPTIONS(_type, _name) _type _name; enum
#endif

凡是需要以按位或操作来组合的枚举都应该使用NS_OPTIONS定义。若是枚举不需要相互组合,则应使用NS_ENUM来定义。

原因如下:

在用或运算操作两个枚举时,C++认为运算结果的数据类型应该是枚举的底层数据类型,也就是NSUInteger,而C++不允许将这个底层类型隐式转换”(implicit cast)为枚举类型本身。


最后说到处理枚举类型的switch语句中不要实现default分支,这样加入新枚举之后,编译器就会提示开发者:switch语句并未处理所有枚举。

个人觉得这个得看情况而定了。



你可能感兴趣的:(IOS开发相关)