1. 程式人生 > >iOS程式設計基礎-OC(九)-專家級技巧:使用執行時系統API

iOS程式設計基礎-OC(九)-專家級技巧:使用執行時系統API

     第九章 專家級技巧:使用執行時系統API

     第7章和第8章介紹了OC的動態特性和用於實現這些特性的執行時系統結構;

     本章將通過幾個示例程式使你獲得使用執行時系統功能及其API的實踐經驗;

        你將使用NSInvocation API建立一個動態代理;

        使用NSBundle API動態載入你編寫的框架包;

        還將編寫一個廣泛使用執行時系統庫API的程式;

     本章之後,您將能夠掌握錯綜複雜的OC執行時系統;

     是不是很期待;

     9.1 使用可選包擴充套件程式

     OC提供了非常多的動態特性,他們提供了這門語言的能力和靈活性;使你能夠在執行程式的過程中修改程式;

        第七章介紹了動態載入,還簡要介紹了使用Foundation框架中的NSBundle類管理包的方式;

        接下來我們將使用這些特性編寫一個程式,使用可選包擴充套件正在執行的程式;

     9.1.1 方法

     這個例子實際需要編寫兩個工程;

        需要在一個工程中編寫可以使用可選包的程式;

        在另一個工程中建立可選包;

     具體步驟如下:

     1)在工程1中,編寫一個協議和遵循該協議的類;

     2)在工程2中,建立一個包含另一個遵守該協議的類的可選包;

     3)在工程1中,使用NSBundle類找到並載入這個包(在步驟2中建立的),使用該包中的類建立一個物件,然後呼叫這個物件中的方法;

     不是很難理解,我們繼續看;

     9.1.2 步驟1:編寫基礎程式碼

     工程1我們就是用當前的這個專案;

     接下來建立一個協議和遵守這個協議的類;

        我們已經知道(第二章)使用協議可以宣告通用的方法和屬性;

        將協議與動態型別一起使用可以獲得一種理想的機制;

            通過包以動態方式載入的類設定實現的方法;

        接下來新建這個協議;

            選擇協議模板,這樣就在導航欄窗格中添加了一個名為C9Greeter.h的標頭檔案;

            點選可以看到這個C9Greeter協議;之後新增一個方法;

        再建一個遵循這個協議的類C9BasicGreeter;

     (Code9_1:C9Greeter.h C9BasicGreeter.h C9BasicGreeter.m)

#import <Foundation/Foundation.h>

@protocol C9Greeter <NSObject>

-(NSString *)greeting:(NSString *)salutation;

@end

#import <Foundation/Foundation.h>
#import "C9Greeter.h"

@interface C9BasicGreeter : NSObject

@end

#import "C9BasicGreeter.h"

@implementation C9BasicGreeter

-(NSString *)greeting:(NSString *)salutation{
    return [NSString stringWithFormat:@"%@,Wrold",salutation];
}

@end

     現在我們來進行測試這段程式碼;

        匯入C9BasicGreeter類的標頭檔案;

        實現如下程式碼;

    id<C9Greeter> greeter1 = [[C9BasicGreeter alloc] init];
    NSLog(@"%@",[greeter1 greeting:@"Hello"]);

      log:

      2017-12-15 15:06:30.574490+0800 精通Objective-C[38928:3729761] Hello,Wrold

      我們慢下腳步,來看看這段程式碼:

        首先,在遵守協議的時候,我們並沒有通過擴充套件的方式,聲明當前類已經遵循了這個協議;

            @interface C9BasicGreeter()<C9Greeter>

            @end

        而是手動將這個協議的方法在C9BasicGreeter類中進行了實現;

        這個裡建立的BasicGreeter物件賦給了一個名為greeter的變數;

        這個變數被宣告為id型別並且遵守Greeter協議;

        然後,呼叫了物件中的方法greeting:,最後顯示了結果;

      使用id<C9Greeter> greeter1宣告的變數,就可以將任何遵守C9Greeter協議的OC物件賦值給他;

      接下來要建立另一個遵守C9Greeter協議的類,只不過這次是在可選包中建立;

     9.1.3 步驟2:建立一個可選包

      新建一個工程;參考下圖:


      (Pic9_1)

      命名為C9CustomGreeter;這樣我們就建立了一個空包;

      我們來看下這個工程的導航欄窗格,如下圖:


      (Pic9_2)

      1)products分組下邊有一個.bundle檔案,這個就是要新增到你編寫的程式碼和資源中的包;

      2)info.plist也是一個必不可少的檔案,含有這個包的配置資訊;

      接下來向該包中新增一個實現C9Greeter協議的類;

        要做到這一點,首先需要將協議對應的標頭檔案包含到這兒工程中;

        我們按住Control點選導航欄窗格,xcode會開啟一個下拉選單,我們可以新增檔案到當前工程;(如下圖)


      (Pic9_3)


      (Pic9_4)

        繼續我們的操作,在可選包工程中新增一個遵守C9Greeter協議的新類:C9CustomGreeter;

      (Code9_2:C9CustomGreeter.h C9CustomGreeter.m)

