刚刚在线

分享iOS开发技术经验的自媒体网站

关于NSRunLoop和NSTimer的深入理解

一、什么是NSRunLoop

NSRunLoop是消息机制的处理模式

NSRunLoop的作用在于有事情做的时候使的当前NSRunLoop的线程工作,没有事情做让当前NSRunLoop的线程休眠

NSTimer默认添加到当前NSRunLoop中,也可以手动制定添加到自己新建的NSRunLoop

NSRunLoop就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)同步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。

在单线程的app中,不需要注意Run Loop,但不代表没有。程序启动时,系统已经在主线程中加入了Run Loop。它保证了我们的主线程在运行起来后,就处于一种“等待”的状态(而不像一些命令行程序一样运行一次就结束了),这个时候如果有接收到的事件(Timer的定时到了或是其他线程的消息),就会执行任务,否则就处于休眠状态。

runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers

模式包括:

  • default模式:几乎包括所有输入源(除NSConnection) NSDefaultRunLoopMode模式
  • mode模式:处理modal panels
  • connection模式:处理NSConnection事件,属于系统内部,用户基本不用
  • event tracking模式:如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件
  • common modes模式:NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。

iOS 9 App Thinning

App Thinning 是苹果在去年 WWDC, 也就是 iOS 9 中推出的为 App 瘦身的一个新的技术。 App Thinning 主要分为 3 个部分:

  • App Slicing
  • Bitcode
  • On Demand Resources

App Slicing

我们正常的应用中都会用到图片资源,为了适配 iPhone 6 Plus 以及 iPhone 6, 图片资源需要包含 @2x 的图片 和 @3x 的图片,而 Slicing 就是在这一步进行优化,Slicing 会根据用户在 App Store 下载的不同设备选择对应的资源。例如:当用户使用 iPhone 6 在 App Store 上下载 App 的时候,App Store 只会给予他 @2x 的图片,而不会像以前一样,不管三七二十一我把所有的资源都给你,根据苹果的描述,这大约能节约你 30% 到 50% 的大小。而你要做的事情,仅仅是使用 Xcode 7 IDE, 并且使用其中的 Assert Catalog 管理你的资源文件。

不过 Slicing 因为 iCloud 备份的问题, 知道 iOS 9.0.2 以及 Xcode 7.0.1 才可以被使用

Bitcode

Bitcode 是苹果在你 App 被下载之前做的另外一件优化。Bitcode 是你的代码编译为最终可执行文件的中间的一层,当你的 App 以 Bitcode 的形式提交到 iTunes Connect 的时候,iTunes Connect 会自动为合适的机器编译出对应的代码,例如:对于 64 位机器的 iPhone 6 或者 iPad Air 2,Bitcode 会为其编译出适合 64 位机器可执行文件。

对于 iOS app 来说, Bitcode 不是必须的,但 Bitcode 编译选项为 Yes(这里应该只是一个过渡期,有些第三方库如果不在继续维护了需要尽快换掉), 对于 tvOS 和 watchOS 的 app,Bitcode 的的选项必须为 Yes。

iOS视频边下边播–缓存播放数据流

google搜索“iOS视频变下边播”,有好几篇博客写到了实现方法,其实只有一篇,其他都是copy的,不过他们都是使用的本地代理服务器的方式,原理很简单,但是缺点也很明显,需要自己写一个本地代理服务器或者使用第三方库httpSever。如果使用httpSever作为本地代理服务器,如果只缓存一个视频是没有问题的,如果缓存多个视频互相切换,本地代理服务器提供的数据很不稳定,crash概率非常大。

这里我采用ios7以后系统自带的方法实现视频边下边播,这里的边下边播不是单独开一个子线程去下载,而是把视频播放的数据给保存到本地。简而言之,就是使用一遍的流量,既播放了视频,也保存了视频。

用到的框架:<AVFoundation/AVFoundation.h>
用到的播放器:AVplayer

先说一下avplayer自身的播放原理,当我们给播放器设置好url等一些参数后,播放器就会向url所在的服务器发送请求(请求参数有两个值,一个是offset偏移量,另一个是length长度,其实就相当于NSRange一样),服务器就根据range参数给播放器返回数据。这就是大致的原理,当然实际的过程还是略微比较复杂。

下面进入主题

产品需求:

  1. 支持正常播放器的一切功能,包括暂停、播放和拖拽

  2. 如果视频加载完成且完整,将视频文件保存到本地cache,下一次播放本地cache中的视频,不再请求网络数据

  3. 如果视频没有加载完(半路关闭或者拖拽)就不用保存到本地cache

实现方案:

  1. 需要在视频播放器和服务器之间添加一层类似代理的机制,视频播放器不再直接访问服务器,而是访问代理对象,代理对象去访问服务器获得数据,之后返回给视频播放器,同时代理对象根据一定的策略缓存数据。

  2. AVURLAsset中的resourceLoader可以实现这个机制,resourceLoader的delegate就是上述的代理对象。

  3. 视频播放器在开始播放之前首先检测是本地cache中是否有此视频,如果没有才通过代理获得数据,如果有,则直接播放本地cache中的视频即可。

视频播放器需要实现的功能

  1. 有开始暂停按钮

  2. 显示播放进度及总时长

  3. 可以通过拖拽从任意位置开始播放视频

  4. 视频加载中的过程和加载失败需要有相应的提示

代理对象需要实现的功能

  1. 接收视频播放器的请求,并根据请求的range向服务器请求本地没有获得的数据

  2. 缓存向服务器请求回的数据到本地

  3. 如果向服务器的请求出现错误,需要通知给视频播放器,以便视频播放器对用户进行提示

具体流程图

1

视频播放器处理流程

  1. 当开始播放视频时,通过视频url判断本地cache中是否已经缓存当前视频,如果有,则直接播放本地cache中视频

  2. 如果本地cache中没有视频,则视频播放器向代理请求数据

  3. 加载视频时展示正在加载的提示(菊花转)

  4. 如果可以正常播放视频,则去掉加载提示,播放视频,如果加载失败,去掉加载提示并显示失败提示

  5. 在播放过程中如果由于网络过慢或拖拽原因导致没有播放数据时,要展示加载提示,跳转到第4步

