第一条:了解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.framework,UIKit.framework,HealthKit.framework和Foundation.framework框架以外,还有libobjc.A.dylib,libSystem.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语句并未处理所有枚举。
个人觉得这个得看情况而定了。