#import <Foundation/Foundation.h>
#import "C9Greeter.h"

@interface C9CustomGreeter : NSObject

@end

#import "C9CustomGreeter.h"

@implementation C9CustomGreeter

-(NSString *)greeting:(NSString *)salutation{
    return [NSString stringWithFormat:@"%@,Universe",salutation];
}

@end

      通過Product選單中選擇Build選項,編譯了這個包;

        這樣我們就建立了一個包;

        選擇這個包檔案,可以在右側檔案檢查器顯示出完整的檔案路徑;

        當你使用NSBundle類載入這個包的時候,需要用到這個路徑;


     9.1.4 步驟3:動態載入包

      接下來我們需要通過該包以動態的方式建立一個物件(通過該包中的類),並呼叫這個物件的方法;

      在實際測試程式碼執行是前,我們還需要做一些準備:

        最主要的就是如何獲取bundle包的地址;

        我們將通過main函式的引數argv[],來獲取對應傳入的引數(這個可以參考我的C語言blog中關於這部分的講解),稍後會有介紹;

        複製包路徑,按照下圖在工程1中進行配置:


        (Pic9_6)

        同時在工程目錄下新建一個plist檔案,用來記錄和儲存這個路徑引數;(其他的儲存路徑方式均可)

        (Code_main.m)

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "C9Greeter.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        NSString * bundleGreeterPath;
        if (argc != 2) {
            NSLog(@"沒有獲得包路徑!");
        }else{
            bundleGreeterPath = [NSString stringWithUTF8String:argv[1]];
            if (bundleGreeterPath == nil) {
                NSLog(@"Bundle not found at this path!");
            }else{

                NSString * filePath = [[NSBundle mainBundle]pathForResource:@"PathList" ofType:@"plist"];
                NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
                NSMutableDictionary * pathData;
                if (!dic) {
                    pathData = [NSMutableDictionary dictionary];
                }else{
                    pathData = [NSMutableDictionary dictionaryWithDictionary:dic];
                }
                [pathData setValue:bundleGreeterPath forKey:@"bundleGreeterPath"];
                BOOL success = [pathData writeToFile:filePath atomically:YES];
                NSLog(@"%d",success);
            }
        }
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

      接下來,回到當前工程(工程1),執行如下程式碼;