代理对象处理流程

  1. 当视频播放器向代理请求dataRequest时,判断代理是否已经向服务器发起了请求,如果没有,则发起下载整个视频文件的请求

  2. 如果代理已经和服务器建立链接,则判断当前的dataRequest请求的offset是否大于当前已经缓存的文件的offset,如果大于则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向后拖拽,并且超过了已缓存的数据时才会出现)

  3. 如果当前的dataRequest请求的offset小于已经缓存的文件的offset,同时大于代理向服务器请求的range的offset,说明有一部分已经缓存的数据可以传给播放器,则将这部分数据返回给播放器(此时应该是由于播放器向前拖拽,请求的数据已经缓存过才会出现)

  4. 如果当前的dataRequest请求的offset小于代理向服务器请求的range的offset,则取消当前与服务器的请求,并从offset开始到文件尾向服务器发起请求(此时应该是由于播放器向前拖拽,并且超过了已缓存的数据时才会出现)

  5. 只要代理重新向服务器发起请求,就会导致缓存的数据不连续,则加载结束后不用将缓存的数据放入本地cache

  6. 如果代理和服务器的链接超时,重试一次,如果还是错误则通知播放器网络错误

  7. 如果服务器返回其他错误,则代理通知播放器网络错误

resourceLoader的难点处理

- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
    [self.pendingRequests addObject:loadingRequest];
    [self dealWithLoadingRequest:loadingRequest];

    return YES;
}

播放器发出的数据请求从这里开始,我们保存从这里发出的所有请求存放到数组,自己来处理这些请求,当一个请求完成后,对请求发出finishLoading消息,并从数组中移除。正常状态下,当播放器发出下一个请求的时候,会把上一个请求给finish。

下面这个方法发出的请求说明播放器自己关闭了这个请求,我们不需要再对这个请求进行处理,系统每次结束一个旧的请求,便必然会发出一个或多个新的请求,除了播放器已经获得整个视频完整的数据,这时候就不会再发起请求。

- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
{
    [self.pendingRequests removeObject:loadingRequest];

}

下面这个方法是对播放器发出的请求进行填充数据

- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest
{
    long long startOffset = dataRequest.requestedOffset;

    if (dataRequest.currentOffset != 0) {
        startOffset = dataRequest.currentOffset;
    }

    if ((self.task.offset +self.task.downLoadingOffset) < startOffset)
    {
        //NSLog(@"NO DATA FOR REQUEST");
        return NO;
    }

    if (startOffset < self.task.offset) {
        return NO;
    }

    NSData *filedata = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:_videoPath] options:NSDataReadingMappedIfSafe error:nil];

    // This is the total data we have from startOffset to whatever has been downloaded so far
    NSUInteger unreadBytes = self.task.downLoadingOffset - ((NSInteger)startOffset - self.task.offset);

    // Respond with whatever is available if we can't satisfy the request fully yet
    NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes);


    [dataRequest respondWithData:[filedata subdataWithRange:NSMakeRange((NSUInteger)startOffset- self.task.offset, (NSUInteger)numberOfBytesToRespondWith)]];



    long long endOffset = startOffset + dataRequest.requestedLength;
    BOOL didRespondFully = (self.task.offset + self.task.downLoadingOffset) >= endOffset;

    return didRespondFully;


}

这是对存放所有的请求的数组进行处理

- (void)processPendingRequests
{
    NSMutableArray *requestsCompleted = [NSMutableArray array];  //请求完成的数组
    //每次下载一块数据都是一次请求,把这些请求放到数组,遍历数组
    for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests)
    {
        [self fillInContentInformation:loadingRequest.contentInformationRequest]; //对每次请求加上长度,文件类型等信息

        BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; //判断此次请求的数据是否处理完全

        if (didRespondCompletely) {

            [requestsCompleted addObject:loadingRequest];  //如果完整,把此次请求放进 请求完成的数组
            [loadingRequest finishLoading];

        }
    }

    [self.pendingRequests removeObjectsInArray:requestsCompleted];   //在所有请求的数组中移除已经完成的

}

resourceLoader的难点基本上就是上面这点了,说到播放器,下面便顺便讲下AVPlayer的难点。

难点:对播放器状态的捕获

举个简单的例子,视频总长度60分,现在缓冲的数据才10分钟,然后拖动到20分钟的位置进行播放,在网速较慢的时候,视频从当前位置开始播放,必然会出现一段时间的卡顿,为了有一个更好的用户体验,在卡顿的时候,我们需要加一个菊花转的状态,现在问题就来了。

在拖动到未缓冲区域内,是否需要加菊花转,如果加,要显示多久再消失,而且如果在网速很慢的时候,播放器如果等了太久,哪怕最后有数据了,播放器也已经“死”了,它自己无法恢复播放,这个时候需要我们人为的去恢复播放,如果恢复播放不成功,那么过一段时间需要再次恢复播放,是否恢复播放成功,这里也需要捕获其状态。所以,如果要有一个好的用户体验,我们需要时时知道播放器的状态。

有两个状态需要捕获,一个是正在缓冲,一个是正在播放,监听播放的“playbackBufferEmpty”属性就可以捕获正在缓冲状态,播放器的时间监听器则可以捕获正在播放状态,我的demo中一共有4个状态:

typedef NS_ENUM(NSInteger, TBPlayerState) {
    TBPlayerStateBuffering = 1,
    TBPlayerStatePlaying   = 2,
    TBPlayerStateStopped   = 3,
    TBPlayerStatePause     = 4
};

这样可以对播放器更好的把握和处理了。然后说一说在缓冲时候的处理,以及缓冲后多久去播放,处理方法:进入缓冲状态后,缓冲2秒后去手动播放,如果播放不成功(缓冲的数据太少,还不足以播放),那就再缓冲2秒再次播放,如此循环,看详细代码:

- (void)bufferingSomeSecond
{
    // playbackBufferEmpty会反复进入,因此在bufferingOneSecond延时播放执行完之前再调用bufferingSomeSecond都忽略
    static BOOL isBuffering = NO;
    if (isBuffering) {
        return;
    }
    isBuffering = YES;

    // 需要先暂停一小会之后再播放,否则网络状况不好的时候时间在走,声音播放不出来
    [self.player pause];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        // 如果此时用户已经暂停了,则不再需要开启播放了
        if (self.isPauseByUser) {
            isBuffering = NO;
            return;
        }

        [self.player play];
        // 如果执行了play还是没有播放则说明还没有缓存好,则再次缓存一段时间
        isBuffering = NO;
        if (!self.currentPlayerItem.isPlaybackLikelyToKeepUp) {
            [self bufferingSomeSecond];
        }
    });
}

这个demo花了我很长的时间,实现这个demo我也遇到了很多坑最后才完成的,现在我奉献出来,也许对你会有所帮助。如果你觉得不错,还请为我Star一个,也算是对我的支持和鼓励。

demo下载地址

