• ObjectiveC一瞥


    *本文为原创, 转载请注明出处 www.cnblogs.com/hucn

    学习Objective-C有段时间了, 写些心得和大家分享讨论. 我的"母语"是C++, 所以会通过和C++比较来加深理解. 文章主要介绍Objective-C的基础知识, 适合Objective-C新手, 熟悉Objective-C的朋友可以不看了, 或是帮忙挑挑错~

    1. C和Objective-C

    Objective-C是C的超集, 即支持所有C的特性, 包括符号重定义typedef, 类型转换, 宏. 增加了面向对象, 异常处理, Block, Catgories等功能, 因此熟悉C的朋友学习Objective-C也会很容易.

    2. OO特性

    Objective-C是一门面向对象的语言, 具有纯正的OO的设计思想. 支持单一继承, 数据封装@private和@protected, 支持运行时多态.

    和C++相比, Objective-C在语言层面引导我们使用OOP设计. 用@interfaced定义类类型, @protocol定义接口, 将实现继承和接口继承分开, 支持一个实现继承和多个接口继承.

    细节

    在Cocoa下Objective-C的对象都继承与NSObject(NS是NeXTSTEP的缩写), 这个类提供了基本的OO的支持. 包括运行时的动态类型, 判断对象是否支持某接口, 判断是否实现了某函数.

    Cocoa采用依赖倒置的设计, 使得我们必须采用面向对象的思想进行设计. 这和C++或其他一些语言的开发不大一样, 在C++中, 我们来控制程序的主要逻辑, 我们使用库是为了实现某个功能. 一个好的设计师, 可能会把具体的实现抽离出一层, 在抽象的接口中处理主要的逻辑, 在具体的实现中对抽象进行具现化处理, 这样使多个实现低耦合. 这其实就是DIP(Dependency-Inversion Principles)的思想. 而Cocoa库把这些都做好了, 我们使用Cocoa库时不关心程序的主要逻辑, 只是在处理某个具体的功能. 和以前的思路刚好相反, 具体见库和设计支持部分.

    3. 内存管理

    对与每个C++程序员, 内存管理是让头疼的事情, 因此在STL和Boost中都提供了智能指针帮助内存管理. Java则通过JVM提供内存的检查和自动回收, 我们不需要也不能手动释放内存. Objective-C则做出了折中的方案, 提供三个内存管理方式:

    1. 每个Objective-C对象内置引用计数管理, 当引用计数为0时自动释放;

    2. 提供以一次函数调用为生存期的系统管理的对象, 在函数中申请, 函数出栈时释放. 和C++中用局部对象的构造和析构控制资源很像.

    3. 提供系统管理的内存池NSAutoRealeasePool, 可以通过创建和释放内存池控制内存申请和回收的时机. 

    细节:

    所有NSObject的子类都可以使用内置的引用计数. 通过retain增加引用计数, release减小计数, 当引用计数为0时会自动调用realloc函数进行析构.

    通常引用计数和Objective-C的存取器功能配合使用, 存取器和Java的setter和getter功能很像, 只是额外提供了对线程安全和引用计数的支持. 存取器通过retain assign copy控制引用计数. retain即引用计数加一, 即对某对象强引用; assign不影响对象的引用计数, 对其弱引用; copy是将对象浅拷贝一份, 使其具有独立的生存期.

    此外, 容器也会影响对象的生存期, 在容器中总结. XCode中提供的引用分析工具Analyze和内存泄漏检查工具, 更提供了强大的编译期和运行期的内存检测, 在工具中总结.

    Tips:

    1 通过Objective-C控制内存比较容易, 首先需要根据期望对象的生存期选择合适的管理方式.

    第1种方式是通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了.

    第2种方式一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期.

    第3种方式是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.

    2 和C++的smart pointer——shared_ptr<>一样, 多个对象不能循环强引用, 否则会泄漏. 比如, UIViewController对象强引用UIView, @property UIView *view(retain); 则UIView中若要引用UIViewController需要弱引用, 即@property UIViewController *controller(assign);

     
    4. 动态特性

    Objective-C是一个动态语言, 具有在三个层次的动态支持.

    1 动态类型 dynamic typing

    id类型是Objective-C提出的类型, 是一个指向Object的指针, 编译器对所有id类型的对象不会进行编译期型别检查, 这些工作可能会延迟到运行时我们自己做, 或者不做.

    id是强大的工具, 举例来说一个NSArray, 一个Objective-C的顺序容器, 可以用这个特性存储不同型别的对象, 他们的类型都是id. dynamic typing是后面两个动态特性的基础.

    2 动态绑定 dynamic binding

    C++的多态是由虚函数表vptr实现的, 简单说来虚函数表就是一个间接层, 将虚函数的具体实现和对象的类型剥离. 运行时根据虚函数表而不是变量的类型去执行函数, 这其实就是动态绑定的概念.

    和C++相比, Objective-C显得更为强大. C++的虚函数表在编译期就固定了, 不能在运行时修改, 但是在Objective-C中, 你可以!因此你可以在运行时替换函数的实现, 补充现有的实现, 甚至创建新的类成员和方法!!!

    细节:

    NSObject中维护一个叫做指针isa, 指向一个Class类型的结构体,  结构体中维护了父类的Class结构体指针以及成员函数指针表dispatch table, 其中每个表项存储了SEL和IMP的对应关系. 简单说, SEL就是一个成员函数, IMP就是一个C的函数实现. 每次调用一个Object的成员函数SEL时, 会索引到这个isa, 然后查询dispatch table对应的表项, 找到对应的实现. 并且dispatch table可以在运行时查询, 修改, 添加. 这样我们就可以通过维护类的dispatch table动态查询, 修改对象的表现. 比如, 通过responsToSelector:(SEL)selector, 可以查询到table中SEL函数对于的IMP是否有效.

     

    3 动态加载 dynamic loading

    动态加载是同过Bundles实现的, XCode将程序中需要的Code, Nib, picture等资源打包成bundles. 由系统控制资源的加载, 在内存受限的移动设备中, 动态加载是很强大的功能.我们还可以通过NSBundle在运行期控制资源加载.

    5. 容器

    Cocoa提供几种主要的容器:

    顺序容器 NSArray

    集合容器 NSSet

    字符串 NSString

    Map NSDictionary

    每种都提供了同STL一样强大的操作. 另外上面的所有容器都是初始化后不能修改的, 每种有对应的可修改版本, 如NSMutableArray. 可见Cocoa对只读的容器和可写容器进行了不同的优化策略.

    6. 库与设计支持

    1 MVC和UIKit

    MVC即Model View Control, Model代表数据, View代表显示, Control代表Model和View之间的控制. MVC是一种复合的设计模式, 将数据、显示和逻辑解耦, 为了方便的修改数据而不影响显示部分, 或是修改显示而不需要调整数据.

    Cocoa的UIKit是iOS的界面设计库, UIKit是按照MVC模式设计的, 它使得iOS的UI开发快捷并灵活. View对应UIVIew, Control对应UIViewController.

    UIView负责屏幕上一个矩形区域的显示和用户交互, 通过继承UIView可以定制具体的渲染行为和交互行为.

    UIViewController负责组织一组UIView, 响应Model的变化, 处理设备方向响应, 组织特殊的UIView, 如UITabBarController组织屏幕下方的一组Tab Bar, UINavigationController组织屏幕上方的导航栏.

    细节

    我们在界面上看到的多数元素都是UIView的特化, 比如按钮UIButton、标签UILabel、输入框UITextView都UIView的子类. 它们由一个树来组织, 每个UIView实例有一个super view, UIView *superView, 和若干subView NSSet *subViews. 每个UIView有一个tag, 默认为0, 通过tag可以方便对UIView访问.

    这样一棵树由一个UIViewController实例所管理, UIViewController的view成员指向树的根节点. ViewController除了管理这些View还处于responder chain中, 若所有的UIView都没有处理一个交互, UIViewController可以处理, 否则交给view的super view处理. 这个super view也会在另一颗树中, 并由另一个UIViewController实例管理着.

     

    2 delegation、依赖倒置和target-action mechanism

    UIKit中到处都是delegation的影子, 每个UIView或是UIViewController或UIApplicationDelegate都处理大量的delegation. 这样带来的好处是我们不用关心应用程序是怎么执行起来的, 只要处理我们感兴趣的delegation就好了. 比如我们想做一个触碰屏幕的相应, 就在UIView的具现化中实现touchBegan:这样一个函数就好了, 或者我们想在某个View出现的时候初始化些东西, 只需在UIView中实现ViewDidLoad就好了. 其他的事情都交给UIKit处理, 这也是为什么main函数如此简单.

    这样我们就"被迫"用依赖倒置原则设计程序, 因为抽象的逻辑已经写好了, 我们就根据他们写写细节吧.

    target-action mechanism是UIKit处理事件Event的方法. 做过UI程序的人都知道, UI最烦的就是处理各种事件了, 事件多起来逻辑可能很复杂. 这个机制尽可能的减少事件的触发和处理的耦合关系.

    通过对一个view调用-(void)addTarget:(id)target: (SEL)action: forControlEvents:(UIControlEvents)controlEvents; 函数设定当这个View的某个cotrolEvent发生时调用target的action函数. 本质上还是delegation, 但是这里的方便之处在于可以多次调用函数为一个Event添加多个响应, 也可以通过将target参数设为nil将事件传递到responder chain上. 这样我们可以在运行时设定事件的处理方法, 而不需改变处理方的实现.

    3 观察者模式NSNotificationCenter

    在Cocoa中提供了观察者模式的支持, 用NSNotificationCenter的全局实例, 可以方便的添加观察者和对观察者发出Notification.

    主要的函数有,

    // 返回一个NSNotificationCenter全局实例, 类似于单件Singleton.

    + (id)defaultCenter;

    // 为anObject添加一个观察者observer, observer的aSelector用于接收Notification, Notification的名字是aName.

    - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject;

    // 发送所有anObject的以aName为名的Notification

    - (void)postNotification:(NSString *)aName object:(id)anObject;

    Tips:

    Notification与delegation

    两者都是解耦事件的触发和响应的, delegation更适用于一个触发一个响应的情况, 并且响应函数的参数为发出delegation的对象, 无法区分是这个对象的哪个Event. 而Notifaction则更是用一个触发响应多个响应情况, 当NotificationTable庞大时可能会影响效率.

     
    7. 安全性

    C++中对空指针是很危险的, 对空指针进行寻址会抛出异常, 若不进行处理会导致程序崩溃. 因此会提倡RAII.

    而Objective-C中对null的寻址可以正常进行, 只是什么都没做. 这样很大程度上减少了这类异常, 增强了程序的健壮性, 但也为调试程序增加了一些困难.

     
    8. 工具

    XCode集成了很多优秀的编译器, 调试器, 以及分析工具. 这里挑我熟悉的几点说明.

    XCode有方便的编辑提示, 快速的代码补全.

    XCode在代码编辑期就对代码进行实时编译并给出提示, 越早的发现错误, 修改的代价就越低.

    XCode提供可视化界面编辑工具, IB Interface Builder

    XCode提供强大的Analyze功能, 方便的进行引用计数分析, 指出可能的内存泄漏或是提前释放, 不仅能够找到可能的位置, 还能找到泄漏或释放的原因, 并用可视化界面帮助程序要分析.

    XCode提供NSZombie和Instruments方便的追宗、分析并解决程序崩溃的难题.

    9. 其他特性

    和C++相比Objective有一些其他特性, 包括支持的和不支持的:

    不支持运算符重载

    不支持模板

    支持Categories 我理解Categories就是开放了类的定义, 可以方便的对已有类进行扩展. 就像C++中namespace是开放的, 可扩展的一样.

    Block Block就像是C++0x中的lambda表达式功能, 可以定义匿名函数作为简单调用或参数传递. 和lambda不同的是, Block可以操作local变量, 这使得Block的用途更为广泛.

  • 相关阅读:
    Github markdown页面内跳转
    github gist 无法访问
    Install pyaudio on Ubuntu
    删除链表的倒数第N个节点
    电话号码的字母组合
    最长公共前缀
    盛最多水的容器
    字符串转化整数与回文数
    Z 字形变换
    LeetCode1-5题
  • 原文地址:https://www.cnblogs.com/hucn/p/2283646.html
Copyright © 2020-2023  润新知