app程序的入口,同样是main函数
main函数为 int main(int argc, char * argv[]),C系语言大多都是这个样子,argc是命令行总的参数个数,argv是参数的数组,值得一提的是argv中第一个参数为app的路径+全名。
然后就是main中的代码
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
autoreleasepool自动释放池,没什么可说,从返回的UIApplicationMain开始分析。
UIApplicationMain的声明为
UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
首先说说UIKIT_EXTERN,
#ifdef __cplusplus
#define UIKIT_EXTERN extern "C" __attribute__((visibility ("default")))
#else
#define UIKIT_EXTERN extern __attribute__((visibility ("default")))
#endif
宏定义 #ifdef __cplusplus,如果宏定义了__cplusplus,则#define UIKIT_EXTERN extern "C" __attribute__((visibility ("default"))),否则
#define UIKIT_EXTERN extern __attribute__((visibility ("default")))
区分在是否定义了__cplusplus,__cplusplus很直观的翻译cpp,也就是C++。__cplusplus标示符用来判断程序是用c还是c++编译程序编译的。当编译c++程序时,这个标示符会被定义,编译c程序时,不会定义。接着对全句理解,如果已经宏定义了__cplusplus(也就是说当前源代码被当作C++源代码处理。否则当前源代码被当中C源代码处理),那么extern "C" __attribute__((visibility ("default")))。
extern "C"很好理解,在C++发明之初,为了兼容在当时正处主流的C语言,按照C编译方式进行编译的作用。可以理解为extern "C"就是告诉编译器(也就是Xcode)在编译的时候,要按照原来C语言的编译方式对(全局)函数和变量进行编译。
C++是一种“不完全的面相对象语言”,对比C/C++ 两种编译方式,C++支持重载,从而使得函数的编译方式不得不同于C的编译,举个栗子,有个函数,更新学生信息的void upDataStudentInfo(int, int);C方式去编译此函数,不会对函数名进行特殊处理,编译后的函数名为_upDataStudentInfo,反观C++方式的编译,为了支持重载,upDataStudentInfo函数会变成类似_upDataStudentInfo_int_int的函数名,同样void upDataStudentInfo(float, int)会编译成_upDataStudentInfo_float_int类似的函数名。这些都可在.obj文件中查看。此处对于C/C++混编互调的深层学习以及使用,不做分析,我只是个搞iOS的,在编译后寻找函数名等多少也能说出些来,但实在不算熟悉,就不误人子弟了。
接下来就是__attribute__((visibility ("default"))),同出于C系语言,__attribute__是用来设置属性的,包括函数、变量、类型,这里我们使用的是设置函数的属性,__attribute__听传闻说是自测利器,同样在C中,或者C++中,作为入门级的iOSer,理解就可。visibility属性是设置将本项目的函数作为库使用时的可见性。
设置了__attribute__((visibility ("default"))),函数的public属性对外可见。
so,总结成一句话,UIKIT_EXTERN就是将函数修饰为兼容以往C编译方式的、具有extern属性(文件外可见性)、public修饰的方法或变量库外仍可见的属性。
继续分析int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
前面两个参数出于main,从第三个开始NSString * __nullable principalClassName,一个字符串类型的参数principalClassName,直译为主要类,必须为UIApplication或者其子类,代表着当前app自身。并且如果此参数为nil的话,则默认为@"UIApplication"。
第四个参数delegateClassName,代理类。在UIApplication中有个delegate的变量,delegate遵守UIApplicationDelegate协议负责程序的生命周期。UIApplication 接收到所有的系统事件和生命周期事件时,都会把事件传递给UIApplicationDelegate进行处理,至于为什么没让UIApplication自己去实现,涉及到了上帝类、框架类,过深,不讲。
综合来说UIApplicationMain主要负责三件事
1、从给定的类名初始化应用程序对象,也就是初始化UIApplication或者子类对象的一个实例,如果你在这里给定的是nil,那么 系统会默认UIApplication类,也就主要是这个类来控制以及协调应用程序的运行。在后续的工作中,你可以用静态方法sharedApplication 来获取应用程序的句柄。
2、从给定的应用程序委托类,初始化一个应用程序委托。并把该委托设置为应用程序的委托,这里就有如果传入参数为nil,会调用函数访问 Info.plist文件来寻找主nib文件,获取应用程序委托。
3、启动主事件循环,并开始接收事件。
end
PS 1:再说说__nullable和__nonnull。在swift中,可以使用!和?来表示一个对象是optional的还是non-optional,如view?和view!。而在Objective-C中则没有这一区分,view即可表示这个对象是optional,也可表示是non-optioanl。当Swift与OC混编的时候,Swift编译器并不知道一个Objective-C对象到底是optional还是non-optional,因此这种情况下编译器会隐式地将Objective-C的对象当成是non-optional。为了解决这个问题,苹果在Xcode 6.3引入了一个Objective-C的新特性:nullability annotations。这一新特性的核心是两个新的类型注释:__nullable和__nonnull。从字面上我们可以猜到,__nullable表示对象可以是NULL或nil,而__nonnull表示对象不应该为空。当我们不遵循这一规则时,编译器就会给出警告。
在任何可以使用const关键字的地方都可以使用__nullable和__nonnull,不过这两个关键字仅限于使用在指针类型上。而在方法的声明中,我们还可以使用不带下划线的nullable和nonnull。
在属性声明中可以这样使用:@property (nonatomic, copy, nonnull) NSString * name;
也可以这样使用:@property (nonatomic, copy) NSString * __nonnull name;
NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull。nullable的可以在两个宏之间的代码中单独指定。
PS 2:NSStringFromClass
正常来说,
id my = [[NSClassFromString(@"Wohenshuai") alloc] init];
和
id my = [[Wohenshuai alloc] init];
是一样的。但是,如果你的程序中并不存在Wohenshuai这个类,下面的写法会出错,而上面的写法只是返回一个空对象而已。
因此,在某些情况下,可以使用NSClassFromString来进行你不确定的类的初始化。
NSClassFromString的好处是:
1 弱化连接,因此并不会把没有的Framework也link到程序中。
2 不需要使用import,因为类是动态加载的,只要存在就可以加载。因此如果你的toolchain中没有某个类的头文件定义,而你确信这个类是可以用的,那么也可以用这种方法。
类似的函数如下:
NSClassFromString
NSGetSizeAndAlignment
NSLog
NSLogv
NSSelectorFromString
NSStringFromClass
NSStringFromSelector