https://github.com/suifengqjn/TBPlayer
文/夜千寻墨(简书作者)
来自:http://www.jianshu.com/p/990ee3db0563

Objective-C和Swift混编的一些经验

阿里云iOS客户端2.1.0版本中开始尝试使用Swift来写新的业务,磕磕绊绊总算是发布了新版,总结一下开发过程中得到的经验和踩过的坑吧。

CocoaPods

使用Swift作为主要的开发语言,很难避免引入Swift编写的库。2.1.0版本引入了SwiftyJSON和Charts这两个Swift写的库,分别用于处理JSON数据和画监控图。

苹果要求使用Swift写的库,必须通过动态链接库引入,其实这一点我也是不太理解的,因为静态库也是可以依赖动态库的符号的,不存在导入多个Swift动态库的问题。允许App使用自带的动态库从iOS8才开始支持,因此必须将App支持的iOS版本升到iOS8。阿里云iOS客户端iOS7的用户不到4%,所以放弃了对iOS7的支持。

Cocoapods支持将依赖的组件编译成动态库,只需要在Podfile顶部加上”use_frameworks!“。开启这个选项之后,所有以源码引入的pod都会编译成动态链接库,而以fake framework引入的pod仍然会编译到主App里面。动态库都放在App里面的Frameworks目录,可以看到Swift相关的动态库也都拷贝进来了,所以支持Swift会导致包变大。我没有记下2.0.0版本的大小,导致没法对比2.1.0放大了多少,这是一个失误。

[~/Library/Developer/Xcode/Archives/2015-12-17/CloudConsoleApp 15-12-17 下午9.24.xcarchive/Products/Applications/CloudConsoleApp.app/Frameworks]$ tree
.
├── Charts.framework
│   ├── Charts
│   ├── Info.plist
│   └── _CodeSignature
│       └── CodeResources
├── EAIntroView.framework
│   ├── EAIntroView
│   ├── Info.plist
│   └── _CodeSignature
│       └── CodeResources
├── FMDB.framework
│   ├── FMDB
│   ├── Info.plist
│   └── _CodeSignature
│       └── CodeResources
├── FTCoreText.framework
│   ├── FTCoreText
│   ├── Info.plist
│   └── _CodeSignature
│       └── CodeResources
├── JSBadgeView.framework
│   ├── Info.plist
│   ├── JSBadgeView
│   ├── JSBadgeView.bundle
│   └── _CodeSignature
│       └── CodeResources
├── MBProgressHUD.framework
│   ├── Info.plist
│   ├── MBProgressHUD
│   └── _CodeSignature
│       └── CodeResources
├── MFSideMenu.framework
│   ├── Info.plist
│   ├── MFSideMenu
│   └── _CodeSignature
│       └── CodeResources
├── PFormanceKit.framework
│   ├── Info.plist
│   ├── PFormanceKit
│   └── _CodeSignature
│       └── CodeResources
├── Reachability.framework
│   ├── Info.plist
│   ├── Reachability
│   └── _CodeSignature
│       └── CodeResources
├── RegexKitLite.framework
│   ├── Info.plist
│   ├── RegexKitLite
│   └── _CodeSignature
│       └── CodeResources
├── SSZipArchive.framework
│   ├── Info.plist
│   ├── SSZipArchive
│   └── _CodeSignature
│       └── CodeResources
├── SnapKit.framework
│   ├── Info.plist
│   ├── SnapKit
│   └── _CodeSignature
│       └── CodeResources
├── SwiftyJSON.framework
│   ├── Info.plist
│   ├── SwiftyJSON
│   └── _CodeSignature
│       └── CodeResources
├── libswiftContacts.dylib
├── libswiftCore.dylib
├── libswiftCoreData.dylib
├── libswiftCoreGraphics.dylib
├── libswiftCoreImage.dylib
├── libswiftDarwin.dylib
├── libswiftDispatch.dylib
├── libswiftFoundation.dylib
├── libswiftObjectiveC.dylib
└── libswiftUIKit.dylib

27 directories, 49 files

$ file Charts 
Charts: Mach-O universal binary with 2 architectures
Charts (for architecture armv7):    Mach-O dynamically linked shared library arm
Charts (for architecture arm64):    Mach-O 64-bit dynamically linked shared library

因为fake framework和源代码pod分别会编译成静态库和动态库,这样会导致一个问题,就是如果源码pod又依赖fake framework,那就没办法了。CocoaPods发现这种情况会提示下面这个错误。

target has transitive dependencies that include static binaries: (xxx.framework, xxx.framework)

pod install时会把静态库编译到App里面,源码编译成的动态库没法依赖它。最终的解决方案只能是CocoaPods对fake framework和源码pod一视同仁,都编译成动态库,这样彼此才能依赖。不知道CocoaPods什么时候会支持这样。

混编

  • Swift使用Objective-C

这种情况占绝大多数。只需要在CloudConsoleApp-Bridging-Header.h这个头文件中包含相关的头文件就行。pod组件另外一种引入的方式是通过@import引入。比如SDWebImage可以通过下面两种方式引入。

//在Bridging头文件包含下面这个头文件
#import <SDWebImage/UIImageView+WebCache.h>

//另外一种办法,在Swift文件中引入。
import SDWebImage

Objective-C写的类和方法都会被改成Swift的使用方式,下面是两个很典型的例子。使用的时候需要尝试一下才能找到翻译的Swift方法。

//Objective-C
titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
titleLabel.numberOfLines = 0;

//Swift
cell.nameLabel?.lineBreakMode = .ByWordWrapping //全写是 NSLineBreakMode.ByWordWrapping
cell.nameLabel?.numberOfLines = 0

//Objective-C
UIImage *image = [UIImage imageNamed:@"abc"];

//Swift
let image = UIImage(named: "abc")
  • Objective-C使用Swift

Xcode会生成一个虚拟的头文件CloudConsoleApp-Swift.h,在工程里面是找不到这个头文件的,但是可以包含,并且跳转进去。这个文件里面包含了所有从Swift导出来的符号,比如下面这个view controller就是用Swift写的。可以看出来Objectivew-C看到的名称跟Swift源码里面的名称是一样的,但是Swift会对类做demangling,变成了_TtC15CloudConsoleApp31YWSResourceDetailViewController这样的,跟C++有点类似。