id greeter = [[C9BasicGreeter alloc] init];
    NSLog(@"%@",[greeter greeting:@"Hello-1"]);
    
    id greeterB;
    
    NSString * filePath = [[NSBundle mainBundle]pathForResource:@"PathList" ofType:@"plist"];
    NSDictionary * dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSLog(@"%@",dic);
    NSString * bundlePath =dic[@"bundleGreeterPath"];
    
    NSBundle * greeterBundle = [NSBundle bundleWithPath:bundlePath];
    
    if (greeterBundle == nil) {
        NSLog(@"Bundle not found at this path!");
    }else{
        NSError * error;
        BOOL isLoaded = [greeterBundle loadAndReturnError:&error];
        if (!isLoaded) {
            NSLog(@"Error = %@",[error localizedDescription]);
        }else{
            //載入包後使用該包建立一個物件並向這個物件傳送一條訊息
            Class greeterClass = [greeterBundle classNamed:@"C9CustomGreeter"];
            greeterB = [[greeterClass alloc] init];
            NSLog(@"%@",[greeterB greeting:@"Hello-2"]);
            
            //使用完以動態方式載入的包,現在解除安裝它
            //先釋放所有用包中類建立的物件!
            greeterB = nil;
            BOOL isUnloaded = [greeterBundle unload];
            if (!isUnloaded) {
                NSLog(@"Counld not unload bundle!");
            }
        }
    }

    這段程式碼雖然看起來很長,我們來分段理解下:

       1)獲取包的路徑引數;

       2)建立一個NSBundle物件;

       3)載入包;

       4)獲得包中的類;

       5)解除安裝包;

       6)設定包路徑引數;

    再逐條研究下這些程式碼的邏輯:

       1.獲取包的路徑引數

       OC中main()函式會設定兩個引數:argc和argv;

       使用這些引數能夠在執行程式的過程中向程式傳遞引數;

       int main(int argc, char * argv[]);

        引數argc設定了程式引數的數量;

        argc陣列儲存了以空格分隔的引數值;

            argv[0]儲存的是程式的名字,對應的argc的值大於等於1;(如果實在命令列中執行程式 程式名以及其之後空格間隔的內容就是main函式接受的引數,這一部分在我的C語言blog中有詳解);

       我們剛剛在scheme中新增argument就是工程2中生成的包的路徑;

       這樣這一點對應的程式碼就好理解了;

       2.建立一個NSBundle物件

       NSBundle * greeterBundle = [NSBundle bundleWithPath:bundlePath];

       if (greeterBundle == nil) {

       }

       根據路徑建立一個NSBundle物件,並驗證該物件是否成功建立;

       3.載入包

       -(BOOL)loadAndReturnError:(NSError **)error;

       NSBundle提供了多個向OC執行時系統中載入包的方法,上邊的就是其中之一,出錯的話也會及時返回,成功則返回BOOL值YES;

       4.獲得包中的類

       從包中獲取類,然後建立一個物件使用;

       這裡也可以使用[greeterBundle principalClass]的方式獲取類greeterClass;

            principalClass被用於獲取包的首要類;

            首要類用於控制包中含有的其他類;

            包的Info.plist檔案舍之類首要類的名字(沒有設定的話,那麼從包中載入的第一個類就是首要類);


       (Pic9_7)

            這個生成的例項被賦值給了已經宣告的變數greeterB,這個變數在宣告時最好直接宣告為id<C9Greeter> greeterB;

       5.解除安裝包

       不再需要的包可以解除安裝,以節省系統資源;

       6.設定包路徑引數

       其實要做的就是查明bundle包的路徑,在程式執行時將這個路徑設定為它的輸入引數;

       如何查詢該路徑以及如何設定,前面我們都已截圖說明過,這裡不再贅述;

       這裡有必要知道,scheme方案定義了Xcode功能和工作區的建立和測試設定;

       建立配置資訊中含有執行目標程式時傳遞的引數;因此在工程1中這裡設定的是包的完整路徑;

       說了這麼多一直還沒給大家看log呢;我們來執行下看看:

       2017-12-18 14:16:24.005835+0800 精通Objective-C[50675:5339959] Error = The bundle “C9CustomGreeter” couldn’t be loaded because it is damaged or missing necessary resources.

       報錯了!

       這個問題我大致查了一下,應該是涉及到動態載入更新程式碼的方式,在客戶端是受限的,或者有其他方式;

            比如我們將一個模組獨立成一個包,然後從伺服器下載,再到客戶端去使用,會達到動態更新的目的;

            這個問題,暫時還沒有更加深入的理解,也在網上同人交流,mark在這,日後清楚之後,再分出一節單獨討論吧;(本段標紅)

       為了證明這種方式的可行性,我們新建了一個專案C9DynaLoader,將其作為工程1的替代,將協議和實現協議的類都在這個工程中使用;

       在其main.m中執行上述邏輯的測試程式碼如下:

       (Code_C9DynaLoader_main.m)

