কোনও এপিআই বাস্তবায়ন করার সময় আমি কীভাবে ব্লকে নিজেকে ক্যাপচার করা এড়াতে পারি?


222

আমার একটি কার্যকরী অ্যাপ্লিকেশন রয়েছে এবং আমি এটিকে এক্সকোড ৪.২-এ এআরসি তে রূপান্তর করার জন্য কাজ করছি। প্রাক-চেক সতর্কতাগুলির selfমধ্যে একটি ব্লকে দৃ strongly ়ভাবে ক্যাপচারের সাথে জড়িত যা একটি রক্ষণ চক্রের দিকে পরিচালিত করে। সমস্যাটি বর্ণনা করার জন্য আমি একটি সাধারণ কোড নমুনা তৈরি করেছি। আমি বিশ্বাস করি আমি এর অর্থটি বুঝতে পেরেছি তবে এই ধরণের পরিস্থিতি বাস্তবায়নের "সঠিক" বা প্রস্তাবিত উপায়ের বিষয়ে আমি নিশ্চিত নই।

  • স্ব মায়াপি ক্লাসের উদাহরণ
  • আমার প্রশ্নের সাথে প্রাসঙ্গিক অবজেক্ট এবং ব্লকগুলির সাথে কেবল ইন্টারঅ্যাকশন দেখানোর জন্য নীচের কোডটি সরল করা হয়েছে
  • ধরে নিই যে মাইএপিআই একটি প্রত্যন্ত উত্স থেকে ডেটা পেয়েছে এবং মাইডাটাপ্রসেসর সেই ডেটাতে কাজ করে এবং একটি আউটপুট উত্পাদন করে
  • প্রসেসরটি অগ্রগতি ও স্থিতি যোগাযোগের জন্য ব্লকগুলিতে কনফিগার করা হয়েছে

কোড নমুনা:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

প্রশ্ন: আমি কী "ভুল" করছি এবং / অথবা কীভাবে এটিআরসি সম্মেলনের সাথে সামঞ্জস্য করতে সংশোধন করা উচিত?

উত্তর:


509

সংক্ষিপ্ত উত্তর

selfসরাসরি অ্যাক্সেস করার পরিবর্তে , আপনাকে এমন কোনও রেফারেন্স থেকে ধরে রাখা হবে যা পরোক্ষভাবে ব্যবহার করা উচিত। আপনি যদি স্বয়ংক্রিয় রেফারেন্স গণনা (এআরসি) ব্যবহার না করে থাকেন তবে আপনি এটি করতে পারেন:

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

__blockশব্দ চিহ্ন ভেরিয়েবল যে ব্লক ভিতরে পরিবর্তন করা যাবে (আমরা যে কাজ করছি) কিন্তু তারা স্বয়ংক্রিয়ভাবে যখন ব্লক বজায় রাখা হয় অপরিবর্তিত রাখা নেই (যতক্ষণ না আপনি এআরসি ব্যবহার করছেন)। আপনি যদি এটি করেন, আপনার অবশ্যই নিশ্চিত হওয়া উচিত যে মাইডাটাপ্রসেসর ইনস্ট্যান্স প্রকাশ হওয়ার পরে অন্য কোনও কিছুই ব্লকটি কার্যকর করার চেষ্টা করছে না। (আপনার কোডের কাঠামো দেওয়া, এটি কোনও সমস্যা হওয়া উচিত নয়)) আরও পড়ুন__block

আপনি যদি এআরসি ব্যবহার করে থাকেন তবে __blockপরিবর্তনের শব্দার্থবিদ্যা এবং রেফারেন্স ধরে রাখা হবে , এক্ষেত্রে আপনার __weakপরিবর্তে এটি ঘোষণা করা উচিত ।

দীর্ঘ উত্তর

ধরা যাক আপনার এইরকম কোড ছিল:

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