SWIFT_CLASS("_TtC15CloudConsoleApp31YWSResourceDetailViewController")
@interface YWSResourceDetailViewController : UIPageViewController
@property (nonatomic, strong) NSArray * __nonnull vcs;
@property (nonatomic, copy) NSString * __null_unspecified pluginId;
@property (nonatomic, strong) YWSSegmentedControl * __nonnull segmentedControl;
@property (nonatomic, strong) YWSInstanceListViewController * __nonnull instanceListViewController;
@property (nonatomic, strong) YWSMetricConcernedViewController * __nonnull metricConcernedViewController;
@property (nonatomic, weak) IBOutlet UIBarButtonItem * __null_unspecified addMetricBarButton;
- (void)viewDidLoad;
- (void)initSegmentedControl;
- (void)indexChanged:(id __nonnull)sender;
- (void)onNavigationBack:(id __nonnull)sender;
- (void)prepareForSegue:(UIStoryboardSegue * __nonnull)segue sender:(id __nullable)sender;
- (nonnull instancetype)initWithTransitionStyle:(UIPageViewControllerTransitionStyle)style navigationOrientation:(UIPageViewControllerNavigationOrientation)navigationOrientation options:(NSDictionary<NSString *, id> * __nullable)options OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * __nonnull)coder OBJC_DESIGNATED_INITIALIZER;
@end

Swift的优缺点

这个项目刚起步,用Swift的经验尚浅,所以都是一些比较浅薄的理解,后面有更深刻的理解再补上。

优点

  • 代码简洁。类的声明和实现在一个文件中。
  • 统一对属性和方法的调用,都用.。
  • 如果不加额外的访问控制,所有的符号都是整个项目可见,无需考虑头文件的问题。
  • 字符串处理太方便了。

    //字符串比较和拼接实在是太方便了 let foo = “abc” let bar = “abc”

    if foo == bar { //blablabla }

    print(“====(foo)+(bar)”)

  • 语言上支持延迟加载。

    lazy var imageView : UIImageView = { var imageView = UIImageView(image: UIImage(named: “empty_hint”)) imageView.contentMode = .ScaleAspectFit

      return imageView
    

    }()

    lazy var infoLabel : UILabel = { var infoLabel = UILabel() infoLabel.lineBreakMode = .ByWordWrapping //支持换行 infoLabel.numberOfLines = 0

      return infoLabel
    

    }()

    lazy var button : UIButton = { var button = UIButton() button.titleLabel?.font = UIFont.systemFontOfSize(15) button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal) button.setBackgroundImage(UIImage(named: “buy_instance_hint_button”), forState: .Normal) button.hidden = true

      return button
    

    }()

  • 多返回值。比如下面这个函数,如果使用Objective-C写还是比较麻烦的。

    //将 “创建中&#FA8C35” 翻译成对应的 “(字符串对象, 颜色对象)” func YWSTranslateRichText (str : String) -> (text : String, color : UIColor) { let statusArray = str.componentsSeparatedByString(“&”)

      if statusArray.count == 0 {
          return ("", UIColor.lightGrayColor())
      }
    
      if statusArray.count == 1 {
          return (statusArray[0], UIColor.lightGrayColor())
      }
    
      return (statusArray[0], UIColor.fromHexString(statusArray[1]))
    

    }

    //使用方式如下 let (text, color) = YWSTranslateRichText(instanceStatusConf)

  • 支持字符串作为枚举值。

    enum YWSECSInstanceStatus : String { case Starting = “Starting” case Running = “Running” case Stopping = “Stopping” case Stopped = “Stopped” }

    //使用方法 cell.ECSInstanceStatus = YWSECSInstanceStatus(rawValue: instanceStatus!)

    //转换成字符串 textDetailLabel.text = YWSECSInstanceStatus.Starting.rawValue

  • selector类型实现了 StringLiteralConvertible,使用起来更加简单。

    self.button.addTarget(self, action: “introduceResources:”, forControlEvents: .TouchUpInside)

  • 不再需要引入libextobjc这个Pod,因为Swift支持更方便的用法。在block开始的时候,在数组里面weak所有要用到的对象。

    inputViewController.finishBlock = { [weak inputViewController, weak cell, weak self] () -> Void in }

  • 函数支持默认参数。比如下面这个函数,有五个参数,其中三个有默认参数,用户需要设置的参数只有两个。

    convenience init(text: String, textColor: UIColor = UIColor.whiteColor(), bgColor: UIColor, font: UIFont = UIFont.systemFontOfSize(10), inset: UIEdgeInsets = UIEdgeInsetsMake(0, 3, 0, 3)) { //blabla }

    lazy var vipLabel : YWSInsetsTextLabel! = YWSInsetsTextLabel(text: “vip”, bgColor: UIColor.orangeColor())

  • 通过减少动态性,使用vtable替换原有的objc_msgSend,获取更高的性能。新增了final、private等关键字,编译器可以对代码做更多优化,提升性能并减少内存的使用。比如final方法不用放入虚表中,节省内存;跳转时不用查表,性能更佳;private的类如果发现有方法只在本文件中使用,可以直接内联,提高性能。

  • 在Playground工程里面练习Swift编程非常之方便,尤其是测试VFL语句的时候。

1

缺点

  • Optional让人头疼,大量的?和!,没处理好很容易导致崩溃。
  • 强类型和Optional,给JSON解释带来了灾难。
  • 目前Xcode不支持对Swift写的代码做重构。

  • Build Settings里面设置Treat Warnings as Errors对Swift代码无效。

用private修饰的类,如果使用KVC来给属性设置值,编译不会报错,运行时也不会报错,但就是设置不上。去掉private就好了。

crash分析

手解crash可以看到具体崩溃代码的行号。

$ symbolicatecrash ~/Downloads/034dc058c5d4ff1f717ec7a05d4d55b8 CloudConsoleApp.app.dSYM

Exception Type:  SIGTRAP
Exception Codes: #0 at 0x1001c09b4
Crashed Thread:  0

Thread 0 Crashed:
0   CloudConsoleApp                     0x00000001001c09b4 YWSInstanceListViewController.goToBuyPage() -> () (YWSInstanceListViewController.swift:701)
1   CloudConsoleApp                     0x00000001001c92cc specialized YWSInstanceListViewController.introduceResources(AnyObject) -> () (YWSInstanceListViewController.swift:689)
2   CloudConsoleApp                     0x00000001001c029c @objc YWSInstanceListViewController.introduceResources(AnyObject) -> () (YWSInstanceListViewController.swift:0)
3   UIKit                               0x000000018601be50 0x185fd0000 + 310864
4   UIKit                               0x000000018601bdcc 0x185fd0000 + 310732
5   UIKit                               0x0000000186003a88 0x185fd0000 + 211592
6   UIKit                               0x000000018601b6e4 0x185fd0000 + 308964
7   UIKit                               0x000000018601b314 0x185fd0000 + 307988
8   UIKit                               0x0000000186013e30 0x185fd0000 + 278064
9   UIKit                               0x0000000185fe44cc 0x185fd0000 + 83148
10  UIKit                               0x0000000185fe2794 0x185fd0000 + 75668
11  CoreFoundation                      0x00000001812a8efc 0x1811cc000 + 904956
12  CoreFoundation                      0x00000001812a8990 0x1811cc000 + 903568
13  CoreFoundation                      0x00000001812a6690 0x1811cc000 + 894608
14  CoreFoundation                      0x00000001811d5680 0x1811cc000 + 38528
15  GraphicsServices                    0x00000001826e4088 0x1826d8000 + 49288
16  UIKit                               0x000000018604cd90 0x185fd0000 + 511376
17  CloudConsoleApp                     0x000000010014b4e0 main (main.m:16)
18  libdyld.dylib                       0x0000000180d768b8 0x180d74000 + 10424

//不过我对着这行代码分析了好久,实在想不出来崩溃的原因。没有任何crash提示信息。
//这个版本Swift代码只有这样一个crash
//后面再看看新crash会不会也是这样
self.resourceType = YWSXXX.shareInstance().getXXXByXXX(self.XXX.pluginId)

实际证明Swift的crash信息非常不准确,能知道崩溃的文件和函数,行号不准确,也不会输出Application Specific Information。比如下面这个crash。

Incident Identifier: 54087A46-D37D-454B-9305-22ED5420B58B
CrashReporter Key:   TODO
Hardware Model:      iPhone6,2
Process:             CloudConsoleApp [696]
Path:                /var/mobile/Containers/Bundle/Application/E8E24C8B-A47B-425E-863F-A871F273FCA2/CloudConsoleApp.app/CloudConsoleApp
Identifier:          com.aliyun.wstudio.amc.AliyunMobileApp
Version:             2.2.0 (2169)
Code Type:           ARM-64
Parent Process:      ??? [1]

Date/Time:           2016-01-25 10:44:56 +0000
OS Version:          iPhone OS 9.2.1 (13D15)
Report Version:      104

Exception Type:  SIGTRAP
Exception Codes: #0 at 0x100139e80
Triggered by Thread:  0

Thread 0 Crashed:
0   CloudConsoleApp                 0x0000000100139e80 __TFC15CloudConsoleApp24YWSTouchIDViewController14viewWillAppearfS0_FSbT_ (in CloudConsoleApp) + 1304
1   CloudConsoleApp                 0x0000000100139eb0 __TToFC15CloudConsoleApp24YWSTouchIDViewController14viewWillAppearfS0_FSbT_ (in CloudConsoleApp) + 44
2   UIKit                           0x000000018722c74c 0x0000000187200000 + 182092
3   UIKit                           0x000000018722c4c0 0x0000000187200000 + 181440
4   UIKit                           0x00000001872d3130 0x0000000187200000 + 864560
5   UIKit                           0x00000001872d2a6c 0x0000000187200000 + 862828
6   UIKit                           0x00000001872d2694 0x0000000187200000 + 861844
7   UIKit                           0x00000001872d25fc 0x0000000187200000 + 861692
8   UIKit                           0x000000018720f778 0x0000000187200000 + 63352
9   QuartzCore                      0x0000000184c1eb2c 0x0000000184c10000 + 60204
10  QuartzCore                      0x0000000184c19738 0x0000000184c10000 + 38712
11  QuartzCore                      0x0000000184c195f8 0x0000000184c10000 + 38392
12  QuartzCore                      0x0000000184c18c94 0x0000000184c10000 + 35988
13  QuartzCore                      0x0000000184c189dc 0x0000000184c10000 + 35292
14  QuartzCore                      0x0000000184c120cc 0x0000000184c10000 + 8396
15  CoreFoundation                  0x00000001824d8588 0x00000001823fc000 + 902536
16  CoreFoundation                  0x00000001824d632c 0x00000001823fc000 + 893740
17  CoreFoundation                  0x00000001824d675c 0x00000001823fc000 + 894812
18  CoreFoundation                  0x0000000182405680 0x00000001823fc000 + 38528
19  GraphicsServices                0x0000000183914088 0x0000000183908000 + 49288
20  UIKit                           0x000000018727cd90 0x0000000187200000 + 511376
21  CloudConsoleApp                 0x000000010007f988 main (in CloudConsoleApp) (main.m:16)
22  libdyld.dylib                   0x0000000181fa68b8 0x0000000181fa4000 + 10424

//确实崩溃在viewWillAppear函数中,但是是97行的 as! 导致的,在crash信息里面这两个重要的信息没有暴露出来。
override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)

    //let urlString = YWSXXX.sharedInstance().get("userIcon") as! String

    //因为2.1.0重构过登录模块,新的登录模块才会保存 userIcon 这个值
    //所以2.2.0覆盖2.1.0之前的版本用 as! 会崩溃,这里改成 as? 了
    if let urlString = YWSXXX.sharedInstance().get("userIcon") as? String {
    }
}