#import <Foundation/Foundation.h>
#import "C9BasicGreeter.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        id greeter = [[C9BasicGreeter alloc] init];
        NSLog(@"%@",[greeter greeting:@"Hello-1"]);
        NSString * bundleGreeterPath;
        if (argc != 2) {
            NSLog(@"沒有獲得包路徑!");
        }else{
            bundleGreeterPath = [NSString stringWithUTF8String:argv[1]];
            if (bundleGreeterPath == nil) {
                NSLog(@"Bundle not found at this path!");
            }else{
                id<C9Greeter> greeterB;
                NSBundle * greeterBundle = [NSBundle bundleWithPath:bundleGreeterPath];
                if (greeterBundle == nil) {
                    NSLog(@"Bundle not found at this path!");
                }else{
                    NSError * error;
                    BOOL isLoaded = [greeterBundle loadAndReturnError:&error];
                    if (!isLoaded) {
                        NSLog(@"Error = %@",[error localizedDescription]);
                    }else{
                        //載入包後使用該包建立一個物件並向這個物件傳送一條訊息
                        Class greeterClass = [greeterBundle classNamed:@"C9CustomGreeter"];
                        greeterB = [[greeterClass alloc] init];
                        NSLog(@"%@",[greeterB greeting:@"Hello-2"]);
                        
                        //使用完以動態方式載入的包,現在解除安裝它
                        //先釋放所有用包中類建立的物件!
                        greeterB = nil;
                        BOOL isUnloaded = [greeterBundle unload];
                        if (!isUnloaded) {
                            NSLog(@"Counld not unload bundle!");
                        }
                    }
                }
            }
        }
        return 0;
    }
}

       目錄結構截圖署截圖說明如下:


       (Pic9_8)


(Pic9_9)

       和原來的工程1一樣,還是要設定包路徑引數;

       執行C9DynaLoader,log如下:

       2017-12-18 15:11:32.521433+0800 C9DynaLoader[51745:5414801] Hello, World!

       2017-12-18 15:11:32.521733+0800 C9DynaLoader[51745:5414801] Hello-1,Wrold

       2017-12-18 15:11:32.530178+0800 C9DynaLoader[51745:5414801] Hello-2,Universe

       至此,我們看到:

        greeterB物件是通過以動態方式載入的C9CustomGreeter包建立的;這就是通過使用NSBundle類以動態的方式載入包,向正在執行的程式新增程式碼和資源的方式;


相關推薦

iOS程式設計基礎-OC-專家級技巧使用執行系統API

     第九章 專家級技巧:使用執行時系統API      第7章和第8章介紹了OC的動態特性和用於實現這些特性的執行時系統結構;      本章將通過幾個示例程式使你獲得使用執行時系統功能及其API的實踐經驗;         你將使用NSInvocati

iOS程式設計基礎-OC-專家級技巧使用ARC

     第6章 專家級技巧:使用ARC      本章是第一部分的最後一章;          本章介紹ARC記憶體管理中的細微之處;          如直接橋接物件使用AR

iOS程式設計基礎-OC-執行系統

第7章 執行時系統  7.4 動態繫結      動態繫結(dynamic binding):         是指在執行程式時(而不是在編譯時)將訊息與方法對應起來的處理過程;

iOS程式設計基礎-OC-執行系統

 第7章 執行時系統      終於到了執行時這一章,讓我們來一步一步揭開它神祕的面紗吧;      OC擁有相當多的動態特性,這些特性在執行程式時發揮作用,而不是在編譯或連結程式碼時發

iOS學習筆記56Runtime-Objective-C Runtime 執行之三方法與訊息

前面我們討論了Runtime中對類和物件的處理,及對成員變數與屬性的處理。這一章,我們就要開始討論Runtime中最有意思的一部分:訊息處理機制。我們將詳細討論訊息的傳送及訊息的轉發。不過在討論訊息之前,我們先來了解一下與方法相關的一些內容。 基礎資料型別 SEL

.NET基礎知識

clear .net基礎 continue 連接數 public 全局 return語句 實現 tar 1、打印出由*號組成的倒三角形的圖案******* 4*2-1 要求: 1、輸入倒三角的行數,行數範圍3-18,對於不在範圍的行數,拋出提示. ***** 3*2-1

11-Linux基礎入門-Linux的通配符

cal echo 微信公眾平臺 當前 技術分享 vertical hub font not 一、概述Linux的通配符和正則表達式是不一樣的,因此代表的意義也是有較大區別的。通配符一般用於用戶命令行bash環境,而Linux正則表達式用於grep、sed、awk場景。符號代