এখানে সমস্যা হ'ল স্ব ব্লকের একটি রেফারেন্স ধরে রাখছেন; ইতোমধ্যে ব্লকের অবশ্যই তার প্রতিনিধিদের সম্পত্তি আনতে এবং প্রতিনিধিটিকে একটি পদ্ধতি প্রেরণের জন্য স্ব সম্পর্কিত একটি রেফারেন্স বজায় রাখতে হবে। যদি আপনার অ্যাপ্লিকেশানের অন্য সমস্ত কিছু এই অবজেক্টের জন্য তার রেফারেন্স প্রকাশ করে তবে এর ধরে রাখার গণনা শূন্য হবে না (কারণ ব্লকটি এটি দেখিয়েছে) এবং ব্লকটি কোনও ভুল করছে না (কারণ বস্তুটি এটি দেখিয়েছে) এবং তাই অবজেক্টের জুড়ি গাদা মধ্যে ফাঁস হবে, মেমরি দখল করে কিন্তু একটি ডিবাগার ছাড়াই চিরতরে অ্যাক্সেসযোগ্য। মর্মান্তিক, সত্যিই।

পরিবর্তে এটি করে সহজেই এই কেসটি ঠিক করা যায়:

id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
    [progressDelegate processingWithProgress:percentComplete];
}

এই কোডটিতে স্ব ব্লকটি ধরে রাখছে, ব্লকটি প্রতিনিধিটিকে ধরে রাখছে, এবং কোনও চক্র নেই (এখান থেকে দৃশ্যমান; প্রতিনিধি আমাদের বস্তু ধরে রাখতে পারে তবে এটি এখন আমাদের হাতের বাইরে)। এই কোডটি একইভাবে কোনও ফাঁস হওয়ার ঝুঁকিপূর্ণ হবে না, কারণ ব্লকটি তৈরি হওয়ার সময় প্রতিনিধি সম্পত্তিটির মান ক্যাপচার করা হয়, যখন এটি কার্যকর হয় তখন সন্ধান করার পরিবর্তে তাকাতে হবে। একটি পার্শ্ব প্রতিক্রিয়া হ'ল, যদি আপনি এই ব্লকটি তৈরি হওয়ার পরে প্রতিনিধি পরিবর্তন করেন তবে ব্লকটি এখনও পুরানো প্রতিনিধিকে আপডেট বার্তা প্রেরণ করবে। এটি হওয়ার সম্ভাবনা রয়েছে কিনা তা আপনার আবেদনের উপর নির্ভর করে।

এমনকি যদি আপনি সেই আচরণে শান্ত ছিলেন, আপনি এখনও নিজের ক্ষেত্রে সেই কৌশলটি ব্যবহার করতে পারবেন না:

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

এখানে আপনি selfপদ্ধতি কলটিতে সরাসরি প্রতিনিধিটির কাছে যাচ্ছেন, সুতরাং আপনাকে এটি কোনও জায়গায় .ুকতে হবে। ব্লকের ধরণের সংজ্ঞাটির উপরে যদি আপনার নিয়ন্ত্রণ থাকে, তবে পরামিতি হিসাবে ডেলিগেটকে ব্লকের মধ্যে স্থান দেওয়া ভাল হবে:

self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};

এই সমাধানটি ধরে রাখার চক্র এড়ানো এবং সর্বদা বর্তমান প্রতিনিধিকে কল করে।

আপনি যদি ব্লকটি পরিবর্তন করতে না পারেন তবে আপনি এটির সাথে ডিল করতে পারেন । একটি বজায় রাখা চক্র হ'ল সতর্কতা, ত্রুটি নয়, এটি হ'ল তারা আপনার অ্যাপ্লিকেশনটির জন্য অযথা ডুমস বানান না। যদি MyDataProcessorব্লক মুক্তি যখন অপারেশন সম্পূর্ণ হলে, আগে তার পিতা বা মাতা এটা মুক্তি চেষ্টা করবে সক্ষম হয়, চক্র ভাঙ্গা হবে এবং সবকিছু সঠিকভাবে পরিষ্কার করা হবে না। যদি আপনি এটি সম্পর্কে নিশ্চিত হতে পারেন, তবে সঠিক জিনিসটি #pragmaহ'ল কোডের সেই ব্লকের জন্য সতর্কতাগুলি দমন করতে একটি ব্যবহার করা উচিত । (অথবা প্রতি-ফাইল সংকলক পতাকা ব্যবহার করুন But তবে পুরো প্রকল্পের জন্য সতর্কতাটি অক্ষম করবেন না))