总结

Swift 2.1版本已经非常稳定,苹果将其开源,也表明对Swift的质量和可靠性有足够的信心。开源社区开始涌现一批优秀的Swift库,比如Charts,这个画图的组件很不错。StackOverflow的答案中很多人会同时提供Objective-C和Swift两个版本。目前来看唯一美中不足的问题就是解出来的crash没有Objective-C那么直观了,很多时候都得靠猜。

文/阿呆少爷(简书作者)
原文链接:http://www.jianshu.com/p/a5e6e574145b

刚刚在线-让学习iOS开发更简单

非常兴奋的告诉大家一个好消息:刚刚在线官网正式上线啦!欢迎大家访问!官网地址:

www.ggteach.com

刚刚在线的使命-让学习iOS开发更简单!我们的团队有30多位实战讲师,将为大家提供更多的技术分享、视频直播、实战项目等等。

本周六技术分享

好消息接连不断,技术分享说来就来!本周六晚上8点,注意时间噢!可以提前关注刚刚在线教育直播间,以免到时候找不到。

技术分享简介:

斗鱼技术分享课程
主题:《App Thinng》
时间:2016-03-5(周六)晚8:00 至 9:00
斗鱼房间:刚刚在线教育
地址:http://www.douyutv.com/ggteach
大家可以提前关注。 

——美丽的分割线——–

编程是一种生活方式

今天在回来的路上看到一个好朋友分享了一篇文章《美国监狱新趋势–学编程》

1