PL/SQL程式設計基礎知識

--PL/SQL變數的宣告和賦值 declare v_ename varchar2(30);--定義變數 begin v_ename:='&請輸入名字';--接受鍵盤輸入 dbms_output.put_line(v_ename); end; --put_line :列印換行

PHP基礎知識

PHP面向物件技術 一、類的定義:    1 <?php 2 class Cat{ 3 //定義成員變數,需使用var關鍵字 4 var $name; 5 var $sex; 6 var $age;

linux基礎Redhat7系統中使用指令碼安裝虛擬機器

安裝虛擬機器 要安裝一個虛擬機器,我們需要開啟virt-manager然後一步一步的去設定才能到達安裝頁面,但是有沒有更好的辦法能夠只需要一部到達安裝虛擬機器的頁面呢,下面就給大家展示用指令碼直接到達安裝一個虛擬機器的頁面 安裝虛擬機器我們當然要在真機中操作,每一步都一定要仔細檢查

深度學習基礎系列| Dropout VS Batch Normalization? 是時候放棄Dropout了 深度學習基礎系列| Batch Normalization

  Dropout是過去幾年非常流行的正則化技術,可有效防止過擬合的發生。但從深度學習的發展趨勢看,Batch Normalizaton(簡稱BN)正在逐步取代Dropout技術,特別是在卷積層。本文將首先引入Dropout的原理和實現,然後觀察現代深度模型Dropout的使用情況,並與BN進行實驗比對,從原

Atitit web 之道 艾龍著 Atitit web 之道 艾龍艾提拉著v2 saa.docx 1. 第1章 Web程式設計基礎知識 1 3 1.1. 1.1 什麼是Web 1 3 1.2.

Atitit web 之道 艾龍著 Atitit web 之道 艾龍艾提拉著v2 saa.docx   1. 第1章 Web程式設計基礎知識 (1) 3 1.1. 1.1 什麼是Web (1) 3 1.2. 1.2 Web的工作原理 (2) 3 1.3. 1.3 Int

Go程式設計基礎—函式func

https://blog.csdn.net/qq_22063697/article/details/74858264 函式是基本的程式碼塊,用於執行一個任務,是構成程式碼執行的邏輯結構。 在Go語言中,函式的基本組成為:關鍵字func、函式名、引數列表、返回值、函式體和返回語句。 函

Python3基礎if elif else 判斷

一、基本使用 if condition1: true1_expressions elif condition2: true2_expressions elif condtion3: true3_expressions elif ... ... else

Unity Editor 基礎EditorUtility編輯器工具

EditorUtility 編輯器工具 轉自:http://blog.csdn.net/liqiangeastsun/article/details/42174339,請檢視原文,尊重樓主原創版權。 這是一個編輯器類,如果想使用它你需要把它放到工程目錄下的Assets/E

深度學習基礎系列| Dropout VS Batch Normalization? 是時候放棄Dropout了

  Dropout是過去幾年非常流行的正則化技術,可有效防止過擬合的發生。但從深度學習的發展趨勢看,Batch Normalizaton(簡稱BN)正在逐步取代Dropout技術,特別是在卷積層。本文將首先引入Dropout的原理和實現,然後觀察現代深度模型Dropout的使用情況,並與BN進行實驗比對,從原

【鏈塊技術54期】智慧合約基礎語言——Solidity繼承

原文連結:以太坊智慧合約(九):Solidity繼承   本文主要講解了有關智慧合約繼承的概念、繼承的引數傳遞、重寫函式以及Solidity的繼承中的呼叫關係與多繼承。掌握區塊鏈技術,學習智慧合約。   一、目錄 ☞繼承的概念 ☞繼承的引數傳

iOS開發基礎Swift04— 迴圈

迴圈的介紹在開發中經常會需要迴圈常見的迴圈有:for/while/do while.這裡我們只介紹for/while,因為for/while最常見for迴圈的寫法最常規寫法// 傳統寫法 for var

java 併發程式設計學習筆記執行緒併發拓展

                                         多執行緒

OpenGL程式設計 基礎與滑鼠的互動

#include "stdafx.h" #include <cstdlib> #include <gl\glut.h> const int screenWidth = 600; const int screenHeight = 480; class GLintPoint { publ