উপরোক্ত অনুরূপ কৌশল ব্যবহার করে আপনি কোনও রেফারেন্সকে দুর্বল বা অপ্রস্তুত ঘোষণা করে এবং ব্লকটিতে এটি ব্যবহার করতে পারেন। উদাহরণ স্বরূপ:

__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

উপরের তিনটিই আপনাকে ফলাফলটি ধরে না রেখে রেফারেন্স দেবে, যদিও তারা সকলেই কিছুটা আলাদাভাবে আচরণ করে: __weakঅবজেক্টটি প্রকাশিত হলে রেফারেন্সটি শূন্য করার চেষ্টা করবে; __unsafe_unretainedআপনাকে অবৈধ পয়েন্টার দিয়ে চলে যাবে; __blockপ্রকৃতপক্ষে অন্য মাত্রার ইন্ডিয়ারেশন যুক্ত করবে এবং আপনাকে ব্লকের মধ্যে থেকে রেফারেন্সের মানটি পরিবর্তন করতে দেবে (এই ক্ষেত্রে অপ্রাসঙ্গিক, যেহেতু dpঅন্য কোথাও ব্যবহার করা হয়নি)।

কোন কোডটি আপনি পরিবর্তন করতে পারবেন এবং কী কী পারবেন না তার উপর সবচেয়ে ভাল নির্ভর করবে। তবে আশা করি এটি আপনাকে কীভাবে এগিয়ে যেতে হবে সে সম্পর্কে কিছু ধারণা দিয়েছে।


1
দুর্দান্ত উত্তর! ধন্যবাদ, আমি কী চলছে এবং কীভাবে এটি সব কাজ করে সে সম্পর্কে আমার আরও অনেক ভাল ধারণা রয়েছে। এই ক্ষেত্রে, আমার সমস্ত কিছুর উপর নিয়ন্ত্রণ রয়েছে তাই আমি প্রয়োজন অনুসারে কয়েকটি বস্তুর পুনঃনির্মাণ করব।
এক্সজোনস

18
ও_ও আমি কিছুটা আলাদা সমস্যা নিয়ে যাচ্ছিলাম, পড়া আটকে গেলাম, এবং এখন সমস্ত পৃষ্ঠা জ্ঞান এবং শীতল বোধ করে এই পৃষ্ঠাটি ছেড়ে দিন। ধন্যবাদ!
Orc JMR