是的,不可思议。有网友表示:想去美国监狱,包吃住还免费学编程!令人震惊的是:“他们中没有人因再次犯罪而重返监狱,他们走了一条正确的路。”

很多美国人已经意识到编程的重要性!编程不再是一项找工作的技能,而是一种生活方式,每个人都多少应该懂一点编程。

2

为了呼吁美国人民学习编程知识,奥巴马甚至还亲自挽起袖子写了一段javascript代码,这也让他成为美国历史上第一位会写代码的总统。(虽然写得不怎么样)

国内编程现状

在中国的大学里面,计算机课程只是讲一些理论知识。很多大学生(包括计算机系)毕业之后,很难靠自己掌握的编程知识找到一份工作。这是一件何其可悲的事情。

很多同学无奈之下,会选择线下的培训班,但是培训班的费用高的让人望而却步。有的同学因为昂贵的费用,这一生可能就与编程无缘;

有的同学可能会咬牙坚持贷款2万甚至更多,来学习一项编程的技能。还未入社会,就要背负着为银行打工的重担。

刚刚在线的诞生

刚刚在线的诞生就是为了帮助广大iOS开发爱好者,让学习iOS开发更简单!

刚刚在线推出iOS开发技术直播课程,直播课程将免费开放,直播课程涉及iOS开发常用的控件、网络请求、第三方类库等。

同时,刚刚在线推出iOS开发社群。为社群成员提供高级进阶、在线答疑、实战项目、面试技巧、独立博客、职位推荐等服务。

刚刚在线团队成员来自北京、上海、广州、美国、日本等地方,每一位讲师都是实战派!

女程序员做了个梦,众网友的神回复

女程序员发的一条微博:

“昨晚梦见男朋友和别的女人在逛街,梦里我的第一反应就是查源代码,结果调试半天查不出来为什么显示的那个女人不是我,最后含泪把那个女人注释掉了,再一运行就是我男朋友自己逛街了。” 众网友的神回复:

1、把那个女人的指针指向你即可; (:з」∠)

2、谁让你把男朋友设成public的?;Σ(っ °Д °;)っ

3、加个断点看看那女人是谁; (ಡωಡ)

4、心真软,就应该把他的接口屏蔽掉; (눈_눈)

5、Protected逛街(youOnly); (ง•̀_•́)ง

6、设计问题,应该采用单例模式; (๑•́ωก̀๑)

7、没做回归测试; ヽ(  ̄д ̄;)ノ

8、标准做法是做个断言; (๑•̀ㅂ•́) ✧

9、注释掉了,逛街的参数不用改吗?; (@ ̄ー ̄@)

10、最后含泪把那个女人给注释掉了,再一运行就是我男朋友自己逛街了—>很明显是变量名作用域的问题,改个名就行了(๑•̀ㅂ•́) ✧

11、还可以有个多线程的算法,把你的优先级设成99,一个idle线程的优先级设成50,把那个女人的优先级设成49。酱紫就永远都调度不到啦 (๑•́ωก̀๑)

12、那也没关系,那就老调用那个女人。。。你BF放在那里不动。。。养着 (ಡωಡ)

13、上绝招,用goto,做个死循环,让他们逛死 (๑•̀ㅂ•́) ✧

14、善心点,别goto了,调用exit函数结束进程吧,冤冤相报何时了啊 ヽ(  ̄д ̄;)ノ

15、查一下Log,仅仅只有逛街吗?ԅ(¯ㅂ¯ԅ)

来自:http://www.wtoutiao.com/p/Xa51bf.html

XMPP客户端登录开发详解

由上篇文章将了如何在MAC本地搭建一个Openfire服务器,今天我们就要着手使用 XMPPFramework来开发 基于XMPP 协议的即时通讯IOS 客户端系统。今天主要看登录功能开发,可能有人会质疑,我们都没有开发注册功能,怎么开发登录功能。注册账号我们有捷径,服务器都在本地,当然很好做了。另外就是通过MAC 自带的客户端 Messages 进行注册。

首先,我们需要搭建起来IOS 的工程,并且将XMPPFramework 引入到我们的工程中来,当然我们可以使用源码 直接导入,也可以使用cocoapods来进行依赖。本文直接使用源码来进行开发。由于在Github上的源码有一些问题,所以大家可以来https://github.com/TerryLMay/TMXMPPClient/tree/master/TMXMPPClient/ThirdTools/XMPPFrameworkio下载相关的XMPPFramework相关的代码。

下载关于XMPPFramework的代码之后,我们需要做的是 创建一个工程,然后将源代码导入到我们的工程中去。当然直接导入并且编译会出现libxml找不到的问题。我们需要在工程中导入libxml2.tbd(ios 9之后的名称) 以及 libresolv.9.tbd(ios 9之后的后缀)。然后在Build Setting的Head search 中加上 头文件的链接地址

${SDKROOT}/usr/include/libxml2

这之后,重新编译工程即可编译成功。后面,我们需要做的就是开发登录功能了,在开始之前,我们还是先看一下怎么注册一个账号吧;登录Openfire后台,创建一个用户。

通过在浏览器中输入 (前提 是 openfire服务器以及mysql服务器都已经启动了)

127.0.0.1:9090     

进入如下界面

1

点击左上角用户/组 进入用户管理界面

2

然后点击 左侧 导航栏中的新建用户 填写好用户信息就可以了。

3

到这边基本上用户注册环节 已经结束了。

开发 登录功能,基本上包括 登录界面的开发、XMPP登录逻辑的开发。登录界面我们可以 随便搭建一个登录界面就可以了。我今天主要是说一下 XMPP登录部分。

首先,创建关于XMPPLoginManager类,实现XMPPStreamDelegate,我定义了自己的一个宏,表示使用的Openfire服务器的地址 以及 端口号

#define LocalOpenfire 1

#if LocalOpenfire

#define HOST_NAME @"127.0.0.1"
#define HOST_PORT 5222
#define CONNECT_IDENTIFIER @"@"

#endif

#define TIME_OUT 20

然后 定义XMPPStream、XMPPReconnect实例,并且初始化

- (void)initXMPPStream {
    self.loginXmppStream = [[XMPPStream alloc] init];
    [self.loginXmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
}

- (void)initXMPPReconnect {
    self.loginReconnectManager = [[XMPPReconnect alloc] init];
    [self.loginReconnectManager activate:self.loginXmppStream];
    // You can also optionally add delegates to the module.
    [self.loginReconnectManager addDelegate:self delegateQueue:dispatch_get_main_queue()];
}

定义 Login按钮点击之后的点击事件调用 XMPPLoginManager中的如下方法进行连接:

#pragma mark -- connect xmpp method for login viewController
- (void)connectXMPPServer:(NSString *)userName password:(NSString *)password {
    self.userName = userName;
    self.password = password;

    NSString *myJid = [NSString stringWithFormat:@"%@%@%@", userName, CONNECT_IDENTIFIER, HOST_NAME];
    self.loginXmppStream.myJID = [XMPPJID jidWithString:myJid];

    self.loginXmppStream.hostName = HOST_NAME;
    self.loginXmppStream.hostPort = HOST_PORT;

    NSError *connectError = nil;
    [self.loginXmppStream connectWithTimeout:TIME_OUT error:&connectError];

    if (connectError) {
        NSLog(@"%@", connectError);
        [self.loginDelegate loginXMPPConnectError:connectError];
    }
}

后面就是处理XMPPStream的各种回调就可以了

#pragma mark -- xmppstream delegate
//连接xmpp成功之后,使用密码认证
- (void)xmppStreamDidConnect:(XMPPStream *)sender {

    NSError *authError = nil;
    [self.loginXmppStream authenticateWithPassword:self.password error:&authError];

    if (authError) {
        NSLog(@"%@", authError);
        [self.loginDelegate loginXMPPDidNotAuthenticate];
    }
}

//认证通过之后的处理
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
    NSLog(@"%@", @"认证通过");
    [self.loginDelegate loginXMPPDidAuthenticate];
}

//连接服务器的超时处理
- (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
    NSLog(@"连接超时");
    [self.loginDelegate loginXMPPConnectDidTimeout];
}

//认证没有通过处理
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error {
    NSLog(@"认证失败");
    [self.loginDelegate loginXMPPDidNotAuthenticate];
}

基本上这样就可以登录了,登录成功之后,可以跳转到相应的界面。提一下我们 能看到的XMPP交互流程 包括:

1、XMPP 使用用户名去连接服务器;

2、XMPP 连接成功之后,通过密码去服务器认证

3、认证成功之后,处理自己想处理的一下事情。

但其实XMPP 报文交互包括好几步,我就从别人那边摘录一点交互细节过来,就不自己一个个的抓包看了。

C1: 客户端初始化流给服务器 

<stream:stream to="127.0.0.1" xmlns="jabber:client" 
xmlns:stream="http://etherx.jabber.org/streams" version="1.0"> S1: 服务器向客户端发送流标签作为应答: 
<?xml version='1.0' encoding='UTF-8'?><stream:stream 
xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="192.168.1.185" id="fb0cfcad" xml:lang="en" version="1.0"> 

S2: 发送 STARTTLS范围 

<stream:features> 
<starttls xmlns="urn:ietf:params:xml:ns: xmpp-tls"></starttls> <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> 
<mechanism>PLAIN</mechanism> <mechanism>CRAM-MD5</mechanism> <mechanism>ANONYMOUS</mechanism> <mechanism>DIGEST-MD5</mechanism> <mechanism>JIVE-SHAREDSECRET</mechanism> </mechanisms> 
<compression xmlns="http://jabber.org/features/compress"> 
<method>zlib</method> </compression> 
<auth xmlns="http://jabber.org/features/iq-auth"/> 
<register xmlns="http://jabber.org/features/iq-register"/> </stream:features>  

C2:客户端发送 STARTTLS 命令给服务器: 

<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> 

S3:服务器通知客户端可以继续进行: 

<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/> 

C3:TLS 握手成功, 客户端初始化一个新的流给服务器

<stream:stream to="192.168.1.185" xmlns="jabber:client" 
xmlns:stream="http://etherx.jabber.org/streams" version="1.0">  

S4:服务器通知客户端可用的验证机制: 

<?xml version='1.0' encoding='UTF-8'?> 
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" 
xmlns="jabber:client" from="192.168.1.185" id="ad6f53e8" xml:lang="en" version="1.0"> <stream:features> 
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> <mechanism>DIGEST-MD5</mechanism> <mechanism>PLAIN</mechanism> 
<mechanism>ANONYMOUS</mechanism> <mechanism>CRAM-MD5</mechanism> </mechanisms> 
<compression xmlns="http://jabber.org/features/compress"> <method>zlib</method> </compression> 
<auth xmlns="http://jabber.org/features/iq-auth"/> 
<register xmlns="http://jabber.org/features/iq-register"/> </stream:features> 

C4: 客户端选择一个验证机制: 

<auth mechanism="DIGEST-MD5" xmlns="urn:ietf:params:xml:ns:xmpp-sasl"></auth> 

S5:服务器发送一个 [BASE64] 编码的挑战给客户端: 

<challenge xmlns="urn:ietf:params:xml:ns:xmpp-sasl">cmVhbG09IjE5Mi4xNjguMS4xODUiLG5vbmNlPSJlcEJaZlBxU1p0WGlLYzBqdGpwT0I1a01HMHdiY0hsUmNhOE52ZE9SIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz</challenge>  C5:客户端发送一个[BASE64]编码的回应这个挑战: 
<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iYWRtaW4iLHJlYWxtPSIxOTIuMTY4LjEuMTg1Iixub25jZT0iZXBCWmZQcVNadFhpS2MwanRqcE9CNWtNRzB3YmNIbFJjYThOdmRPUiIsbmM9MDAwMDAwMDEsY25vbmNlPSJMNDJ1SE1XK2piemh6N1hzdWRndHN1V1VIT2hNZmFLVUJpcU5iR1p2IixkaWdlc3QtdXJpPSJ4bXBwLzE5Mi4xNjguMS4xODUiLG1heGJ1Zj02NTUzNixyZXNwb25zZT1hN2JhMWZlOThiMDc2ZjUzZWUzNTczM2Q5NDMwODJlYSxxb3A9YXV0aCxhdXRoemlkPSJhZG1pbiI=</response> 

S6:服务器通知客户端验证成功 

<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl">cnNwYXV0aD0yNDZlZDcyOTQ3ZjVhYzFiNWQ2ZDc4ZTkxM2QzMmFjMQ==</success> 

C6客户端初始化一个新流给服务器: 

<stream:stream to="192.168.1.185" xmlns="jabber:client" 
xmlns:stream="http://etherx.jabber.org/streams" version="1.0">

基本上XMPP的登录细节就已经清楚了。后面我们看一下 注册相关的模块 以及 联系人请求模块的开发。如果想获取源码的话,请转到https://github.com/TerryLMay/TMXMPPClient/tree/master/TMXMPPClient