ঠিক আছে, যদি কোনও কারণে ব্লক কার্যকর করার মুহুর্তে dpমুক্তি দেওয়া হয় (উদাহরণস্বরূপ যদি এটি একটি ভিউ কন্ট্রোলার ছিল এবং এটি পপ করা হয়েছিল), তবে লাইনটি [dp.delegate ...EXC_BADACCESS সৃষ্টি করবে?
পিটোন

ব্লকযুক্ত সম্পত্তি (উদাহরণস্বরূপ ডেটাপ্রসেস.প্রোগ্রেস) হওয়া উচিত strongনাকি weak?
ডিজেস্কিনার

1
আপনার কাছে লিবিস্ট্টোবিজির একটি নজর থাকতে পারে যা দুটি হ্যান্ডি ম্যাক্রোগুলি সরবরাহ করে @weakify(..)এবং যা আপনাকে অরক্ষণীয় ফ্যাশনে ব্লকে @strongify(...)ব্যবহার করতে দেয় self

25

আপনি যখন ইতিবাচক হন যে ভবিষ্যতে চক্রটি নষ্ট হয়ে যাবে তখন সতর্কতা দমন করার বিকল্পও রয়েছে:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

এই ভাবে আপনি নিয়ে বানর হবে না __weak, selfaliasing এবং স্পষ্ট ivar দিন যোগ করে।


8
খুব খারাপ অনুশীলনের মতো মনে হচ্ছে কোডের 3 টিরও বেশি লাইন লাগে যা __weak id দুর্বল দ্বারা প্রতিস্থাপন করা যেতে পারে = সেল্ফ;
বেন সিনক্লেয়ার

3
কোডগুলির একটি বৃহত ব্লক প্রায়শই রয়েছে যা দমন করা সতর্কতা থেকে উপকৃত হতে পারে।
জুল

2
ছাড়া __weak id weakSelf = self;সতর্কবার্তা দমন চেয়ে মৌলিকভাবে ভিন্ন আচরণ আছে। "... আপনি যদি ইতিবাচক হন যে ধরে রাখার চক্রটি ভেঙে যাবে" দিয়ে প্রশ্নটি শুরু হয়েছিল
টিম

প্রায়শই লোকেরা অন্ধভাবে ভেরিয়েবলকে দুর্বল করে দেয়, সত্যিকারের অনুধাবনগুলি না বুঝে। উদাহরণস্বরূপ, আমি দেখেছি লোকেরা কোনও বস্তুকে দুর্বল করে এবং তারপরে, তারা যে ব্লকটি করে: [array addObject:weakObject];যদি দুর্বলঅবজেক্টটি প্রকাশ করা হয়, এটি ক্রাশের কারণ হয়ে দাঁড়ায়। স্পষ্টতই এটি একটি ধরে রাখা চক্রের চেয়ে বেশি পছন্দ করা হয় না। আপনার ব্লকটি দুর্বল হয়ে যাওয়ার পরোয়ানা দেওয়ার পক্ষে যথেষ্ট দীর্ঘ সময় বেঁচে আছে কিনা তাও আপনাকে বুঝতে হবে এবং ব্লকের ক্রিয়াটি দুর্বল বস্তুটি এখনও বৈধ কিনা তার উপর নির্ভর করতে চায় কিনা তাও আপনাকে বুঝতে হবে।
mahboudz

14

একটি সাধারণ সমাধানের জন্য, আমি এগুলি পূর্বনির্ধারক শিরোনামে সংজ্ঞায়িত করেছি। ক্যাপচারিং এড়ানো এবং এখনও ব্যবহার এড়াতে সংকলক সহায়তা সক্ষম করেid

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

তারপরে কোড আপনি করতে পারেন:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};

সম্মত, এটি ব্লকের ভিতরে কোনও সমস্যা তৈরি করতে পারে। রিঅ্যাকটিভ selfকোকোয়াতে এই সমস্যার জন্য আরও একটি আকর্ষণীয় সমাধান রয়েছে যা আপনাকে আপনার ব্লকের @Wakify (স্ব) এর ভিতরে ব্যবহার চালিয়ে যেতে দেয় ; আইডি ব্লক = ^ {@ স্ট্রংফায়াই (স্ব); [self.delegate myAPIDidFinish: self]; };
ড্যামিয়েন পন্টিফেক্স

@ ড্যাম্পন্টিফেক্স এটি লিবক্সটোবিজেসি গিথুব
জেএসপাহারসামার্স

11

আমি বিশ্বাস করি যে এআরসিবিহীন সমাধানটি __blockকীওয়ার্ডটি ব্যবহার করে এআরসি-র সাথেও কাজ করে :

সম্পাদনা: এআরসি রিলিজ নোটগুলিতে রূপান্তরকরণের পরে , __blockস্টোরেজ সহ ঘোষিত একটি সামগ্রী এখনও বজায় রয়েছে। __weak(পছন্দসই) বা __unsafe_unretained(পিছনের সামঞ্জস্যের জন্য ) ব্যবহার করুন ।

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

কি জানে না যে __blockশব্দ এটা প্রসঙ্গ ধারনকারী এড়ানো। ধন্যবাদ! আমি আমার একচেটিয়া উত্তর আপডেট করেছি। :-)
বেনজাদো

3
অ্যাপল ডক্স অনুসারে "ম্যানুয়াল রেফারেন্স গণনা মোডে, __ block id x; এর এক্স এক্স ধরে না রাখার প্রভাব রয়েছে। এআরসি মোডে, __ block id x; এক্স ধরে রাখার ডিফল্ট (অন্যান্য সমস্ত মানের মতো)"।
এক্সজোনস

11

কয়েকটি অন্যান্য উত্তরের সংমিশ্রণে, ব্লকগুলিতে টাইপ করা দুর্বল স্বের জন্য আমি এখন এটি ব্যবহার করি:

__typeof(self) __weak welf = self;

আমি সেট করেছি যে পদ্ধতি / ফাংশনগুলিতে "ভাল" এর সম্পূর্ণতা উপসর্গ সহ একটি এক্সকোড কোড স্নিপেট হিসাবে সেট করুন যা কেবল "আমরা" টাইপ করার পরে হিট হয়।


তুমি কি নিশ্চিত? এই লিঙ্ক এবং ঝনঝন ডক্স উভয়ই মনে করে অবজেক্টের রেফারেন্স রাখতে ব্যবহার করা উচিত এবং তা ব্যবহার করা উচিত তবে এমন কোনও লিঙ্ক নয় যা রক্ষণ চক্রের কারণ হয়ে উঠবে: স্ট্যাকওভারফ্লো
কেন্ডল হেলস্টেটার জেলনার

ক্ল্যাং ডক্স থেকে: clang.llvm.org/docs/BlockLanguageSpec.html "উদ্দেশ্য-সি এবং উদ্দেশ্য-সি ++ ভাষায়, আমরা __Wak স্পেসিফেরটিকে __ block অবজেক্টের ধরণের ভেরিয়েবলের অনুমতি দিই garbage এই পরিবর্তনগুলি রক্ষা বার্তা প্রেরণ না করেই রাখতে হবে। "
কেন্ডল হেলস্টেটার জেলনার


6

সতর্কতা => "ব্লকের ভিতরে নিজেকে ক্যাপচার করা বজায় রাখার চক্রকে পরিচালিত করতে পারে"

যখন আপনি কোনও ব্লকের অভ্যন্তরে স্ব বা তার সম্পত্তি উল্লেখ করছেন যা উপরোক্ত সতর্কতা দেখানোর চেয়ে নিজের দ্বারা দৃ self়ভাবে ধরে রাখা হয়েছে retain

সুতরাং এটি এড়ানোর জন্য আমাদের এটি এক সপ্তাহ রেফারেট করতে হবে

__weak typeof(self) weakSelf = self;

পরিবর্তে ব্যবহারের পরিবর্তে

blockname=^{
    self.PROPERTY =something;
}

আমাদের ব্যবহার করা উচিত

blockname=^{
    weakSelf.PROPERTY =something;
}

দ্রষ্টব্য: ধরে রাখার চক্রটি সাধারণত তখন ঘটে যখন কিছু দুটি কীভাবে একে অপরকে উল্লেখ করে যার দ্বারা উভয়টির রেফারেন্স গণনা = 1 থাকে এবং তাদের ডেলোক পদ্ধতিটি কখনই কল হয় না।



-1

আপনি যদি নিশ্চিত হন যে আপনার কোডটি কোনও ধরে রাখার চক্র তৈরি করবে না, বা চক্রটি পরে ভেঙে যাবে, তবে সতর্কতাটি নিঃশব্দ করার সহজ উপায় হ'ল:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

[self dataProcessor].progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

[self dataProcessor].completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

এটি কাজ করার কারণটি হ'ল এক্সকোডের বিশ্লেষণ দ্বারা সম্পত্তিগুলির বিন্দু অ্যাক্সেসকে বিবেচনায় নেওয়া হয় এবং তাই

x.y.z = ^{ block that retains x}

x এর y দ্বারা (অ্যাসাইনমেন্টের বাম দিকে) এবং x এর y (ডানদিকে) ধরে রাখা হিসাবে দেখা যায়, সম্পত্তি কল অ্যাক্সেস পদ্ধতিতে কল করার পরেও পদ্ধতি কলগুলি একই বিশ্লেষণের বিষয় নয় যেগুলি ডট-অ্যাক্সেসের সমতুল্য, এমনকি যখন সেই সম্পত্তি অ্যাক্সেসের পদ্ধতিগুলি সংকলক-উত্পাদিত হয়, তখনও

[x y].z = ^{ block that retains x}

শুধুমাত্র ডান দিকটি ধরে রাখা (এক্স এর y দ্বারা) তৈরি হিসাবে দেখা যায় এবং কোনও রক্ষণ চক্রের সতর্কতা উত্পন্ন হয় না।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.