本文作者:Terry 专注于技术开发;打滚于IOS 技术领域,偶尔也会迈出脚步探索其他领域
地址:http://www.terrylmay.com/terrylmay.github.io/2016/01/31/XMPP%E5%AE%A2%E6%88%B7%E7%AB%AF%E7%99%BB%E5%BD%95%E5%BC%80%E5%8F%91%E8%AF%A6%E8%A7%A3/

推荐:iOS开发面试题整理(一)

年底了估计有朋友已经坐不住了,前面有一篇文章介绍《 年底程序猿迁徙的5大理由,你占了几个? 》。你是不是也占几个呢?

跳槽不可避免会遇到面试,有些公司会找一些奇葩的面试题来坑我们。所以我们不得不做好防备、提前备战,怎么感觉跟备考一样呢?

我不得不遗憾的告诉你,这比备考重要多了。面试题做不好,很有可能错过一次大好机会。很早之前我收集了很多面试题,以后都分享出来。大家如果手里有不错的面试题目,可以直接发给我,我的邮箱:worldligang@163.com,我也会分享给大家!

本文挡会持续更新,欢迎关注公众号iOS开发:iOSDevTip 关注之后回复:面试

newteiba.png

1. Object-C有多继承吗?没有的话用什么代替?

cocoa 中所有的类都是NSObject 的子类

多继承在这里是用protocol 委托代理 来实现的 你不用去考虑繁琐的多继承 ,虚基类的概念. ood的多态特性 在 obj-c 中通过委托来实现.

2. Object-C有私有方法吗?私有变量呢?

objective-c – 类里面的方法只有两种, 静态方法和实例方法. 这似乎就不是完整的面向对象了,按照OO的原则就是一个对象只暴露有用的东西. 如果没有了私有方法的话, 对于一些小范围的代码重用就不那么顺手了.

在类里面声名一个私有方法

@interface Controller : NSObject { NSString *something; } 
+ (void)thisIsAStaticMethod; – (void)thisIsAnInstanceMethod;
 @end

@interface Controller (private) 
- (void)thisIsAPrivateMethod; 
- @end

@private可以用来修饰私有变量

在Objective‐C中,所有实例变量默认都是私有的,所有实例方法默认都是公有的

3. #import和#include的区别,@class代表什么?

@class一般用于头文件中需要声明该类的某个实例变量的时候用到,在m文件中还是需要使用#import‘

而#import比起#include的好处就是不会引起重复包含

4. 线程和进程的区别?

进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

5. 堆和栈的区别?

管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。 申请大小:

栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出

分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的。

6. Object-C的内存管理?

1.当你使用new,alloc和copy方法创建一个对象时,该对象的保留计数器值为1.当你不再使用该对象时,你要负责向该对象发送一条release或autorelease消息.这样,该对象将在使用寿命结束时被销毁.

2.当你通过任何其他方法获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理.如果你打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它.

3.如果你保留了某个对象,你需要(最终)释放或自动释放该对象.必须保持retain方法和release方法的使用次数相等.

7 浅复制和深复制的区别?

答案:浅层复制:只复制指向对象的指针,而不复制引用对象本身。

深层复制:复制引用对象本身。

意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅复制来说,A和A_copy指向的是同一个内存资源,复制的只不过是是一个指针,对象本身资源 还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改,这其实违背了我们复制拷贝的一个思想。深复制就好理解了,内存中存在了 两份独立对象本身。

用网上一哥们通俗的话将就是:

浅复制好比你和你的影子,你完蛋,你的影子也完蛋

深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。

我已经写了48年代码了,我感觉我还能写下去

这是来自quroa的问题:Why-aren’t-there-a-lot-of-old-programmers-at-software-companies下的一个回答。

很多人会疑问是不是程序员最后只能转管理,是不是到了中年之后就应该放弃编程。看到这个回答后很受触动,尽力翻译出来,有不恰当的地方欢迎指出。

下 个星期我就69岁了。我从1967年开始编程。到现在已经48年了,从COBOL一直写到jQuery。我已经忘掉的编程语言比很多程序员这辈子遇到的还 多。我现在自己接一些项目,因为作为一个员工我完全是个工作狂,即使在我已经三十多岁的时候。我永远不会再去做一份每天工作8小时的传统编程工作。

我 已经写了两三百万行或者四百万行代码,我觉得我至少要写到…额,算了,无所谓。代码写的再多也没有什么意义,除非,这个代码对你关心的人们产生了好的 影响。(原文是: a positive impact on people I care about,和《硅谷》里的make world a better place 真是异曲同工)

变老带来的一个好处是,我不会再相信那些有一个点子就能改变世界的二逼想法。我只想把我应该做的工作做好,然后交给用户一个体验更好的产品。

但 是一个老年程序员有一个非常明显的缺点。这个世界变化的非常快,而且越来越快。15年前,我投身C++,然后我很精通它。大概4年前,我开始沉迷于 jQuery,同时学习怎么把jQuery和C#混在一起(hook jQuery and C#)去快速实现一个用户界面。3年前我从公司退休,于是我又开始关注C#并且沉迷于WordPress。现在是Drupal(php的一个框架)。趋势 就是学习一样新东西对我而言越来越难。我现在还在尝试用本地存储和 Web Workers写一个WordPress插件,调试这些真的让我有点累。

但是我真正要坚持的是要不断的编程,不要停,因为我真的热爱编程,其实我也只会这么一件事。不像人际交往、建立社交圈是我最近二十几年才开始尝试去做。

所以左脑用于编程,右脑用于处理人际关系让我保持着生活的平衡。这肯定因为我已经和一个美丽的女人结婚四十多年,我和我的三个孩子也有着非常和睦的关系。

我给自己设立了一个目标,至少要写50年(到2017年6月)。然后在70岁的时候成为Mankind Project & New Warriors Training Adventure项目联合领袖。非常有挑战性。几年以后你可以再来看看我做的怎么样。

来自:http://www.jianshu.com/p/285421a867c9

苹果公司即将放假8天暂停APP提交审核

圣诞节对于美国人来说是一个重要的节日。每年的圣诞节苹果公司都会全员放假8天时间,时间2015年12月22日之2015年12月29日。今年也不例外。

昨天登录苹果开发者平台: http://developer.apple.com 就有提示:

dev

提示的很清楚:

我们将于这段时间休假。在此期间,我们会停用一些功能,如 APP 提交等...

在放假这段时间里,苹果将会暂停AppStore推荐、审核、提交、更新等工作。所以这段时间内更新、上架都会受到影响。

元旦之前,想要上架的朋友,必须得抓紧时间啦!